address comments

This commit is contained in:
Michael Zhu
2019-12-11 16:52:48 -08:00
parent 2f9891f0aa
commit 49538f272e
14 changed files with 151 additions and 159 deletions

View File

@@ -29,6 +29,7 @@ export class Actor {
public simulationActions: { public simulationActions: {
[action: string]: AsyncIterableIterator<AssertionResult | void>; [action: string]: AsyncIterableIterator<AssertionResult | void>;
} = {}; } = {};
public mixins: string[] = [];
protected readonly _transactionFactory: TransactionFactory; protected readonly _transactionFactory: TransactionFactory;
public static reset(): void { public static reset(): void {

View File

@@ -35,6 +35,7 @@ export function FeeRecipientMixin<TBase extends Constructor>(Base: TBase): TBase
// tslint:disable-next-line:no-inferred-empty-object-type // tslint:disable-next-line:no-inferred-empty-object-type
super(...args); super(...args);
this.actor = (this as any) as Actor; this.actor = (this as any) as Actor;
this.actor.mixins.push('FeeRecipient');
const { verifyingContract } = args[0] as FeeRecipientConfig; const { verifyingContract } = args[0] as FeeRecipientConfig;
if (verifyingContract !== undefined) { if (verifyingContract !== undefined) {

View File

@@ -36,6 +36,7 @@ export function KeeperMixin<TBase extends Constructor>(Base: TBase): TBase & Con
// tslint:disable-next-line:no-inferred-empty-object-type // tslint:disable-next-line:no-inferred-empty-object-type
super(...args); super(...args);
this.actor = (this as any) as Actor; this.actor = (this as any) as Actor;
this.actor.mixins.push('Keeper');
// Register this mixin's assertion generators // Register this mixin's assertion generators
this.actor.simulationActions = { this.actor.simulationActions = {

View File

@@ -1,3 +1,4 @@
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { constants, OrderFactory } from '@0x/contracts-test-utils'; import { constants, OrderFactory } from '@0x/contracts-test-utils';
import { Order, SignedOrder } from '@0x/types'; import { Order, SignedOrder } from '@0x/types';
import { TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { TransactionReceiptWithDecodedLogs } from 'ethereum-types';
@@ -18,6 +19,7 @@ export interface MakerInterface {
signOrderAsync: (customOrderParams?: Partial<Order>) => Promise<SignedOrder>; signOrderAsync: (customOrderParams?: Partial<Order>) => Promise<SignedOrder>;
cancelOrderAsync: (order: SignedOrder) => Promise<TransactionReceiptWithDecodedLogs>; cancelOrderAsync: (order: SignedOrder) => Promise<TransactionReceiptWithDecodedLogs>;
joinStakingPoolAsync: (poolId: string) => Promise<TransactionReceiptWithDecodedLogs>; joinStakingPoolAsync: (poolId: string) => Promise<TransactionReceiptWithDecodedLogs>;
createFillableOrderAsync: (taker: Actor) => Promise<SignedOrder>;
} }
/** /**
@@ -39,6 +41,7 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
// tslint:disable-next-line:no-inferred-empty-object-type // tslint:disable-next-line:no-inferred-empty-object-type
super(...args); super(...args);
this.actor = (this as any) as Actor; this.actor = (this as any) as Actor;
this.actor.mixins.push('Maker');
const { orderConfig } = args[0] as MakerConfig; const { orderConfig } = args[0] as MakerConfig;
const defaultOrderParams = { const defaultOrderParams = {
@@ -84,6 +87,57 @@ export function MakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
}); });
} }
public async createFillableOrderAsync(taker: Actor): Promise<SignedOrder> {
const { actors, balanceStore } = this.actor.simulationEnvironment!;
await balanceStore.updateErc20BalancesAsync();
// Choose the assets for the order
const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize(
this.actor.deployment.tokens.erc20,
4, // tslint:disable-line:custom-no-magic-numbers
);
// Maker and taker set balances/allowances to guarantee that the fill succeeds.
// Amounts are chosen to be within each actor's balance (divided by 2, in case
// e.g. makerAsset = makerFeeAsset)
const [makerAssetAmount, makerFee, takerAssetAmount, takerFee] = await Promise.all(
[
[this.actor, makerToken],
[this.actor, makerFeeToken],
[taker, takerToken],
[taker, takerFeeToken],
].map(async ([owner, token]) => {
let balance = balanceStore.balances.erc20[owner.address][token.address];
await (owner as Actor).configureERC20TokenAsync(token as DummyERC20TokenContract);
balance = balanceStore.balances.erc20[owner.address][token.address] =
constants.INITIAL_ERC20_BALANCE;
return Pseudorandom.integer(balance.dividedToIntegerBy(2));
}),
);
// Encode asset data
const [makerAssetData, makerFeeAssetData, takerAssetData, takerFeeAssetData] = [
makerToken,
makerFeeToken,
takerToken,
takerFeeToken,
].map(token =>
this.actor.deployment.assetDataEncoder.ERC20Token(token.address).getABIEncodedTransactionData(),
);
// Maker signs the order
return this.signOrderAsync({
makerAssetData,
takerAssetData,
makerFeeAssetData,
takerFeeAssetData,
makerAssetAmount,
takerAssetAmount,
makerFee,
takerFee,
feeRecipientAddress: Pseudorandom.sample(actors)!.address,
});
}
private async *_validJoinStakingPool(): AsyncIterableIterator<AssertionResult | void> { private async *_validJoinStakingPool(): AsyncIterableIterator<AssertionResult | void> {
const { stakingPools } = this.actor.simulationEnvironment!; const { stakingPools } = this.actor.simulationEnvironment!;
const assertion = validJoinStakingPoolAssertion(this.actor.deployment); const assertion = validJoinStakingPoolAssertion(this.actor.deployment);

View File

@@ -35,6 +35,7 @@ export function PoolOperatorMixin<TBase extends Constructor>(Base: TBase): TBase
// tslint:disable-next-line:no-inferred-empty-object-type // tslint:disable-next-line:no-inferred-empty-object-type
super(...args); super(...args);
this.actor = (this as any) as Actor; this.actor = (this as any) as Actor;
this.actor.mixins.push('PoolOperator');
// Register this mixin's assertion generators // Register this mixin's assertion generators
this.actor.simulationActions = { this.actor.simulationActions = {

View File

@@ -34,6 +34,8 @@ export function StakerMixin<TBase extends Constructor>(Base: TBase): TBase & Con
// tslint:disable-next-line:no-inferred-empty-object-type // tslint:disable-next-line:no-inferred-empty-object-type
super(...args); super(...args);
this.actor = (this as any) as Actor; this.actor = (this as any) as Actor;
this.actor.mixins.push('Staker');
this.stake = { this.stake = {
[StakeStatus.Undelegated]: new StoredBalance(), [StakeStatus.Undelegated]: new StoredBalance(),
[StakeStatus.Delegated]: { total: new StoredBalance() }, [StakeStatus.Delegated]: { total: new StoredBalance() },

View File

@@ -1,5 +1,3 @@
import { DummyERC20TokenContract } from '@0x/contracts-erc20';
import { constants } from '@0x/contracts-test-utils';
import { SignedOrder } from '@0x/types'; import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils'; import { BigNumber } from '@0x/utils';
import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types'; import { TransactionReceiptWithDecodedLogs, TxData } from 'ethereum-types';
@@ -38,6 +36,7 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
// tslint:disable-next-line:no-inferred-empty-object-type // tslint:disable-next-line:no-inferred-empty-object-type
super(...args); super(...args);
this.actor = (this as any) as Actor; this.actor = (this as any) as Actor;
this.actor.mixins.push('Taker');
// Register this mixin's assertion generators // Register this mixin's assertion generators
this.actor.simulationActions = { this.actor.simulationActions = {
@@ -65,7 +64,7 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
} }
private async *_validFillOrder(): AsyncIterableIterator<AssertionResult | void> { private async *_validFillOrder(): AsyncIterableIterator<AssertionResult | void> {
const { actors, balanceStore } = this.actor.simulationEnvironment!; const { actors } = this.actor.simulationEnvironment!;
const assertion = validFillOrderAssertion(this.actor.deployment, this.actor.simulationEnvironment!); const assertion = validFillOrderAssertion(this.actor.deployment, this.actor.simulationEnvironment!);
while (true) { while (true) {
// Choose a maker to be the other side of the order // Choose a maker to be the other side of the order
@@ -73,53 +72,8 @@ export function TakerMixin<TBase extends Constructor>(Base: TBase): TBase & Cons
if (maker === undefined) { if (maker === undefined) {
yield; yield;
} else { } else {
await balanceStore.updateErc20BalancesAsync(); // Maker creates and signs a fillable order
// Choose the assets for the order const order = await maker.createFillableOrderAsync(this.actor);
const [makerToken, makerFeeToken, takerToken, takerFeeToken] = Pseudorandom.sampleSize(
this.actor.deployment.tokens.erc20,
4, // tslint:disable-line:custom-no-magic-numbers
);
// Maker and taker set balances/allowances to guarantee that the fill succeeds.
// Amounts are chosen to be within each actor's balance (divided by 2, in case
// e.g. makerAsset = makerFeeAsset)
const [makerAssetAmount, makerFee, takerAssetAmount, takerFee] = await Promise.all(
[
[maker, makerToken],
[maker, makerFeeToken],
[this.actor, takerToken],
[this.actor, takerFeeToken],
].map(async ([owner, token]) => {
let balance = balanceStore.balances.erc20[owner.address][token.address];
await (owner as Actor).configureERC20TokenAsync(token as DummyERC20TokenContract);
balance = balanceStore.balances.erc20[owner.address][token.address] =
constants.INITIAL_ERC20_BALANCE;
return Pseudorandom.integer(balance.dividedToIntegerBy(2));
}),
);
// Encode asset data
const [makerAssetData, makerFeeAssetData, takerAssetData, takerFeeAssetData] = [
makerToken,
makerFeeToken,
takerToken,
takerFeeToken,
].map(token =>
this.actor.deployment.assetDataEncoder.ERC20Token(token.address).getABIEncodedTransactionData(),
);
// Maker signs the order
const order = await maker.signOrderAsync({
makerAssetData,
takerAssetData,
makerFeeAssetData,
takerFeeAssetData,
makerAssetAmount,
takerAssetAmount,
makerFee,
takerFee,
feeRecipientAddress: Pseudorandom.sample(actors)!.address,
});
// Taker fills the order by a random amount (up to the order's takerAssetAmount) // Taker fills the order by a random amount (up to the order's takerAssetAmount)
const fillAmount = Pseudorandom.integer(order.takerAssetAmount); const fillAmount = Pseudorandom.integer(order.takerAssetAmount);
// Taker executes the fill with a random msg.value, so that sometimes the // Taker executes the fill with a random msg.value, so that sometimes the

View File

@@ -12,11 +12,12 @@ export function actorAddressesByName(actors: Actor[]): ObjectMap<string> {
} }
/** /**
* Filters the given actors by class. * Filters the given actors by role, specified by the class exported by an actor mixin file,
* e.g, 'Maker', 'Taker', etc.
*/ */
export function filterActorsByRole<TClass extends Constructor>( export function filterActorsByRole<TClass extends Constructor>(
actors: Actor[], actors: Actor[],
role: TClass, role: TClass,
): Array<InstanceType<typeof role>> { ): Array<InstanceType<typeof role>> {
return actors.filter(actor => actor instanceof role) as InstanceType<typeof role>; return actors.filter(actor => actor.mixins.includes(role.name)) as InstanceType<typeof role>;
} }

View File

@@ -24,9 +24,9 @@ import { SimulationEnvironment } from '../simulation';
import { FunctionAssertion, FunctionResult } from './function_assertion'; import { FunctionAssertion, FunctionResult } from './function_assertion';
const PRECISION = 15; const PRECISION = 15;
const ALPHA_NUMERATOR = 1; const COBB_DOUGLAS_ALPHA = toDecimal(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaNumerator).dividedBy(
const ALPHA_DENOMINATOR = 3; toDecimal(stakingConstants.DEFAULT_PARAMS.cobbDouglasAlphaDenominator),
const COBB_DOUGLAS_ALPHA = toDecimal(ALPHA_NUMERATOR).dividedBy(toDecimal(ALPHA_DENOMINATOR)); );
// Reference function for Cobb-Douglas // Reference function for Cobb-Douglas
function cobbDouglas(poolStats: PoolStats, aggregatedStats: AggregatedStats): BigNumber { function cobbDouglas(poolStats: PoolStats, aggregatedStats: AggregatedStats): BigNumber {
@@ -95,34 +95,19 @@ export function validFinalizePoolAssertion(
expect(result.success, `Error: ${result.data}`).to.be.true(); expect(result.success, `Error: ${result.data}`).to.be.true();
const logs = result.receipt!.logs; // tslint:disable-line:no-non-null-assertion const logs = result.receipt!.logs; // tslint:disable-line:no-non-null-assertion
// // Compute relevant epochs
// uint256 currentEpoch_ = currentEpoch;
// uint256 prevEpoch = currentEpoch_.safeSub(1);
const { stakingPools, currentEpoch } = simulationEnvironment; const { stakingPools, currentEpoch } = simulationEnvironment;
const prevEpoch = currentEpoch.minus(1); const prevEpoch = currentEpoch.minus(1);
const [poolId] = args; const [poolId] = args;
const pool = stakingPools[poolId]; const pool = stakingPools[poolId];
// // Load the aggregated stats into memory; noop if no pools to finalize. // finalizePool noops if there are no pools to finalize or
// IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[prevEpoch]; // the pool did not earn rewards or already finalized (has no fees).
// if (aggregatedStats.numPoolsToFinalize == 0) {
// return;
// }
//
// // Noop if the pool did not earn rewards or already finalized (has no fees).
// IStructs.PoolStats memory poolStats = poolStatsByEpoch[poolId][prevEpoch];
// if (poolStats.feesCollected == 0) {
// return;
// }
if (beforeInfo.aggregatedStats.numPoolsToFinalize.isZero() || beforeInfo.poolStats.feesCollected.isZero()) { if (beforeInfo.aggregatedStats.numPoolsToFinalize.isZero() || beforeInfo.poolStats.feesCollected.isZero()) {
expect(logs.length, 'Expect no events to be emitted').to.equal(0); expect(logs.length, 'Expect no events to be emitted').to.equal(0);
return; return;
} }
// // Clear the pool stats so we don't finalize it again, and to recoup // It should have cleared the pool stats for prevEpoch
// // some gas.
// delete poolStatsByEpoch[poolId][prevEpoch];
const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync()); const poolStats = PoolStats.fromArray(await stakingWrapper.poolStatsByEpoch(poolId, prevEpoch).callAsync());
expect(poolStats).to.deep.equal({ expect(poolStats).to.deep.equal({
feesCollected: constants.ZERO_AMOUNT, feesCollected: constants.ZERO_AMOUNT,
@@ -130,43 +115,26 @@ export function validFinalizePoolAssertion(
membersStake: constants.ZERO_AMOUNT, membersStake: constants.ZERO_AMOUNT,
}); });
// // Compute the rewards.
// uint256 rewards = _getUnfinalizedPoolRewardsFromPoolStats(poolStats, aggregatedStats); // uint256 rewards = _getUnfinalizedPoolRewardsFromPoolStats(poolStats, aggregatedStats);
const rewards = BigNumber.min( const rewards = BigNumber.min(
cobbDouglas(beforeInfo.poolStats, beforeInfo.aggregatedStats), cobbDouglas(beforeInfo.poolStats, beforeInfo.aggregatedStats),
beforeInfo.aggregatedStats.rewardsAvailable.minus(beforeInfo.aggregatedStats.totalRewardsFinalized), beforeInfo.aggregatedStats.rewardsAvailable.minus(beforeInfo.aggregatedStats.totalRewardsFinalized),
); );
// // Pay the operator and update rewards for the pool. // Check that a RewardsPaid event was emitted
// // Note that we credit at the CURRENT epoch even though these rewards
// // were earned in the previous epoch.
// (uint256 operatorReward, uint256 membersReward) = _syncPoolRewards(
// poolId,
// rewards,
// poolStats.membersStake
// );
//
// // Emit an event.
// emit RewardsPaid(
// currentEpoch_,
// poolId,
// operatorReward,
// membersReward
// );
//
// uint256 totalReward = operatorReward.safeAdd(membersReward);
const events = filterLogsToArguments<StakingRewardsPaidEventArgs>(logs, StakingEvents.RewardsPaid); const events = filterLogsToArguments<StakingRewardsPaidEventArgs>(logs, StakingEvents.RewardsPaid);
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.poolId, 'RewardsPaid event: poolId').to.equal(poolId); expect(rewardsPaidEvent.poolId, 'RewardsPaid event: poolId').to.equal(poolId);
expect(rewardsPaidEvent.epoch, 'RewardsPaid event: currentEpoch_').to.bignumber.equal(currentEpoch); expect(rewardsPaidEvent.epoch, 'RewardsPaid event: currentEpoch_').to.bignumber.equal(currentEpoch);
// Pull the operator and members' reward from the event
const { operatorReward, membersReward } = rewardsPaidEvent; const { operatorReward, membersReward } = rewardsPaidEvent;
const totalReward = operatorReward.plus(membersReward); const totalReward = operatorReward.plus(membersReward);
// Should be approximately equal to the rewards compute using the Cobb-Douglas reference function
assertRoughlyEquals(totalReward, rewards, PRECISION); assertRoughlyEquals(totalReward, rewards, PRECISION);
// See _computePoolRewardsSplit // Operator takes their share of the rewards
if (beforeInfo.poolStats.membersStake.isZero()) { if (beforeInfo.poolStats.membersStake.isZero()) {
expect( expect(
operatorReward, operatorReward,
@@ -182,7 +150,7 @@ export function validFinalizePoolAssertion(
); );
} }
// See _syncPoolRewards // Pays the operator in WETH if the operator's reward is non-zero
const expectedTransferEvents = operatorReward.isGreaterThan(0) const expectedTransferEvents = operatorReward.isGreaterThan(0)
? [ ? [
{ {
@@ -211,22 +179,15 @@ export function validFinalizePoolAssertion(
beforeInfo.poolStats.membersStake, beforeInfo.poolStats.membersStake,
); );
[numerator, denominator] = ReferenceFunctions.LibFractions.normalize(numerator, denominator); [numerator, denominator] = ReferenceFunctions.LibFractions.normalize(numerator, denominator);
// There's a bug in our reference functions, but I can't figure it out :/ // There's a bug in our reference functions, probably due to the fact that safeDiv in
// Solidity truncates in bits, whereas the safeDiv reference function truncates in base 10.
assertRoughlyEquals( assertRoughlyEquals(
mostRecentCumulativeRewards.numerator.dividedBy(mostRecentCumulativeRewards.denominator), mostRecentCumulativeRewards.numerator.dividedBy(mostRecentCumulativeRewards.denominator),
numerator.dividedBy(denominator), numerator.dividedBy(denominator),
PRECISION, PRECISION,
); );
// // Increase `totalRewardsFinalized`. // Check that aggregated stats have been updated
// aggregatedStatsByEpoch[prevEpoch].totalRewardsFinalized =
// aggregatedStats.totalRewardsFinalized =
// aggregatedStats.totalRewardsFinalized.safeAdd(totalReward);
//
// // Decrease the number of unfinalized pools left.
// aggregatedStatsByEpoch[prevEpoch].numPoolsToFinalize =
// aggregatedStats.numPoolsToFinalize =
// aggregatedStats.numPoolsToFinalize.safeSub(1);
const aggregatedStats = AggregatedStats.fromArray( const aggregatedStats = AggregatedStats.fromArray(
await stakingWrapper.aggregatedStatsByEpoch(prevEpoch).callAsync(), await stakingWrapper.aggregatedStatsByEpoch(prevEpoch).callAsync(),
); );
@@ -236,15 +197,7 @@ export function validFinalizePoolAssertion(
numPoolsToFinalize: beforeInfo.aggregatedStats.numPoolsToFinalize.minus(1), numPoolsToFinalize: beforeInfo.aggregatedStats.numPoolsToFinalize.minus(1),
}); });
// // If there are no more unfinalized pools remaining, the epoch is // If there are no more unfinalized pools remaining, the epoch is finalized.
// // finalized.
// if (aggregatedStats.numPoolsToFinalize == 0) {
// emit EpochFinalized(
// prevEpoch,
// aggregatedStats.totalRewardsFinalized,
// aggregatedStats.rewardsAvailable.safeSub(aggregatedStats.totalRewardsFinalized)
// );
// }
const expectedEpochFinalizedEvents = aggregatedStats.numPoolsToFinalize.isZero() const expectedEpochFinalizedEvents = aggregatedStats.numPoolsToFinalize.isZero()
? [ ? [
{ {
@@ -262,6 +215,7 @@ export function validFinalizePoolAssertion(
StakingEvents.EpochFinalized, StakingEvents.EpochFinalized,
); );
// Update local state
pool.lastFinalized = prevEpoch; pool.lastFinalized = prevEpoch;
}, },
}); });

View File

@@ -1,6 +1,6 @@
import { import {
decrementNextEpochBalance, decreaseNextBalance,
incrementNextEpochBalance, increaseNextBalance,
loadCurrentBalance, loadCurrentBalance,
OwnerStakeByStatus, OwnerStakeByStatus,
StakeInfo, StakeInfo,
@@ -32,18 +32,18 @@ function updateNextEpochBalances(
// 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, currentEpoch); decreaseNextBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch);
// Decrement global undelegated stake // Decrement global undelegated stake
decrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch); decreaseNextBalance(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, currentEpoch); decreaseNextBalance(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, currentEpoch); decreaseNextBalance(ownerStake[StakeStatus.Delegated].total, amount, currentEpoch);
// Decrement global delegated stake // Decrement global delegated stake
decrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch); decreaseNextBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch);
// Decrement pool's delegated stake // Decrement pool's delegated stake
decrementNextEpochBalance(stakingPools[from.poolId].delegatedStake, amount, currentEpoch); decreaseNextBalance(stakingPools[from.poolId].delegatedStake, amount, currentEpoch);
updatedPools.push(from.poolId); updatedPools.push(from.poolId);
// TODO: Check that delegator rewards have been withdrawn/synced // TODO: Check that delegator rewards have been withdrawn/synced
@@ -52,22 +52,22 @@ function updateNextEpochBalances(
// 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) {
// Increment owner undelegated stake // Increment owner undelegated stake
incrementNextEpochBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch); increaseNextBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch);
// Increment global undelegated stake // Increment global undelegated stake
incrementNextEpochBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch); increaseNextBalance(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, currentEpoch); increaseNextBalance(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, currentEpoch); increaseNextBalance(ownerStake[StakeStatus.Delegated].total, amount, currentEpoch);
// Increment global delegated stake // Increment global delegated stake
incrementNextEpochBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch); increaseNextBalance(globalStake[StakeStatus.Delegated], amount, currentEpoch);
// Increment pool's delegated stake // Increment pool's delegated stake
incrementNextEpochBalance(stakingPools[to.poolId].delegatedStake, amount, currentEpoch); increaseNextBalance(stakingPools[to.poolId].delegatedStake, amount, currentEpoch);
updatedPools.push(to.poolId); updatedPools.push(to.poolId);
// TODO: Check that delegator rewards have been withdrawn/synced // TODO: Check that delegator rewards have been withdrawn/synced
@@ -84,7 +84,7 @@ export function validMoveStakeAssertion(
simulationEnvironment: SimulationEnvironment, simulationEnvironment: SimulationEnvironment,
ownerStake: OwnerStakeByStatus, ownerStake: OwnerStakeByStatus,
): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void> { ): FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void> {
const { stakingWrapper, zrxVault } = deployment.staking; const { stakingWrapper } = deployment.staking;
return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void>(stakingWrapper, 'moveStake', { return new FunctionAssertion<[StakeInfo, StakeInfo, BigNumber], void, void>(stakingWrapper, 'moveStake', {
after: async ( after: async (
@@ -125,15 +125,12 @@ export function validMoveStakeAssertion(
const globalUndelegatedStake = await stakingWrapper const globalUndelegatedStake = await stakingWrapper
.getGlobalStakeByStatus(StakeStatus.Undelegated) .getGlobalStakeByStatus(StakeStatus.Undelegated)
.callAsync(); .callAsync();
const totalStake = await zrxVault.balanceOfZrxVault().callAsync();
expect(globalDelegatedStake).to.deep.equal( expect(globalDelegatedStake).to.deep.equal(
loadCurrentBalance(globalStake[StakeStatus.Delegated], currentEpoch), loadCurrentBalance(globalStake[StakeStatus.Delegated], currentEpoch),
); );
expect(globalUndelegatedStake).to.deep.equal({ expect(globalUndelegatedStake).to.deep.equal(
currentEpochBalance: totalStake.minus(globalDelegatedStake.currentEpochBalance), loadCurrentBalance(globalStake[StakeStatus.Undelegated], currentEpoch),
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) {

View File

@@ -1,4 +1,4 @@
import { loadCurrentBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking'; import { increaseCurrentAndNextBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking';
import { expect } from '@0x/contracts-test-utils'; 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';
@@ -47,26 +47,29 @@ export function validStakeAssertion(
expect(result.success, `Error: ${result.data}`).to.be.true(); expect(result.success, `Error: ${result.data}`).to.be.true();
const [amount] = args; const [amount] = args;
const { balanceStore, currentEpoch } = simulationEnvironment; const { balanceStore, currentEpoch, globalStake } = 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();
balanceStore.assertEquals(expectedBalances); balanceStore.assertEquals(expectedBalances);
// _increaseCurrentAndNextBalance // _increaseCurrentAndNextBalance
loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch, true); increaseCurrentAndNextBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch);
ownerStake[StakeStatus.Undelegated].currentEpochBalance = ownerStake[ increaseCurrentAndNextBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch);
StakeStatus.Undelegated
].currentEpochBalance.plus(amount);
ownerStake[StakeStatus.Undelegated].nextEpochBalance = ownerStake[
StakeStatus.Undelegated
].nextEpochBalance.plus(amount);
// Checks that the owner's undelegated stake has increased by the stake amount // Checks that the owner's undelegated stake has increased by the stake amount
const ownerUndelegatedStake = await stakingWrapper const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated) .getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
.callAsync(); .callAsync();
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]);
// Checks that the global undelegated stake has also increased by the stake amount
const globalUndelegatedStake = await stakingWrapper
.getGlobalStakeByStatus(StakeStatus.Undelegated)
.callAsync();
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(
globalStake[StakeStatus.Undelegated],
);
}, },
}); });
} }

View File

@@ -1,4 +1,4 @@
import { loadCurrentBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking'; import { decreaseCurrentAndNextBalance, OwnerStakeByStatus, StakeStatus } from '@0x/contracts-staking';
import { expect } from '@0x/contracts-test-utils'; 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';
@@ -47,26 +47,29 @@ export function validUnstakeAssertion(
expect(result.success, `Error: ${result.data}`).to.be.true(); expect(result.success, `Error: ${result.data}`).to.be.true();
const [amount] = args; const [amount] = args;
const { balanceStore, currentEpoch } = simulationEnvironment; const { balanceStore, currentEpoch, globalStake } = 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();
balanceStore.assertEquals(expectedBalances); balanceStore.assertEquals(expectedBalances);
// _decreaseCurrentAndNextBalance // _decreaseCurrentAndNextBalance
loadCurrentBalance(ownerStake[StakeStatus.Undelegated], currentEpoch, true); decreaseCurrentAndNextBalance(ownerStake[StakeStatus.Undelegated], amount, currentEpoch);
ownerStake[StakeStatus.Undelegated].currentEpochBalance = ownerStake[ decreaseCurrentAndNextBalance(globalStake[StakeStatus.Undelegated], amount, currentEpoch);
StakeStatus.Undelegated
].currentEpochBalance.minus(amount);
ownerStake[StakeStatus.Undelegated].nextEpochBalance = ownerStake[
StakeStatus.Undelegated
].nextEpochBalance.minus(amount);
// Checks that the owner's undelegated stake has decreased by the stake amount // Checks that the owner's undelegated stake has decreased by the stake amount
const ownerUndelegatedStake = await stakingWrapper const ownerUndelegatedStake = await stakingWrapper
.getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated) .getOwnerStakeByStatus(txData.from as string, StakeStatus.Undelegated)
.callAsync(); .callAsync();
expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]); expect(ownerUndelegatedStake, 'Owner undelegated stake').to.deep.equal(ownerStake[StakeStatus.Undelegated]);
// Checks that the global undelegated stake has also increased by the stake amount
const globalUndelegatedStake = await stakingWrapper
.getGlobalStakeByStatus(StakeStatus.Undelegated)
.callAsync();
expect(globalUndelegatedStake, 'Global undelegated stake').to.deep.equal(
globalStake[StakeStatus.Undelegated],
);
}, },
}); });
} }

View File

@@ -49,8 +49,10 @@ export {
StakeStatus, StakeStatus,
StoredBalance, StoredBalance,
loadCurrentBalance, loadCurrentBalance,
incrementNextEpochBalance, increaseNextBalance,
decrementNextEpochBalance, decreaseNextBalance,
increaseCurrentAndNextBalance,
decreaseCurrentAndNextBalance,
StakingPoolById, StakingPoolById,
OwnerStakeByStatus, OwnerStakeByStatus,
GlobalStakeByStatus, GlobalStakeByStatus,

View File

@@ -90,21 +90,39 @@ export function loadCurrentBalance(
} }
/** /**
* Simulates _incrementNextEpochBalance * Simulates _increaseNextBalance
*/ */
export function incrementNextEpochBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { export function increaseNextBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void {
loadCurrentBalance(balance, epoch, true); loadCurrentBalance(balance, epoch, true);
balance.nextEpochBalance = balance.nextEpochBalance.plus(amount); balance.nextEpochBalance = balance.nextEpochBalance.plus(amount);
} }
/** /**
* Simulates _decrementNextEpochBalance * Simulates _decreaseNextBalance
*/ */
export function decrementNextEpochBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void { export function decreaseNextBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void {
loadCurrentBalance(balance, epoch, true); loadCurrentBalance(balance, epoch, true);
balance.nextEpochBalance = balance.nextEpochBalance.minus(amount); balance.nextEpochBalance = balance.nextEpochBalance.minus(amount);
} }
/**
* Simulates _increaseCurrentAndNextBalance
*/
export function increaseCurrentAndNextBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void {
loadCurrentBalance(balance, epoch, true);
balance.currentEpochBalance = balance.currentEpochBalance.plus(amount);
balance.nextEpochBalance = balance.nextEpochBalance.plus(amount);
}
/**
* Simulates _decreaseCurrentAndNextBalance
*/
export function decreaseCurrentAndNextBalance(balance: StoredBalance, amount: Numberish, epoch: BigNumber): void {
loadCurrentBalance(balance, epoch, true);
balance.currentEpochBalance = balance.currentEpochBalance.minus(amount);
balance.nextEpochBalance = balance.nextEpochBalance.minus(amount);
}
export interface StakeBalanceByPool { export interface StakeBalanceByPool {
[key: string]: StoredBalance; [key: string]: StoredBalance;
} }