fixing bugs

This commit is contained in:
Michael Zhu 2019-12-04 16:14:46 -08:00
parent be0e6c8925
commit ccb477687a
19 changed files with 238 additions and 201 deletions

View File

@ -100,9 +100,9 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
private async *_validEndEpoch(): AsyncIterableIterator<AssertionResult | void> { private async *_validEndEpoch(): AsyncIterableIterator<AssertionResult | void> {
const assertion = validEndEpochAssertion(this.actor.deployment, this.actor.simulationEnvironment!); const assertion = validEndEpochAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
const { currentEpoch } = this.actor.simulationEnvironment!;
const { stakingWrapper } = this.actor.deployment.staking; const { stakingWrapper } = this.actor.deployment.staking;
while (true) { while (true) {
const { currentEpoch } = this.actor.simulationEnvironment!;
const aggregatedStats = AggregatedStats.fromArray( const aggregatedStats = AggregatedStats.fromArray(
await stakingWrapper.aggregatedStatsByEpoch(currentEpoch.minus(1)).callAsync(), await stakingWrapper.aggregatedStatsByEpoch(currentEpoch.minus(1)).callAsync(),
); );

View File

@ -71,8 +71,8 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
private async *_validStake(): AsyncIterableIterator<AssertionResult> { private async *_validStake(): AsyncIterableIterator<AssertionResult> {
const { zrx } = this.actor.deployment.tokens; const { zrx } = this.actor.deployment.tokens;
const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!; const { deployment, balanceStore } = this.actor.simulationEnvironment!;
const assertion = validStakeAssertion(deployment, balanceStore, globalStake, this.stake); const assertion = validStakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
while (true) { while (true) {
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
@ -84,8 +84,8 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
private async *_validUnstake(): AsyncIterableIterator<AssertionResult> { private async *_validUnstake(): AsyncIterableIterator<AssertionResult> {
const { stakingWrapper } = this.actor.deployment.staking; const { stakingWrapper } = this.actor.deployment.staking;
const { deployment, balanceStore, globalStake } = this.actor.simulationEnvironment!; const { deployment, balanceStore } = this.actor.simulationEnvironment!;
const assertion = validUnstakeAssertion(deployment, balanceStore, globalStake, this.stake); const assertion = validUnstakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
while (true) { while (true) {
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
@ -102,22 +102,23 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
} }
private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> { private async *_validMoveStake(): AsyncIterableIterator<AssertionResult> {
const { deployment, globalStake, stakingPools } = this.actor.simulationEnvironment!; const { deployment, stakingPools } = this.actor.simulationEnvironment!;
const assertion = validMoveStakeAssertion(deployment, globalStake, this.stake, stakingPools); const assertion = validMoveStakeAssertion(deployment, this.actor.simulationEnvironment!, this.stake);
while (true) { while (true) {
const { currentEpoch } = this.actor.simulationEnvironment!;
const fromPoolId = Pseudorandom.sample( const fromPoolId = Pseudorandom.sample(
Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])), Object.keys(_.omit(this.stake[StakeStatus.Delegated], ['total'])),
); );
const fromStatus = const fromStatus =
fromPoolId === undefined fromPoolId === undefined || stakingPools[fromPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
? StakeStatus.Undelegated ? StakeStatus.Undelegated
: (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); : (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const from = new StakeInfo(fromStatus, fromPoolId); const from = new StakeInfo(fromStatus, fromPoolId);
const toPoolId = Pseudorandom.sample(Object.keys(stakingPools)); const toPoolId = Pseudorandom.sample(Object.keys(stakingPools));
const toStatus = const toStatus =
toPoolId === undefined toPoolId === undefined || stakingPools[toPoolId].lastFinalized.isLessThan(currentEpoch.minus(1))
? StakeStatus.Undelegated ? StakeStatus.Undelegated
: (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus); : (Pseudorandom.sample([StakeStatus.Undelegated, StakeStatus.Delegated]) as StakeStatus);
const to = new StakeInfo(toStatus, toPoolId); const to = new StakeInfo(toStatus, toPoolId);
@ -134,9 +135,17 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
private async *_validWithdrawDelegatorRewards(): AsyncIterableIterator<AssertionResult | void> { private async *_validWithdrawDelegatorRewards(): AsyncIterableIterator<AssertionResult | void> {
const { stakingPools } = this.actor.simulationEnvironment!; const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validWithdrawDelegatorRewardsAssertion(this.actor.deployment, this.actor.simulationEnvironment!); const assertion = validWithdrawDelegatorRewardsAssertion(
this.actor.deployment,
this.actor.simulationEnvironment!,
);
while (true) { while (true) {
const poolId = Pseudorandom.sample(Object.keys(stakingPools)); const prevEpoch = this.actor.simulationEnvironment!.currentEpoch.minus(1);
const poolId = Pseudorandom.sample(
Object.keys(stakingPools).filter(poolId =>
stakingPools[poolId].lastFinalized.isGreaterThanOrEqualTo(prevEpoch),
),
);
if (poolId === undefined) { if (poolId === undefined) {
yield; yield;
} else { } else {

View File

@ -83,11 +83,9 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
token: DummyERC20TokenContract, token: DummyERC20TokenContract,
): Promise<BigNumber> => { ): Promise<BigNumber> => {
let balance = balanceStore.balances.erc20[owner.address][token.address]; let balance = balanceStore.balances.erc20[owner.address][token.address];
if (balance === undefined || balance.isZero()) {
await owner.configureERC20TokenAsync(token); await owner.configureERC20TokenAsync(token);
balance = balanceStore.balances.erc20[owner.address][token.address] = balance = balanceStore.balances.erc20[owner.address][token.address] =
constants.INITIAL_ERC20_BALANCE; constants.INITIAL_ERC20_BALANCE;
}
return Pseudorandom.integer(balance.dividedToIntegerBy(2)); return Pseudorandom.integer(balance.dividedToIntegerBy(2));
}; };

View File

@ -37,6 +37,9 @@ export function validCreateStakingPoolAssertion(
args: [number, boolean], args: [number, boolean],
txData: Partial<TxData>, txData: Partial<TxData>,
) => { ) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
const [operatorShare] = args; const [operatorShare] = args;
// Checks the logs for the new poolId, verifies that it is as expected // Checks the logs for the new poolId, verifies that it is as expected

View File

@ -17,7 +17,10 @@ export function validDecreaseStakingPoolOperatorShareAssertion(
const { stakingWrapper } = deployment.staking; const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<[string, number], {}, void>(stakingWrapper, 'decreaseStakingPoolOperatorShare', { return new FunctionAssertion<[string, number], {}, void>(stakingWrapper, 'decreaseStakingPoolOperatorShare', {
after: async (_beforeInfo, _result: FunctionResult, args: [string, number], _txData: Partial<TxData>) => { after: async (_beforeInfo, result: FunctionResult, args: [string, number], _txData: Partial<TxData>) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
const [poolId, expectedOperatorShare] = args; const [poolId, expectedOperatorShare] = args;
// Checks that the on-chain pool's operator share has been updated. // Checks that the on-chain pool's operator share has been updated.

View File

@ -1,8 +1,14 @@
import { WETH9DepositEventArgs, WETH9Events } from '@0x/contracts-erc20'; import { WETH9DepositEventArgs, WETH9Events } from '@0x/contracts-erc20';
import { AggregatedStats, StakingEvents, StakingEpochEndedEventArgs } from '@0x/contracts-staking'; import {
import { expect, verifyEventsFromLogs } from '@0x/contracts-test-utils'; AggregatedStats,
StakingEvents,
StakingEpochEndedEventArgs,
StakingEpochFinalizedEventArgs,
} from '@0x/contracts-staking';
import { constants, expect, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types'; import { TxData } from 'ethereum-types';
import * as _ from 'lodash';
import { DeploymentManager } from '../deployment_manager'; import { DeploymentManager } from '../deployment_manager';
import { SimulationEnvironment } from '../simulation'; import { SimulationEnvironment } from '../simulation';
@ -25,20 +31,25 @@ export function validEndEpochAssertion(
simulationEnvironment: SimulationEnvironment, simulationEnvironment: SimulationEnvironment,
): FunctionAssertion<[], EndEpochBeforeInfo, void> { ): FunctionAssertion<[], EndEpochBeforeInfo, void> {
const { stakingWrapper } = deployment.staking; const { stakingWrapper } = deployment.staking;
const { balanceStore, currentEpoch } = simulationEnvironment; const { balanceStore } = simulationEnvironment;
return new FunctionAssertion(stakingWrapper, 'endEpoch', { return new FunctionAssertion(stakingWrapper, 'endEpoch', {
before: async () => { before: async () => {
await balanceStore.updateEthBalancesAsync(); await balanceStore.updateEthBalancesAsync();
const aggregatedStatsBefore = AggregatedStats.fromArray( const aggregatedStatsBefore = AggregatedStats.fromArray(
await stakingWrapper.aggregatedStatsByEpoch(currentEpoch).callAsync(), await stakingWrapper.aggregatedStatsByEpoch(simulationEnvironment.currentEpoch).callAsync(),
); );
const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync(); const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync();
return { wethReservedForPoolRewards, aggregatedStatsBefore }; return { wethReservedForPoolRewards, aggregatedStatsBefore };
}, },
after: async (beforeInfo: EndEpochBeforeInfo, result: FunctionResult, _args: [], _txData: Partial<TxData>) => { after: async (beforeInfo: EndEpochBeforeInfo, result: FunctionResult, _args: [], _txData: Partial<TxData>) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
const { currentEpoch } = simulationEnvironment;
// Check WETH deposit event // Check WETH deposit event
const previousEthBalance = balanceStore.balances.eth[stakingWrapper.address]; const previousEthBalance = balanceStore.balances.eth[stakingWrapper.address] || constants.ZERO_AMOUNT;
if (previousEthBalance.isGreaterThan(0)) { if (previousEthBalance.isGreaterThan(0)) {
verifyEventsFromLogs<WETH9DepositEventArgs>( verifyEventsFromLogs<WETH9DepositEventArgs>(
result.receipt!.logs, result.receipt!.logs,
@ -56,9 +67,11 @@ export function validEndEpochAssertion(
const { wethReservedForPoolRewards, aggregatedStatsBefore } = beforeInfo; const { wethReservedForPoolRewards, aggregatedStatsBefore } = beforeInfo;
const expectedAggregatedStats = { const expectedAggregatedStats = {
...aggregatedStatsBefore, ...aggregatedStatsBefore,
rewardsAvailable: balanceStore.balances.erc20[stakingWrapper.address][ rewardsAvailable: _.get(
deployment.tokens.weth.address balanceStore.balances,
].minus(wethReservedForPoolRewards), ['erc20', stakingWrapper.address, deployment.tokens.weth.address],
constants.ZERO_AMOUNT,
).minus(wethReservedForPoolRewards),
}; };
const aggregatedStatsAfter = AggregatedStats.fromArray( const aggregatedStatsAfter = AggregatedStats.fromArray(
@ -66,8 +79,9 @@ export function validEndEpochAssertion(
); );
expect(aggregatedStatsAfter).to.deep.equal(expectedAggregatedStats); expect(aggregatedStatsAfter).to.deep.equal(expectedAggregatedStats);
const expectedEpochEndedEvents = aggregatedStatsAfter.numPoolsToFinalize.isZero() verifyEventsFromLogs<StakingEpochEndedEventArgs>(
? [ result.receipt!.logs,
[
{ {
epoch: currentEpoch, epoch: currentEpoch,
numPoolsToFinalize: aggregatedStatsAfter.numPoolsToFinalize, numPoolsToFinalize: aggregatedStatsAfter.numPoolsToFinalize,
@ -75,13 +89,25 @@ export function validEndEpochAssertion(
totalFeesCollected: aggregatedStatsAfter.totalFeesCollected, totalFeesCollected: aggregatedStatsAfter.totalFeesCollected,
totalWeightedStake: aggregatedStatsAfter.totalWeightedStake, totalWeightedStake: aggregatedStatsAfter.totalWeightedStake,
}, },
] ],
: [];
verifyEventsFromLogs<StakingEpochEndedEventArgs>(
result.receipt!.logs,
expectedEpochEndedEvents,
StakingEvents.EpochEnded, StakingEvents.EpochEnded,
); );
const expectedEpochFinalizedEvents = aggregatedStatsAfter.numPoolsToFinalize.isZero()
? [
{
epoch: currentEpoch,
rewardsPaid: constants.ZERO_AMOUNT,
rewardsRemaining: aggregatedStatsAfter.rewardsAvailable,
},
]
: [];
verifyEventsFromLogs<StakingEpochFinalizedEventArgs>(
result.receipt!.logs,
expectedEpochFinalizedEvents,
StakingEvents.EpochFinalized,
);
expect(result.data, 'endEpoch should return the number of unfinalized pools').to.bignumber.equal( expect(result.data, 'endEpoch should return the number of unfinalized pools').to.bignumber.equal(
aggregatedStatsAfter.numPoolsToFinalize, aggregatedStatsAfter.numPoolsToFinalize,
); );

View File

@ -104,7 +104,7 @@ export function validFillOrderAssertion(
simulationEnvironment: SimulationEnvironment, simulationEnvironment: SimulationEnvironment,
): FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults> { ): FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults> {
const { stakingWrapper } = deployment.staking; const { stakingWrapper } = deployment.staking;
const { actors, currentEpoch } = simulationEnvironment; const { actors } = simulationEnvironment;
return new FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults>( return new FunctionAssertion<[Order, BigNumber, string], FillOrderBeforeInfo | void, FillResults>(
deployment.exchange, deployment.exchange,
@ -112,6 +112,7 @@ export function validFillOrderAssertion(
{ {
before: async (args: [Order, BigNumber, string]) => { before: async (args: [Order, BigNumber, string]) => {
const [order] = args; const [order] = args;
const { currentEpoch } = simulationEnvironment;
const maker = filterActorsByRole(actors, Maker).find(maker => maker.address === order.makerAddress); const maker = filterActorsByRole(actors, Maker).find(maker => maker.address === order.makerAddress);
const poolId = maker!.makerPoolId; const poolId = maker!.makerPoolId;
@ -139,11 +140,12 @@ export function validFillOrderAssertion(
args: [Order, BigNumber, string], args: [Order, BigNumber, string],
txData: Partial<TxData>, txData: Partial<TxData>,
) => { ) => {
const [order, fillAmount] = args;
// Ensure that the tx succeeded. // Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true(); expect(result.success, `Error: ${result.data}`).to.be.true();
const [order, fillAmount] = args;
const { currentEpoch } = simulationEnvironment;
// Ensure that the correct events were emitted. // Ensure that the correct events were emitted.
verifyFillEvents(txData, order, result.receipt!, deployment, fillAmount); verifyFillEvents(txData, order, result.receipt!, deployment, fillAmount);

View File

@ -65,12 +65,12 @@ export function validFinalizePoolAssertion(
simulationEnvironment: SimulationEnvironment, simulationEnvironment: SimulationEnvironment,
): FunctionAssertion<[string], FinalizePoolBeforeInfo, void> { ): FunctionAssertion<[string], FinalizePoolBeforeInfo, void> {
const { stakingWrapper } = deployment.staking; const { stakingWrapper } = deployment.staking;
const { currentEpoch } = simulationEnvironment;
const prevEpoch = currentEpoch.minus(1);
return new FunctionAssertion<[string], FinalizePoolBeforeInfo, void>(stakingWrapper, 'finalizePool', { return new FunctionAssertion<[string], FinalizePoolBeforeInfo, void>(stakingWrapper, 'finalizePool', {
before: async (args: [string]) => { before: async (args: [string]) => {
const [poolId] = args; const [poolId] = args;
const { currentEpoch } = simulationEnvironment;
const prevEpoch = currentEpoch.minus(1);
const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync()); const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync());
const aggregatedStats = AggregatedStats.fromArray( const aggregatedStats = AggregatedStats.fromArray(
@ -90,10 +90,14 @@ export function validFinalizePoolAssertion(
}; };
}, },
after: async (beforeInfo: FinalizePoolBeforeInfo, result: FunctionResult, args: [string]) => { after: async (beforeInfo: FinalizePoolBeforeInfo, result: FunctionResult, args: [string]) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
// // Compute relevant epochs // // Compute relevant epochs
// uint256 currentEpoch_ = currentEpoch; // uint256 currentEpoch_ = currentEpoch;
// uint256 prevEpoch = currentEpoch_.safeSub(1); // uint256 prevEpoch = currentEpoch_.safeSub(1);
const { stakingPools } = simulationEnvironment; const { stakingPools, currentEpoch } = simulationEnvironment;
const prevEpoch = currentEpoch.minus(1);
const [poolId] = args; const [poolId] = args;
const pool = stakingPools[poolId]; const pool = stakingPools[poolId];
@ -155,9 +159,8 @@ export function validFinalizePoolAssertion(
expect(events.length, 'Number of RewardsPaid events emitted').to.equal(1); expect(events.length, 'Number of RewardsPaid events emitted').to.equal(1);
const [rewardsPaidEvent] = events; const [rewardsPaidEvent] = events;
expect(rewardsPaidEvent.currentEpoch_, 'RewardsPaid event: currentEpoch_').to.bignumber.equal(currentEpoch); expect(rewardsPaidEvent.poolId, 'RewardsPaid event: poolId').to.equal(poolId);
expect(rewardsPaidEvent.poolId, 'RewardsPaid event: poolId').to.bignumber.equal(poolId); expect(rewardsPaidEvent.epoch, 'RewardsPaid event: currentEpoch_').to.bignumber.equal(currentEpoch);
expect(rewardsPaidEvent.currentEpoch_, 'RewardsPaid event: currentEpoch_').to.bignumber.equal(currentEpoch);
const { operatorReward, membersReward } = rewardsPaidEvent; const { operatorReward, membersReward } = rewardsPaidEvent;
const totalReward = operatorReward.plus(membersReward); const totalReward = operatorReward.plus(membersReward);
@ -189,6 +192,7 @@ export function validFinalizePoolAssertion(
}, },
] ]
: []; : [];
// Check for WETH transfer event emitted when paying out operator's reward. // Check for WETH transfer event emitted when paying out operator's reward.
verifyEventsFromLogs<WETH9TransferEventArgs>( verifyEventsFromLogs<WETH9TransferEventArgs>(
result.receipt!.logs, result.receipt!.logs,

View File

@ -79,13 +79,15 @@ export class FunctionAssertion<TArgs extends any[], TBefore, ReturnDataType> imp
// Initialize the callResult so that the default success value is true. // Initialize the callResult so that the default success value is true.
const callResult: FunctionResult = { success: true }; const callResult: FunctionResult = { success: true };
// Log function name, arguments, and txData
logger.logFunctionAssertion(this._functionName, args, txData);
// Try to make the call to the function. If it is successful, pass the // Try to make the call to the function. If it is successful, pass the
// result and receipt to the after condition. // result and receipt to the after condition.
try { try {
const functionWithArgs = (this._contractWrapper as any)[this._functionName]( const functionWithArgs = (this._contractWrapper as any)[this._functionName](
...args, ...args,
) as ContractTxFunctionObj<ReturnDataType>; ) as ContractTxFunctionObj<ReturnDataType>;
logger.logFunctionAssertion(this._functionName, args, txData);
callResult.data = await functionWithArgs.callAsync(txData); callResult.data = await functionWithArgs.callAsync(txData);
callResult.receipt = callResult.receipt =
functionWithArgs.awaitTransactionSuccessAsync !== undefined functionWithArgs.awaitTransactionSuccessAsync !== undefined

View File

@ -15,12 +15,13 @@ export function validJoinStakingPoolAssertion(deployment: DeploymentManager): Fu
const { stakingWrapper } = deployment.staking; const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<[string], {}, void>(stakingWrapper, 'joinStakingPoolAsMaker', { return new FunctionAssertion<[string], {}, void>(stakingWrapper, 'joinStakingPoolAsMaker', {
after: async (_beforeInfo, _result: FunctionResult, args: [string], txData: Partial<TxData>) => { after: async (_beforeInfo, result: FunctionResult, args: [string], txData: Partial<TxData>) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
const [poolId] = args; const [poolId] = args;
expect(_result.success).to.be.true(); const logs = result.receipt!.logs;
const logs = _result.receipt!.logs;
const logArgs = filterLogsToArguments<StakingMakerStakingPoolSetEventArgs>( const logArgs = filterLogsToArguments<StakingMakerStakingPoolSetEventArgs>(
logs, logs,
StakingEvents.MakerStakingPoolSet, StakingEvents.MakerStakingPoolSet,

View File

@ -1,75 +1,94 @@
import { import { OwnerStakeByStatus, StakeInfo, StakeStatus, StoredBalance } from '@0x/contracts-staking';
GlobalStakeByStatus,
OwnerStakeByStatus,
StakeInfo,
StakeStatus,
StakingPoolById,
StoredBalance,
} from '@0x/contracts-staking';
import { constants, expect } from '@0x/contracts-test-utils'; import { constants, expect } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types'; import { TxData } from 'ethereum-types';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { DeploymentManager } from '../deployment_manager'; import { DeploymentManager } from '../deployment_manager';
import { SimulationEnvironment } from '../simulation';
import { FunctionAssertion, FunctionResult } from './function_assertion'; import { FunctionAssertion, FunctionResult } from './function_assertion';
function incrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber): void { function incrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber, currentEpoch: BigNumber): void {
stakeBalance.currentEpochBalance = currentEpoch.isGreaterThan(stakeBalance.currentEpoch)
? stakeBalance.nextEpochBalance
: stakeBalance.currentEpochBalance;
stakeBalance.currentEpoch = currentEpoch;
_.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).plus(amount)); _.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).plus(amount));
} }
function decrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber): void { function decrementNextEpochBalance(stakeBalance: StoredBalance, amount: BigNumber, currentEpoch: BigNumber): void {
stakeBalance.currentEpochBalance = currentEpoch.isGreaterThan(stakeBalance.currentEpoch)
? stakeBalance.nextEpochBalance
: stakeBalance.currentEpochBalance;
stakeBalance.currentEpoch = currentEpoch;
_.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).minus(amount)); _.update(stakeBalance, ['nextEpochBalance'], balance => (balance || constants.ZERO_AMOUNT).minus(amount));
} }
function loadCurrentBalance(balance: StoredBalance, currentEpoch: BigNumber): StoredBalance {
return {
...balance,
currentEpoch: currentEpoch,
currentEpochBalance: currentEpoch.isGreaterThan(balance.currentEpoch)
? balance.nextEpochBalance
: balance.currentEpochBalance,
};
}
function updateNextEpochBalances( function updateNextEpochBalances(
globalStake: GlobalStakeByStatus,
ownerStake: OwnerStakeByStatus, ownerStake: OwnerStakeByStatus,
pools: StakingPoolById,
from: StakeInfo, from: StakeInfo,
to: StakeInfo, to: StakeInfo,
amount: BigNumber, amount: BigNumber,
simulationEnvironment: SimulationEnvironment,
): string[] { ): string[] {
const { globalStake, stakingPools, currentEpoch } = simulationEnvironment;
// The on-chain state of these updated pools will be verified in the `after` of the assertion. // The on-chain state of these updated pools will be verified in the `after` of the assertion.
const updatedPools = []; const updatedPools = [];
// Decrement next epoch balances associated with the `from` stake // Decrement next epoch balances associated with the `from` stake
if (from.status === StakeStatus.Undelegated) { if (from.status === StakeStatus.Undelegated) {
// Decrement owner undelegated stake // Decrement owner undelegated stake
decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount); decrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch);
// Decrement global undelegated stake // Decrement global undelegated stake
decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount); decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch);
} else if (from.status === StakeStatus.Delegated) { } else if (from.status === StakeStatus.Delegated) {
// Decrement owner's delegated stake to this pool // Decrement owner's delegated stake to this pool
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount); decrementNextEpochBalance(ownerStake[StakeStatus.Delegated][from.poolId], amount, currentEpoch);
// Decrement owner's total delegated stake // Decrement owner's total delegated stake
decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount); decrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount, currentEpoch);
// Decrement global delegated stake // Decrement global delegated stake
decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount); decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch);
// Decrement pool's delegated stake // Decrement pool's delegated stake
decrementNextEpochBalance(pools[from.poolId].delegatedStake, amount); decrementNextEpochBalance(stakingPools[from.poolId].delegatedStake, amount, currentEpoch);
updatedPools.push(from.poolId); updatedPools.push(from.poolId);
// TODO: Check that delegator rewards have been withdrawn/synced
} }
// Increment next epoch balances associated with the `to` stake // Increment next epoch balances associated with the `to` stake
if (to.status === StakeStatus.Undelegated) { if (to.status === StakeStatus.Undelegated) {
incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount); // Increment owner undelegated stake
incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount); incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch);
// Increment global undelegated stake
incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch);
} else if (to.status === StakeStatus.Delegated) { } else if (to.status === StakeStatus.Delegated) {
// Initializes the balance for this pool if the user has not previously delegated to it // Initializes the balance for this pool if the user has not previously delegated to it
_.defaults(ownerStake[StakeStatus.Delegated], { _.defaults(ownerStake[StakeStatus.Delegated], {
[to.poolId]: new StoredBalance(), [to.poolId]: new StoredBalance(),
}); });
// Increment owner's delegated stake to this pool // Increment owner's delegated stake to this pool
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount); incrementNextEpochBalance(ownerStake[StakeStatus.Delegated][to.poolId], amount, currentEpoch);
// Increment owner's total delegated stake // Increment owner's total delegated stake
incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount); incrementNextEpochBalance(ownerStake[StakeStatus.Delegated].total, amount, currentEpoch);
// Increment global delegated stake // Increment global delegated stake
incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount); incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch);
// Increment pool's delegated stake // Increment pool's delegated stake
incrementNextEpochBalance(pools[to.poolId].delegatedStake, amount); incrementNextEpochBalance(stakingPools[to.poolId].delegatedStake, amount, currentEpoch);
updatedPools.push(to.poolId); updatedPools.push(to.poolId);
// TODO: Check that delegator rewards have been withdrawn/synced
} }
return updatedPools; return updatedPools;
} }
@ -80,25 +99,28 @@ function updateNextEpochBalances(
/* tslint:disable:no-unnecessary-type-assertion */ /* tslint:disable:no-unnecessary-type-assertion */
export function validMoveStakeAssertion( export function validMoveStakeAssertion(
deployment: DeploymentManager, deployment: DeploymentManager,
globalStake: GlobalStakeByStatus, simulationEnvironment: SimulationEnvironment,
ownerStake: OwnerStakeByStatus, ownerStake: OwnerStakeByStatus,
pools: StakingPoolById,
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void> { ): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void> {
const { stakingWrapper } = deployment.staking; const { stakingWrapper, zrxVault } = deployment.staking;
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>(stakingWrapper, 'moveStake', { return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], {}, void>(stakingWrapper, 'moveStake', {
after: async ( after: async (
_beforeInfo: {}, _beforeInfo: {},
_result: FunctionResult, result: FunctionResult,
args: [StakeInfo, StakeInfo, BigNumber], args: [StakeInfo, StakeInfo, BigNumber],
txData: Partial<TxData>, txData: Partial<TxData>,
) => { ) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
const [from, to, amount] = args; const [from, to, amount] = args;
const { stakingPools, globalStake, currentEpoch } = simulationEnvironment;
const owner = txData.from!; // tslint:disable-line:no-non-null-assertion const owner = txData.from!; // tslint:disable-line:no-non-null-assertion
// Update local balances to match the expected result of this `moveStake` operation // Update local balances to match the expected result of this `moveStake` operation
const updatedPools = updateNextEpochBalances(globalStake, ownerStake, pools, from, to, amount); const updatedPools = updateNextEpochBalances(ownerStake, from, to, amount, simulationEnvironment);
// Fetches on-chain owner stake balances and checks against local balances // Fetches on-chain owner stake balances and checks against local balances
const ownerUndelegatedStake = { const ownerUndelegatedStake = {
@ -109,16 +131,27 @@ export function validMoveStakeAssertion(
...new StoredBalance(), ...new StoredBalance(),
...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()), ...(await stakingWrapper.getOwnerStakeByStatus(owner, StakeStatus.Delegated).callAsync()),
}; };
expect(ownerUndelegatedStake).to.deep.equal(ownerStake[StakeStatus.Undelegated]); expect(ownerUndelegatedStake).to.deep.equal(
expect(ownerDelegatedStake).to.deep.equal(ownerStake[StakeStatus.Delegated].total); loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch),
);
expect(ownerDelegatedStake).to.deep.equal(
loadCurrentBalance(ownerStake[StakeStatus.Delegated].total, currentEpoch),
);
// Fetches on-chain global stake balances and checks against local balances // Fetches on-chain global stake balances and checks against local balances
const globalDelegatedStake = await stakingWrapper.getGlobalStakeByStatus(StakeStatus.Delegated).callAsync();
const globalUndelegatedStake = await stakingWrapper const globalUndelegatedStake = await stakingWrapper
.getGlobalStakeByStatus(StakeStatus.Undelegated) .getGlobalStakeByStatus(StakeStatus.Undelegated)
.callAsync(); .callAsync();
const globalDelegatedStake = await stakingWrapper.getGlobalStakeByStatus(StakeStatus.Delegated).callAsync(); const totalStake = await zrxVault.balanceOfZrxVault().callAsync();
expect(globalUndelegatedStake).to.deep.equal(globalStake[StakeStatus.Undelegated]); expect(globalDelegatedStake).to.deep.equal(
expect(globalDelegatedStake).to.deep.equal(globalStake[StakeStatus.Delegated]); loadCurrentBalance(globalStake[StakeStatus.Delegated], currentEpoch),
);
expect(globalUndelegatedStake).to.deep.equal({
currentEpochBalance: totalStake.minus(globalDelegatedStake.currentEpochBalance),
nextEpochBalance: totalStake.minus(globalDelegatedStake.nextEpochBalance),
currentEpoch,
});
// Fetches on-chain pool stake balances and checks against local balances // Fetches on-chain pool stake balances and checks against local balances
for (const poolId of updatedPools) { for (const poolId of updatedPools) {
@ -126,8 +159,12 @@ export function validMoveStakeAssertion(
.getStakeDelegatedToPoolByOwner(owner, poolId) .getStakeDelegatedToPoolByOwner(owner, poolId)
.callAsync(); .callAsync();
const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync(); const totalStakeDelegated = await stakingWrapper.getTotalStakeDelegatedToPool(poolId).callAsync();
expect(stakeDelegatedByOwner).to.deep.equal(ownerStake[StakeStatus.Delegated][poolId]); expect(stakeDelegatedByOwner).to.deep.equal(
expect(totalStakeDelegated).to.deep.equal(pools[poolId].delegatedStake); loadCurrentBalance(ownerStake[StakeStatus.Delegated][poolId], currentEpoch),
);
expect(totalStakeDelegated).to.deep.equal(
loadCurrentBalance(stakingPools[poolId].delegatedStake, currentEpoch),
);
} }
}, },
}); });

View File

@ -3,19 +3,23 @@ import { expect } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types'; import { TxData } from 'ethereum-types';
import { BlockchainBalanceStore } from '../balances/blockchain_balance_store';
import { LocalBalanceStore } from '../balances/local_balance_store'; import { LocalBalanceStore } from '../balances/local_balance_store';
import { DeploymentManager } from '../deployment_manager'; import { DeploymentManager } from '../deployment_manager';
import { SimulationEnvironment } from '../simulation';
import { FunctionAssertion, FunctionResult } from './function_assertion'; import { FunctionAssertion, FunctionResult } from './function_assertion';
function expectedUndelegatedStake( function expectedUndelegatedStake(
initStake: OwnerStakeByStatus | GlobalStakeByStatus, initStake: OwnerStakeByStatus | GlobalStakeByStatus,
amount: BigNumber, amount: BigNumber,
currentEpoch: BigNumber,
): StoredBalance { ): StoredBalance {
return { return {
currentEpoch: initStake[StakeStatus.Undelegated].currentEpoch, currentEpoch: currentEpoch,
currentEpochBalance: initStake[StakeStatus.Undelegated].currentEpochBalance.plus(amount), currentEpochBalance: (currentEpoch.isGreaterThan(initStake[StakeStatus.Undelegated].currentEpoch)
? initStake[StakeStatus.Undelegated].nextEpochBalance
: initStake[StakeStatus.Undelegated].currentEpochBalance
).plus(amount),
nextEpochBalance: initStake[StakeStatus.Undelegated].nextEpochBalance.plus(amount), nextEpochBalance: initStake[StakeStatus.Undelegated].nextEpochBalance.plus(amount),
}; };
} }
@ -28,8 +32,7 @@ function expectedUndelegatedStake(
/* tslint:disable:no-unnecessary-type-assertion */ /* tslint:disable:no-unnecessary-type-assertion */
export function validStakeAssertion( export function validStakeAssertion(
deployment: DeploymentManager, deployment: DeploymentManager,
balanceStore: BlockchainBalanceStore, simulationEnvironment: SimulationEnvironment,
globalStake: GlobalStakeByStatus,
ownerStake: OwnerStakeByStatus, ownerStake: OwnerStakeByStatus,
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> { ): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
const { stakingWrapper, zrxVault } = deployment.staking; const { stakingWrapper, zrxVault } = deployment.staking;
@ -37,6 +40,7 @@ export function validStakeAssertion(
return new FunctionAssertion(stakingWrapper, 'stake', { return new FunctionAssertion(stakingWrapper, 'stake', {
before: async (args: [BigNumber], txData: Partial<TxData>) => { before: async (args: [BigNumber], txData: Partial<TxData>) => {
const [amount] = args; const [amount] = args;
const { balanceStore } = simulationEnvironment;
// Simulates the transfer of ZRX from staker to vault // Simulates the transfer of ZRX from staker to vault
const expectedBalances = LocalBalanceStore.create(balanceStore); const expectedBalances = LocalBalanceStore.create(balanceStore);
@ -50,11 +54,15 @@ export function validStakeAssertion(
}, },
after: async ( after: async (
expectedBalances: LocalBalanceStore, expectedBalances: LocalBalanceStore,
_result: FunctionResult, result: FunctionResult,
args: [BigNumber], args: [BigNumber],
txData: Partial<TxData>, txData: Partial<TxData>,
) => { ) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
const [amount] = args; const [amount] = args;
const { balanceStore, globalStake, currentEpoch } = simulationEnvironment;
// Checks that the ZRX transfer updated balances as expected. // Checks that the ZRX transfer updated balances as expected.
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
@ -64,7 +72,7 @@ export function validStakeAssertion(
const ownerUndelegatedStake = await stakingWrapper const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated) // tslint:disable-line:no-non-null-assertion .getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated) // tslint:disable-line:no-non-null-assertion
.callAsync(); .callAsync();
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount); const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount, currentEpoch);
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
// Updates local state accordingly // Updates local state accordingly
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake; ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
@ -73,7 +81,7 @@ export function validStakeAssertion(
const globalUndelegatedStake = await stakingWrapper const globalUndelegatedStake = await stakingWrapper
.getGlobalStakeByStatus(StakeStatus.Undelegated) .getGlobalStakeByStatus(StakeStatus.Undelegated)
.callAsync(); .callAsync();
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount); const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount, currentEpoch);
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake); expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
// Updates local state accordingly // Updates local state accordingly
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake; globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;

View File

@ -3,19 +3,23 @@ import { expect } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types'; import { TxData } from 'ethereum-types';
import { BlockchainBalanceStore } from '../balances/blockchain_balance_store';
import { LocalBalanceStore } from '../balances/local_balance_store'; import { LocalBalanceStore } from '../balances/local_balance_store';
import { DeploymentManager } from '../deployment_manager'; import { DeploymentManager } from '../deployment_manager';
import { SimulationEnvironment } from '../simulation';
import { FunctionAssertion, FunctionResult } from './function_assertion'; import { FunctionAssertion, FunctionResult } from './function_assertion';
function expectedUndelegatedStake( function expectedUndelegatedStake(
initStake: OwnerStakeByStatus | GlobalStakeByStatus, initStake: OwnerStakeByStatus | GlobalStakeByStatus,
amount: BigNumber, amount: BigNumber,
currentEpoch: BigNumber,
): StoredBalance { ): StoredBalance {
return { return {
currentEpoch: initStake[StakeStatus.Undelegated].currentEpoch, currentEpoch: currentEpoch,
currentEpochBalance: initStake[StakeStatus.Undelegated].currentEpochBalance.minus(amount), currentEpochBalance: (currentEpoch.isGreaterThan(initStake[StakeStatus.Undelegated].currentEpoch)
? initStake[StakeStatus.Undelegated].nextEpochBalance
: initStake[StakeStatus.Undelegated].currentEpochBalance
).minus(amount),
nextEpochBalance: initStake[StakeStatus.Undelegated].nextEpochBalance.minus(amount), nextEpochBalance: initStake[StakeStatus.Undelegated].nextEpochBalance.minus(amount),
}; };
} }
@ -29,8 +33,7 @@ function expectedUndelegatedStake(
/* tslint:disable:no-non-null-assertion */ /* tslint:disable:no-non-null-assertion */
export function validUnstakeAssertion( export function validUnstakeAssertion(
deployment: DeploymentManager, deployment: DeploymentManager,
balanceStore: BlockchainBalanceStore, simulationEnvironment: SimulationEnvironment,
globalStake: GlobalStakeByStatus,
ownerStake: OwnerStakeByStatus, ownerStake: OwnerStakeByStatus,
): FunctionAssertion<[BigNumber], LocalBalanceStore, void> { ): FunctionAssertion<[BigNumber], LocalBalanceStore, void> {
const { stakingWrapper, zrxVault } = deployment.staking; const { stakingWrapper, zrxVault } = deployment.staking;
@ -38,6 +41,7 @@ export function validUnstakeAssertion(
return new FunctionAssertion(stakingWrapper, 'unstake', { return new FunctionAssertion(stakingWrapper, 'unstake', {
before: async (args: [BigNumber], txData: Partial<TxData>) => { before: async (args: [BigNumber], txData: Partial<TxData>) => {
const [amount] = args; const [amount] = args;
const { balanceStore } = simulationEnvironment;
// Simulates the transfer of ZRX from vault to staker // Simulates the transfer of ZRX from vault to staker
const expectedBalances = LocalBalanceStore.create(balanceStore); const expectedBalances = LocalBalanceStore.create(balanceStore);
@ -51,11 +55,15 @@ export function validUnstakeAssertion(
}, },
after: async ( after: async (
expectedBalances: LocalBalanceStore, expectedBalances: LocalBalanceStore,
_result: FunctionResult, result: FunctionResult,
args: [BigNumber], args: [BigNumber],
txData: Partial<TxData>, txData: Partial<TxData>,
) => { ) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
const [amount] = args; const [amount] = args;
const { balanceStore, globalStake, currentEpoch } = simulationEnvironment;
// Checks that the ZRX transfer updated balances as expected. // Checks that the ZRX transfer updated balances as expected.
await balanceStore.updateErc20BalancesAsync(); await balanceStore.updateErc20BalancesAsync();
@ -65,7 +73,7 @@ export function validUnstakeAssertion(
const ownerUndelegatedStake = await stakingWrapper const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated) .getOwnerStakeByStatus(txData.from!, StakeStatus.Undelegated)
.callAsync(); .callAsync();
const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount); const expectedOwnerUndelegatedStake = expectedUndelegatedStake(ownerStake, amount, currentEpoch);
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(expectedOwnerUndelegatedStake);
// Updates local state accordingly // Updates local state accordingly
ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake; ownerStake[StakeStatus.Undelegated] = expectedOwnerUndelegatedStake;
@ -74,7 +82,7 @@ export function validUnstakeAssertion(
const globalUndelegatedStake = await stakingWrapper const globalUndelegatedStake = await stakingWrapper
.getGlobalStakeByStatus(StakeStatus.Undelegated) .getGlobalStakeByStatus(StakeStatus.Undelegated)
.callAsync(); .callAsync();
const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount); const expectedGlobalUndelegatedStake = expectedUndelegatedStake(globalStake, amount, currentEpoch);
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake); expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(expectedGlobalUndelegatedStake);
// Updates local state accordingly // Updates local state accordingly
globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake; globalStake[StakeStatus.Undelegated] = expectedGlobalUndelegatedStake;

View File

@ -1,6 +1,6 @@
import { WETH9TransferEventArgs, WETH9Events } from '@0x/contracts-erc20'; import { WETH9TransferEventArgs, WETH9Events } from '@0x/contracts-erc20';
import { StoredBalance } from '@0x/contracts-staking'; import { StoredBalance } from '@0x/contracts-staking';
import { expect, verifyEventsFromLogs } from '@0x/contracts-test-utils'; import { expect, filterLogsToArguments, verifyEventsFromLogs } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { TxData } from 'ethereum-types'; import { TxData } from 'ethereum-types';
@ -13,7 +13,6 @@ interface WithdrawDelegatorRewardsBeforeInfo {
delegatorStake: StoredBalance; delegatorStake: StoredBalance;
poolRewards: BigNumber; poolRewards: BigNumber;
wethReservedForPoolRewards: BigNumber; wethReservedForPoolRewards: BigNumber;
delegatorReward: BigNumber;
} }
/** /**
@ -27,35 +26,17 @@ export function validWithdrawDelegatorRewardsAssertion(
simulationEnvironment: SimulationEnvironment, simulationEnvironment: SimulationEnvironment,
): FunctionAssertion<[string], WithdrawDelegatorRewardsBeforeInfo, void> { ): FunctionAssertion<[string], WithdrawDelegatorRewardsBeforeInfo, void> {
const { stakingWrapper } = deployment.staking; const { stakingWrapper } = deployment.staking;
const { currentEpoch } = simulationEnvironment;
return new FunctionAssertion(stakingWrapper, 'withdrawDelegatorRewards', { return new FunctionAssertion(stakingWrapper, 'withdrawDelegatorRewards', {
before: async (args: [string], txData: Partial<TxData>) => { before: async (args: [string], txData: Partial<TxData>) => {
const [poolId] = args; const [poolId] = args;
const delegatorStake = await stakingWrapper const delegatorStake = await stakingWrapper
.getStakeDelegatedToPoolByOwner(txData.from!, poolId) .getStakeDelegatedToPoolByOwner(txData.from!, poolId)
.callAsync(); .callAsync();
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync(); const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync(); const wethReservedForPoolRewards = await stakingWrapper.wethReservedForPoolRewards().callAsync();
const delegatorReward = BigNumber.sum( return { delegatorStake, poolRewards, wethReservedForPoolRewards };
await stakingWrapper
.computeMemberRewardOverInterval(
poolId,
delegatorStake.currentEpochBalance,
delegatorStake.currentEpoch,
delegatorStake.currentEpoch.plus(1),
)
.callAsync(),
await stakingWrapper
.computeMemberRewardOverInterval(
poolId,
delegatorStake.nextEpochBalance,
delegatorStake.currentEpoch.plus(1),
currentEpoch,
)
.callAsync(),
); // TODO: Test the reward computation more robustly
return { delegatorStake, poolRewards, wethReservedForPoolRewards, delegatorReward };
}, },
after: async ( after: async (
beforeInfo: WithdrawDelegatorRewardsBeforeInfo, beforeInfo: WithdrawDelegatorRewardsBeforeInfo,
@ -63,7 +44,11 @@ export function validWithdrawDelegatorRewardsAssertion(
args: [string], args: [string],
txData: Partial<TxData>, txData: Partial<TxData>,
) => { ) => {
// Ensure that the tx succeeded.
expect(result.success, `Error: ${result.data}`).to.be.true();
const [poolId] = args; const [poolId] = args;
const { currentEpoch } = simulationEnvironment;
const expectedDelegatorStake = { const expectedDelegatorStake = {
...beforeInfo.delegatorStake, ...beforeInfo.delegatorStake,
@ -77,18 +62,16 @@ export function validWithdrawDelegatorRewardsAssertion(
.callAsync(); .callAsync();
expect(delegatorStake).to.deep.equal(expectedDelegatorStake); expect(delegatorStake).to.deep.equal(expectedDelegatorStake);
const expectedPoolRewards = beforeInfo.poolRewards.minus(beforeInfo.delegatorReward); const transferEvents = filterLogsToArguments<WETH9TransferEventArgs>(
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
expect(poolRewards).to.bignumber.equal(expectedPoolRewards);
const expectedTransferEvents = beforeInfo.delegatorReward.isZero()
? []
: [{ _from: stakingWrapper.address, _to: txData.from!, _value: beforeInfo.delegatorReward }];
verifyEventsFromLogs<WETH9TransferEventArgs>(
result.receipt!.logs, result.receipt!.logs,
expectedTransferEvents,
WETH9Events.Transfer, WETH9Events.Transfer,
); );
const expectedPoolRewards =
transferEvents.length > 0
? beforeInfo.poolRewards.minus(transferEvents[0]._value)
: beforeInfo.poolRewards;
const poolRewards = await stakingWrapper.rewardsByPoolId(poolId).callAsync();
expect(poolRewards).to.bignumber.equal(expectedPoolRewards);
// TODO: Check CR // TODO: Check CR
}, },

View File

@ -393,7 +393,16 @@ export class DeploymentManager {
stakingLogic.address, stakingLogic.address,
); );
const stakingWrapper = new TestStakingContract(stakingProxy.address, environment.provider, txDefaults); const logDecoderDependencies = _.mapValues(
{ ...stakingArtifacts, ...ERC20Artifacts },
v => v.compilerOutput.abi,
);
const stakingWrapper = new TestStakingContract(
stakingProxy.address,
environment.provider,
txDefaults,
logDecoderDependencies,
);
// Add the zrx vault and the weth contract to the staking proxy. // Add the zrx vault and the weth contract to the staking proxy.
await stakingWrapper.setWethContract(tokens.weth.address).awaitTransactionSuccessAsync({ from: owner }); await stakingWrapper.setWethContract(tokens.weth.address).awaitTransactionSuccessAsync({ from: owner });

View File

@ -29,7 +29,7 @@ class Logger {
msg: `Function called: ${functionName}(${functionArgs msg: `Function called: ${functionName}(${functionArgs
.map(arg => JSON.stringify(arg).replace(/"/g, "'")) .map(arg => JSON.stringify(arg).replace(/"/g, "'"))
.join(', ')})`, .join(', ')})`,
step: this._step++, step: ++this._step,
txData, txData,
}), }),
); );

View File

@ -3,7 +3,7 @@ import { BigNumber } from '@0x/utils';
import * as seedrandom from 'seedrandom'; import * as seedrandom from 'seedrandom';
class PRNGWrapper { class PRNGWrapper {
public readonly seed = process.env.UUID || Math.random().toString(); public readonly seed = process.env.SEED || Math.random().toString();
private readonly _rng = seedrandom(this.seed); private readonly _rng = seedrandom(this.seed);
/* /*

View File

@ -20,7 +20,6 @@ import { DeploymentManager } from '../framework/deployment_manager';
import { Simulation, SimulationEnvironment } from '../framework/simulation'; import { Simulation, SimulationEnvironment } from '../framework/simulation';
import { Pseudorandom } from '../framework/utils/pseudorandom'; import { Pseudorandom } from '../framework/utils/pseudorandom';
import { PoolManagementSimulation } from './pool_management_test';
import { PoolMembershipSimulation } from './pool_membership_test'; import { PoolMembershipSimulation } from './pool_membership_test';
import { StakeManagementSimulation } from './stake_management_test'; import { StakeManagementSimulation } from './stake_management_test';
@ -30,7 +29,6 @@ export class StakingRewardsSimulation extends Simulation {
const stakers = filterActorsByRole(actors, Staker); const stakers = filterActorsByRole(actors, Staker);
const keepers = filterActorsByRole(actors, Keeper); const keepers = filterActorsByRole(actors, Keeper);
const poolManagement = new PoolManagementSimulation(this.environment);
const poolMembership = new PoolMembershipSimulation(this.environment); const poolMembership = new PoolMembershipSimulation(this.environment);
const stakeManagement = new StakeManagementSimulation(this.environment); const stakeManagement = new StakeManagementSimulation(this.environment);
@ -38,7 +36,6 @@ export class StakingRewardsSimulation extends Simulation {
...stakers.map(staker => staker.simulationActions.validWithdrawDelegatorRewards), ...stakers.map(staker => staker.simulationActions.validWithdrawDelegatorRewards),
...keepers.map(keeper => keeper.simulationActions.validFinalizePool), ...keepers.map(keeper => keeper.simulationActions.validFinalizePool),
...keepers.map(keeper => keeper.simulationActions.validEndEpoch), ...keepers.map(keeper => keeper.simulationActions.validEndEpoch),
poolManagement.generator,
poolMembership.generator, poolMembership.generator,
stakeManagement.generator, stakeManagement.generator,
]; ];
@ -66,12 +63,22 @@ blockchainTests('Staking rewards fuzz test', env => {
numErc721TokensToDeploy: 0, numErc721TokensToDeploy: 0,
numErc1155TokensToDeploy: 0, numErc1155TokensToDeploy: 0,
}); });
const [ERC20TokenA, ERC20TokenB, ERC20TokenC, ERC20TokenD] = deployment.tokens.erc20;
const balanceStore = new BlockchainBalanceStore( const balanceStore = new BlockchainBalanceStore(
{ {
StakingProxy: deployment.staking.stakingProxy.address, StakingProxy: deployment.staking.stakingProxy.address,
ZRXVault: deployment.staking.zrxVault.address, ZRXVault: deployment.staking.zrxVault.address,
}, },
{ erc20: { ZRX: deployment.tokens.zrx } }, {
erc20: {
ZRX: deployment.tokens.zrx,
WETH: deployment.tokens.weth,
ERC20TokenA,
ERC20TokenB,
ERC20TokenC,
ERC20TokenD,
},
},
); );
const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore); const simulationEnvironment = new SimulationEnvironment(deployment, balanceStore);

View File

@ -67,69 +67,6 @@ contract TestStaking is
cumulativeRewards = _cumulativeRewardsByPool[poolId][lastStoredEpoch]; cumulativeRewards = _cumulativeRewardsByPool[poolId][lastStoredEpoch];
} }
function computeMemberRewardOverInterval(
bytes32 poolId,
uint256 memberStakeOverInterval,
uint256 beginEpoch,
uint256 endEpoch
)
external
view
returns (uint256 reward)
{
// Sanity check if we can skip computation, as it will result in zero.
if (memberStakeOverInterval == 0 || beginEpoch == endEpoch) {
return 0;
}
// Sanity check interval
require(beginEpoch < endEpoch, "CR_INTERVAL_INVALID");
// Sanity check begin reward
IStructs.Fraction memory beginReward = getCumulativeRewardAtEpoch(poolId, beginEpoch);
IStructs.Fraction memory endReward = getCumulativeRewardAtEpoch(poolId, endEpoch);
// Compute reward
reward = LibFractions.scaleDifference(
endReward.numerator,
endReward.denominator,
beginReward.numerator,
beginReward.denominator,
memberStakeOverInterval
);
}
function getCumulativeRewardAtEpoch(bytes32 poolId, uint256 epoch)
public
view
returns (IStructs.Fraction memory cumulativeReward)
{
// Return CR at `epoch`, given it's set.
cumulativeReward = _cumulativeRewardsByPool[poolId][epoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return cumulativeReward;
}
// Return CR at `epoch-1`, given it's set.
uint256 lastEpoch = epoch.safeSub(1);
cumulativeReward = _cumulativeRewardsByPool[poolId][lastEpoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return cumulativeReward;
}
// Return the most recent CR, given it's less than `epoch`.
uint256 mostRecentEpoch = _cumulativeRewardsByPoolLastStored[poolId];
if (mostRecentEpoch < epoch) {
cumulativeReward = _cumulativeRewardsByPool[poolId][mostRecentEpoch];
if (_isCumulativeRewardSet(cumulativeReward)) {
return cumulativeReward;
}
}
// Otherwise return an empty CR.
return IStructs.Fraction(0, 1);
}
/// @dev Overridden to use testWethAddress; /// @dev Overridden to use testWethAddress;
function getWethContract() function getWethContract()
public public