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 { 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 { BigNumber } from '@0x/utils';
import * as _ from 'lodash'; 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. * tracked in subsequent operations.
* @param address Address of the token owner * @param address Address of the token owner
* @param name Name of the token owner * @param name Name of the token owner
@ -83,8 +83,8 @@ export class BalanceStore {
*/ */
private _assertEthBalancesEqual(rhs: BalanceStore): void { private _assertEthBalancesEqual(rhs: BalanceStore): void {
for (const ownerAddress of [...this._ownerAddresses, ...rhs._ownerAddresses]) { for (const ownerAddress of [...this._ownerAddresses, ...rhs._ownerAddresses]) {
const thisBalance = _.get(this.balances.eth, [ownerAddress], new BigNumber(0)); const thisBalance = _.get(this.balances.eth, [ownerAddress], constants.ZERO_AMOUNT);
const rhsBalance = _.get(rhs.balances.eth, [ownerAddress], new BigNumber(0)); const rhsBalance = _.get(rhs.balances.eth, [ownerAddress], constants.ZERO_AMOUNT);
expect(thisBalance, `${this._readableAddressName(ownerAddress)} ETH balance`).to.bignumber.equal( expect(thisBalance, `${this._readableAddressName(ownerAddress)} ETH balance`).to.bignumber.equal(
rhsBalance, rhsBalance,
); );
@ -98,8 +98,8 @@ export class BalanceStore {
private _assertErc20BalancesEqual(rhs: BalanceStore): void { private _assertErc20BalancesEqual(rhs: BalanceStore): void {
for (const ownerAddress of [...this._ownerAddresses, ...rhs._ownerAddresses]) { for (const ownerAddress of [...this._ownerAddresses, ...rhs._ownerAddresses]) {
for (const tokenAddress of [...this._tokenAddresses.erc20, ...rhs._tokenAddresses.erc20]) { for (const tokenAddress of [...this._tokenAddresses.erc20, ...rhs._tokenAddresses.erc20]) {
const thisBalance = _.get(this.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], new BigNumber(0)); const rhsBalance = _.get(rhs.balances.erc20, [ownerAddress, tokenAddress], constants.ZERO_AMOUNT);
expect( expect(
thisBalance, thisBalance,
`${this._readableAddressName(ownerAddress)} ${this._readableAddressName(tokenAddress)} balance`, `${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 { getRandomInteger } from '@0x/contracts-test-utils';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -7,7 +7,6 @@ import {
validCreateStakingPoolAssertion, validCreateStakingPoolAssertion,
validDecreaseStakingPoolOperatorShareAssertion, validDecreaseStakingPoolOperatorShareAssertion,
} from '../function-assertions'; } from '../function-assertions';
import { SimulationEnvironment } from '../simulation/simulation';
import { AssertionResult } from '../utils/function_assertions'; import { AssertionResult } from '../utils/function_assertions';
import { Actor, Constructor } from './base'; 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; this.actor = (this as any) as Actor;
// Register this mixin's assertion generators // Register this mixin's assertion generators
if (this.actor.simulationEnvironment !== undefined) {
this.actor.simulationActions = { this.actor.simulationActions = {
...this.actor.simulationActions, ...this.actor.simulationActions,
validCreateStakingPool: this._validCreateStakingPool(this.actor.simulationEnvironment), validCreateStakingPool: this._validCreateStakingPool(),
validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare( validDecreaseStakingPoolOperatorShare: this._validDecreaseStakingPoolOperatorShare(),
this.actor.simulationEnvironment,
),
}; };
} }
}
/** /**
* Creates a staking pool and returns the ID of the new pool. * 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[] { private _getOperatorPoolIds(stakingPools: StakingPoolById): string[] {
const operatorPools = _.pickBy( const operatorPools = _.pickBy(stakingPools, pool => pool.operator === this.actor.address);
simulationEnvironment.stakingPools,
pool => pool.operator === this.actor.address,
);
return Object.keys(operatorPools); return Object.keys(operatorPools);
} }
private async *_validCreateStakingPool( private async *_validCreateStakingPool(): AsyncIterableIterator<AssertionResult> {
simulationEnvironment: SimulationEnvironment, const { stakingPools } = this.actor.simulationEnvironment!;
): AsyncIterableIterator<AssertionResult> { const assertion = validCreateStakingPoolAssertion(this.actor.deployment, stakingPools);
const assertion = validCreateStakingPoolAssertion(
this.actor.deployment,
simulationEnvironment.stakingPools,
);
while (true) { while (true) {
const operatorShare = getRandomInteger(0, constants.PPM); const operatorShare = getRandomInteger(0, constants.PPM);
yield assertion.executeAsync(operatorShare, false, { from: this.actor.address }); yield assertion.executeAsync(operatorShare, false, { from: this.actor.address });
} }
} }
private async *_validDecreaseStakingPoolOperatorShare( private async *_validDecreaseStakingPoolOperatorShare(): AsyncIterableIterator<AssertionResult | void> {
simulationEnvironment: SimulationEnvironment, const { stakingPools } = this.actor.simulationEnvironment!;
): AsyncIterableIterator<AssertionResult | void> { const assertion = validDecreaseStakingPoolOperatorShareAssertion(this.actor.deployment, stakingPools);
const assertion = validDecreaseStakingPoolOperatorShareAssertion(
this.actor.deployment,
simulationEnvironment.stakingPools,
);
while (true) { while (true) {
const poolId = _.sample(this._getOperatorPoolIds(simulationEnvironment)); const poolId = _.sample(this._getOperatorPoolIds(stakingPools));
if (poolId === undefined) { if (poolId === undefined) {
yield undefined; yield undefined;
} else { } 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 }); 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 * as _ from 'lodash';
import { validMoveStakeAssertion, validStakeAssertion, validUnstakeAssertion } from '../function-assertions'; import { validMoveStakeAssertion, validStakeAssertion, validUnstakeAssertion } from '../function-assertions';
import { SimulationEnvironment } from '../simulation/simulation';
import { AssertionResult } from '../utils/function_assertions'; import { AssertionResult } from '../utils/function_assertions';
import { Actor, Constructor } from './base'; 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 // Register this mixin's assertion generators
if (this.actor.simulationEnvironment !== undefined) {
this.actor.simulationActions = { this.actor.simulationActions = {
...this.actor.simulationActions, ...this.actor.simulationActions,
validStake: this._validStake(this.actor.simulationEnvironment), validStake: this._validStake(),
validUnstake: this._validUnstake(this.actor.simulationEnvironment), validUnstake: this._validUnstake(),
validMoveStake: this._validMoveStake(this.actor.simulationEnvironment), validMoveStake: this._validMoveStake(),
}; };
} }
}
/** /**
* Stakes the given amount of ZRX. If `poolId` is provided, subsequently delegates the newly * 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( private async *_validStake(): AsyncIterableIterator<AssertionResult> {
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
const { zrx } = this.actor.deployment.tokens; 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); const assertion = validStakeAssertion(deployment, balanceStore, globalStake, this.stake);
while (true) { while (true) {
await simulationEnvironment.balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
const zrxBalance = simulationEnvironment.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 = getRandomInteger(0, zrxBalance);
yield assertion.executeAsync(amount, { from: this.actor.address }); yield assertion.executeAsync(amount, { from: this.actor.address });
} }
} }
private async *_validUnstake( private async *_validUnstake(): AsyncIterableIterator<AssertionResult> {
simulationEnvironment: SimulationEnvironment,
): AsyncIterableIterator<AssertionResult> {
const { stakingWrapper } = this.actor.deployment.staking; 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); const assertion = validUnstakeAssertion(deployment, balanceStore, globalStake, this.stake);
while (true) { while (true) {
await simulationEnvironment.balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
const undelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync( const undelegatedStake = await stakingWrapper.getOwnerStakeByStatus.callAsync(
this.actor.address, this.actor.address,
StakeStatus.Undelegated, StakeStatus.Undelegated,
@ -103,16 +96,9 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
} }
} }
private async *_validMoveStake( private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> {
simulationEnvironment: SimulationEnvironment, const { deployment, globalStake, stakingPools } = this.actor.simulationEnvironment!;
): AsyncIterableIterator<AssertionResult> { const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools);
const { deployment, globalStake } = simulationEnvironment;
const assertion = validMoveStakeAssertion(
deployment,
globalStake,
this.stake,
simulationEnvironment.stakingPools,
);
while (true) { while (true) {
const fromPoolId = _.sample(Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total']))); 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); : (_.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const from = new StakeInfo(fromStatus, fromPoolId); const from = new StakeInfo(fromStatus, fromPoolId);
const toPoolId = _.sample(Object.keys(simulationEnvironment.stakingPools)); const toPoolId = _.sample(Object.keys(stakingPools));
const toStatus = const toStatus =
toPoolId === undefined toPoolId === undefined
? StakeStatus.Undelegated ? StakeStatus.Undelegated

View File

@ -1,6 +1,7 @@
{ {
"extends": ["@0x/tslint-config"], "extends": ["@0x/tslint-config"],
"rules": { "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; const { stakingWrapper } = deployment.staking;
return new FunctionAssertion(stakingWrapper.createStakingPool, { return new FunctionAssertion(stakingWrapper.createStakingPool, {
// Returns the expected ID of th created pool
before: async () => { before: async () => {
const lastPoolId = await stakingWrapper.lastPoolId.callAsync(); const lastPoolId = await stakingWrapper.lastPoolId.callAsync();
// Effectively the last poolId + 1, but as a bytestring
return `0x${new BigNumber(lastPoolId) return `0x${new BigNumber(lastPoolId)
.plus(1) .plus(1)
.toString(16) .toString(16)
@ -35,9 +37,12 @@ export function validCreateStakingPoolAssertion(
) => { ) => {
logUtils.log(`createStakingPool(${operatorShare}, ${addOperatorAsMaker}) => ${expectedPoolId}`); 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 log = result.receipt!.logs[0]; // tslint:disable-line:no-non-null-assertion
const actualPoolId = (log as any).args.poolId; const actualPoolId = (log as any).args.poolId;
expect(actualPoolId).to.equal(expectedPoolId); expect(actualPoolId).to.equal(expectedPoolId);
// Adds the new pool to local state
pools[actualPoolId] = { pools[actualPoolId] = {
operator: txData.from as string, operator: txData.from as string,
operatorShare, operatorShare,

View File

@ -19,8 +19,10 @@ export function validDecreaseStakingPoolOperatorShareAssertion(
after: async (_beforeInfo, _result: FunctionResult, poolId: string, expectedOperatorShare: number) => { after: async (_beforeInfo, _result: FunctionResult, poolId: string, expectedOperatorShare: number) => {
logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`); logUtils.log(`decreaseStakingPoolOperatorShare(${poolId}, ${expectedOperatorShare})`);
// Checks that the on-chain pool's operator share has been updated.
const { operatorShare } = await stakingWrapper.getStakingPool.callAsync(poolId); const { operatorShare } = await stakingWrapper.getStakingPool.callAsync(poolId);
expect(operatorShare).to.bignumber.equal(expectedOperatorShare); expect(operatorShare).to.bignumber.equal(expectedOperatorShare);
// Updates the pool in local state.
pools[poolId].operatorShare = operatorShare; 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)); _.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 * Returns a FunctionAssertion for `moveStake` which assumes valid input is provided. The
* FunctionAssertion checks that the staker's * FunctionAssertion checks that the staker's
@ -50,35 +100,11 @@ export function validMoveStakeAssertion(
); );
const owner = txData.from as string; 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) { // Update local balances to match the expected result of this `moveStake` operation
incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount); const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, 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);
}
// Fetches on-chain owner stake balances and checks against local balances
const ownerUndelegatedStake = { const ownerUndelegatedStake = {
...new StoredBalance(), ...new StoredBalance(),
...(await stakingWrapper.getOwnerStakeByStatus.callAsync(owner, StakeStatus.Undelegated)), ...(await stakingWrapper.getOwnerStakeByStatus.callAsync(owner, StakeStatus.Undelegated)),
@ -90,6 +116,7 @@ export function validMoveStakeAssertion(
expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]); expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]);
expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total); 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( const globalUndelegatedStake = await stakingWrapper.getGlobalStakeByStatus.callAsync(
StakeStatus.Undelegated, StakeStatus.Undelegated,
); );
@ -97,6 +124,7 @@ export function validMoveStakeAssertion(
expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]); expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]);
expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]); expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]);
// Fetches on-chain pool stake balances and checks against local balances
for (const poolId of updatedPools) { for (const poolId of updatedPools) {
const stakeDelegatedByOwner = await stakingWrapper.getStakeDelegatedToPoolByOwner.callAsync( const stakeDelegatedByOwner = await stakingWrapper.getStakeDelegatedToPoolByOwner.callAsync(
owner, owner,

View File

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

View File

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

View File

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