address comments
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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() },
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,10 @@ export {
|
|||||||
StakeStatus,
|
StakeStatus,
|
||||||
StoredBalance,
|
StoredBalance,
|
||||||
loadCurrentBalance,
|
loadCurrentBalance,
|
||||||
incrementNextEpochBalance,
|
increaseNextBalance,
|
||||||
decrementNextEpochBalance,
|
decreaseNextBalance,
|
||||||
|
increaseCurrentAndNextBalance,
|
||||||
|
decreaseCurrentAndNextBalance,
|
||||||
StakingPoolById,
|
StakingPoolById,
|
||||||
OwnerStakeByStatus,
|
OwnerStakeByStatus,
|
||||||
GlobalStakeByStatus,
|
GlobalStakeByStatus,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user