Refactored finalization state.
1. Removes state variables: - totalWeightedStakeThisEpoch - totalFeesCollectedThisEpoch - numActivePoolsThisEpoch 2. No longer indexes by epoch % 2 3. Renamed event StakingPoolActivated → StakingPoolEarnedRewards. 4. Renamed structs: - ActivePool → PoolStats. This holds stats for a pool that earned rewards. - UnfinalizedState → AggregatedStats. This aggregates stats from the former struct.
This commit is contained in:
@@ -238,7 +238,7 @@ export class FinalizerActor extends BaseActor {
|
||||
private async _getRewardByPoolIdAsync(poolIds: string[]): Promise<RewardByPoolId> {
|
||||
const activePools = await Promise.all(
|
||||
poolIds.map(async poolId =>
|
||||
this._stakingApiWrapper.stakingContract.getActiveStakingPoolThisEpoch.callAsync(poolId),
|
||||
this._stakingApiWrapper.stakingContract.getStakingPoolStatsThisEpoch.callAsync(poolId),
|
||||
),
|
||||
);
|
||||
const totalRewards = await this._stakingApiWrapper.utils.getAvailableRewardsBalanceAsync();
|
||||
|
||||
@@ -87,21 +87,14 @@ blockchainTests.resets('Finalizer unit tests', env => {
|
||||
|
||||
interface UnfinalizedState {
|
||||
rewardsAvailable: Numberish;
|
||||
poolsRemaining: number;
|
||||
poolsToFinalize: Numberish;
|
||||
totalFeesCollected: Numberish;
|
||||
totalWeightedStake: Numberish;
|
||||
totalRewardsFinalized: Numberish;
|
||||
}
|
||||
|
||||
async function getUnfinalizedStateAsync(): Promise<UnfinalizedState> {
|
||||
const r = await testContract.unfinalizedState.callAsync();
|
||||
return {
|
||||
rewardsAvailable: r[0],
|
||||
poolsRemaining: r[1].toNumber(),
|
||||
totalFeesCollected: r[2],
|
||||
totalWeightedStake: r[3],
|
||||
totalRewardsFinalized: r[4],
|
||||
};
|
||||
return testContract.getAggregatedStatsForPreviousEpoch.callAsync();
|
||||
}
|
||||
|
||||
async function finalizePoolsAsync(poolIds: string[]): Promise<LogEntry[]> {
|
||||
@@ -142,16 +135,16 @@ blockchainTests.resets('Finalizer unit tests', env => {
|
||||
|
||||
async function assertFinalizationLogsAndBalancesAsync(
|
||||
rewardsAvailable: Numberish,
|
||||
activePools: ActivePoolOpts[],
|
||||
poolsToFinalize: ActivePoolOpts[],
|
||||
finalizationLogs: LogEntry[],
|
||||
): Promise<void> {
|
||||
const currentEpoch = await getCurrentEpochAsync();
|
||||
// Compute the expected rewards for each pool.
|
||||
const poolsWithStake = activePools.filter(p => !new BigNumber(p.weightedStake).isZero());
|
||||
const poolsWithStake = poolsToFinalize.filter(p => !new BigNumber(p.weightedStake).isZero());
|
||||
const poolRewards = await calculatePoolRewardsAsync(rewardsAvailable, poolsWithStake);
|
||||
const totalRewards = BigNumber.sum(...poolRewards);
|
||||
const rewardsRemaining = new BigNumber(rewardsAvailable).minus(totalRewards);
|
||||
const [totalOperatorRewards, totalMembersRewards] = getTotalSplitRewards(activePools, poolRewards);
|
||||
const [totalOperatorRewards, totalMembersRewards] = getTotalSplitRewards(poolsToFinalize, poolRewards);
|
||||
|
||||
// Assert the `RewardsPaid` logs.
|
||||
const rewardsPaidEvents = getRewardsPaidEvents(finalizationLogs);
|
||||
@@ -203,13 +196,13 @@ blockchainTests.resets('Finalizer unit tests', env => {
|
||||
|
||||
async function calculatePoolRewardsAsync(
|
||||
rewardsAvailable: Numberish,
|
||||
activePools: ActivePoolOpts[],
|
||||
poolsToFinalize: ActivePoolOpts[],
|
||||
): Promise<BigNumber[]> {
|
||||
const totalFees = BigNumber.sum(...activePools.map(p => p.feesCollected));
|
||||
const totalStake = BigNumber.sum(...activePools.map(p => p.weightedStake));
|
||||
const poolRewards = _.times(activePools.length, () => constants.ZERO_AMOUNT);
|
||||
for (const i of _.times(activePools.length)) {
|
||||
const pool = activePools[i];
|
||||
const totalFees = BigNumber.sum(...poolsToFinalize.map(p => p.feesCollected));
|
||||
const totalStake = BigNumber.sum(...poolsToFinalize.map(p => p.weightedStake));
|
||||
const poolRewards = _.times(poolsToFinalize.length, () => constants.ZERO_AMOUNT);
|
||||
for (const i of _.times(poolsToFinalize.length)) {
|
||||
const pool = poolsToFinalize[i];
|
||||
const feesCollected = new BigNumber(pool.feesCollected);
|
||||
if (feesCollected.isZero()) {
|
||||
continue;
|
||||
@@ -307,32 +300,48 @@ blockchainTests.resets('Finalizer unit tests', env => {
|
||||
expect(events).to.deep.eq([]);
|
||||
});
|
||||
|
||||
it("clears the next epoch's finalization state", async () => {
|
||||
// Add a pool so there is state to clear.
|
||||
await addActivePoolAsync();
|
||||
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
const epoch = await testContract.currentEpoch.callAsync();
|
||||
expect(epoch).to.bignumber.eq(stakingConstants.INITIAL_EPOCH.plus(1));
|
||||
const numActivePools = await testContract.numActivePoolsThisEpoch.callAsync();
|
||||
const totalFees = await testContract.totalFeesCollectedThisEpoch.callAsync();
|
||||
const totalStake = await testContract.totalWeightedStakeThisEpoch.callAsync();
|
||||
expect(numActivePools).to.bignumber.eq(0);
|
||||
expect(totalFees).to.bignumber.eq(0);
|
||||
expect(totalStake).to.bignumber.eq(0);
|
||||
});
|
||||
|
||||
it('prepares unfinalized state', async () => {
|
||||
// Add a pool so there is state to clear.
|
||||
const pool = await addActivePoolAsync();
|
||||
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
return assertUnfinalizedStateAsync({
|
||||
poolsRemaining: 1,
|
||||
poolsToFinalize: 1,
|
||||
rewardsAvailable: INITIAL_BALANCE,
|
||||
totalFeesCollected: pool.feesCollected,
|
||||
totalWeightedStake: pool.weightedStake,
|
||||
});
|
||||
});
|
||||
|
||||
it("correctly stores the epoch's aggregated stats after ending the epoch", async () => {
|
||||
const pool = await addActivePoolAsync();
|
||||
const epoch = await testContract.currentEpoch.callAsync();
|
||||
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
const aggregatedStats = await testContract.aggregatedStatsByEpoch.callAsync(epoch);
|
||||
expect(aggregatedStats).to.be.deep.equal([
|
||||
INITIAL_BALANCE,
|
||||
new BigNumber(1), // pools to finalize
|
||||
pool.feesCollected,
|
||||
pool.weightedStake,
|
||||
new BigNumber(0), // rewards finalized
|
||||
]);
|
||||
});
|
||||
|
||||
it("correctly clear an epoch's aggregated stats after it is finalized", async () => {
|
||||
const pool = await addActivePoolAsync();
|
||||
const epoch = await testContract.currentEpoch.callAsync();
|
||||
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
await testContract.finalizePool.awaitTransactionSuccessAsync(pool.poolId);
|
||||
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
const aggregatedStats = await testContract.aggregatedStatsByEpoch.callAsync(epoch);
|
||||
expect(aggregatedStats).to.be.deep.equal([
|
||||
new BigNumber(0),
|
||||
new BigNumber(0),
|
||||
new BigNumber(0),
|
||||
new BigNumber(0),
|
||||
new BigNumber(0),
|
||||
]);
|
||||
});
|
||||
|
||||
it('reverts if the prior epoch is unfinalized', async () => {
|
||||
await addActivePoolAsync();
|
||||
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
@@ -382,7 +391,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
|
||||
const pool = _.sample(pools) as ActivePoolOpts;
|
||||
await testContract.endEpoch.awaitTransactionSuccessAsync();
|
||||
await finalizePoolsAsync([pool.poolId]);
|
||||
const poolState = await testContract.getActivePoolFromEpoch.callAsync(
|
||||
const poolState = await testContract.getPoolStatsFromEpoch.callAsync(
|
||||
stakingConstants.INITIAL_EPOCH,
|
||||
pool.poolId,
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ import * as _ from 'lodash';
|
||||
import {
|
||||
artifacts,
|
||||
IStakingEventsEvents,
|
||||
IStakingEventsStakingPoolActivatedEventArgs,
|
||||
IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs,
|
||||
TestProtocolFeesContract,
|
||||
TestProtocolFeesERC20ProxyTransferFromEventArgs,
|
||||
TestProtocolFeesEvents,
|
||||
@@ -152,7 +152,7 @@ blockchainTests('Protocol Fees unit tests', env => {
|
||||
});
|
||||
|
||||
async function getProtocolFeesAsync(poolId: string): Promise<BigNumber> {
|
||||
return (await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId)).feesCollected;
|
||||
return (await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId)).feesCollected;
|
||||
}
|
||||
|
||||
describe('ETH fees', () => {
|
||||
@@ -369,21 +369,22 @@ blockchainTests('Protocol Fees unit tests', env => {
|
||||
});
|
||||
|
||||
interface FinalizationState {
|
||||
numActivePools: BigNumber;
|
||||
poolsToFinalize: BigNumber;
|
||||
totalFeesCollected: BigNumber;
|
||||
totalWeightedStake: BigNumber;
|
||||
}
|
||||
|
||||
async function getFinalizationStateAsync(): Promise<FinalizationState> {
|
||||
const aggregatedStats = await testContract.getAggregatedStatsForCurrentEpoch.callAsync();
|
||||
return {
|
||||
numActivePools: await testContract.numActivePoolsThisEpoch.callAsync(),
|
||||
totalFeesCollected: await testContract.totalFeesCollectedThisEpoch.callAsync(),
|
||||
totalWeightedStake: await testContract.totalWeightedStakeThisEpoch.callAsync(),
|
||||
poolsToFinalize: aggregatedStats.poolsToFinalize,
|
||||
totalFeesCollected: aggregatedStats.totalFeesCollected,
|
||||
totalWeightedStake: aggregatedStats.totalWeightedStake,
|
||||
};
|
||||
}
|
||||
|
||||
interface PayToMakerResult {
|
||||
poolActivatedEvents: IStakingEventsStakingPoolActivatedEventArgs[];
|
||||
poolEarnedRewardsEvents: IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs[];
|
||||
fee: BigNumber;
|
||||
}
|
||||
|
||||
@@ -395,13 +396,13 @@ blockchainTests('Protocol Fees unit tests', env => {
|
||||
new BigNumber(_fee),
|
||||
{ from: exchangeAddress, value: _fee },
|
||||
);
|
||||
const events = filterLogsToArguments<IStakingEventsStakingPoolActivatedEventArgs>(
|
||||
const events = filterLogsToArguments<IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs>(
|
||||
receipt.logs,
|
||||
IStakingEventsEvents.StakingPoolActivated,
|
||||
IStakingEventsEvents.StakingPoolEarnedRewardsInEpoch,
|
||||
);
|
||||
return {
|
||||
fee: new BigNumber(_fee),
|
||||
poolActivatedEvents: events,
|
||||
poolEarnedRewardsEvents: events,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -414,14 +415,14 @@ blockchainTests('Protocol Fees unit tests', env => {
|
||||
|
||||
it('no active pools to start', async () => {
|
||||
const state = await getFinalizationStateAsync();
|
||||
expect(state.numActivePools).to.bignumber.eq(0);
|
||||
expect(state.poolsToFinalize).to.bignumber.eq(0);
|
||||
expect(state.totalFeesCollected).to.bignumber.eq(0);
|
||||
expect(state.totalWeightedStake).to.bignumber.eq(0);
|
||||
});
|
||||
|
||||
it('pool is not registered to start', async () => {
|
||||
const { poolId } = await createTestPoolAsync();
|
||||
const pool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||
const pool = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
|
||||
expect(pool.feesCollected).to.bignumber.eq(0);
|
||||
expect(pool.membersStake).to.bignumber.eq(0);
|
||||
expect(pool.weightedStake).to.bignumber.eq(0);
|
||||
@@ -433,16 +434,16 @@ blockchainTests('Protocol Fees unit tests', env => {
|
||||
poolId,
|
||||
makers: [poolMaker],
|
||||
} = pool;
|
||||
const { fee, poolActivatedEvents } = await payToMakerAsync(poolMaker);
|
||||
expect(poolActivatedEvents.length).to.eq(1);
|
||||
expect(poolActivatedEvents[0].poolId).to.eq(poolId);
|
||||
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||
const { fee, poolEarnedRewardsEvents } = await payToMakerAsync(poolMaker);
|
||||
expect(poolEarnedRewardsEvents.length).to.eq(1);
|
||||
expect(poolEarnedRewardsEvents[0].poolId).to.eq(poolId);
|
||||
const actualPoolStats = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
|
||||
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
|
||||
expect(actualPool.feesCollected).to.bignumber.eq(fee);
|
||||
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake);
|
||||
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||
expect(actualPoolStats.feesCollected).to.bignumber.eq(fee);
|
||||
expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake);
|
||||
expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||
const state = await getFinalizationStateAsync();
|
||||
expect(state.numActivePools).to.bignumber.eq(1);
|
||||
expect(state.poolsToFinalize).to.bignumber.eq(1);
|
||||
expect(state.totalFeesCollected).to.bignumber.eq(fee);
|
||||
expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||
});
|
||||
@@ -454,16 +455,16 @@ blockchainTests('Protocol Fees unit tests', env => {
|
||||
makers: [poolMaker],
|
||||
} = pool;
|
||||
const { fee: fee1 } = await payToMakerAsync(poolMaker);
|
||||
const { fee: fee2, poolActivatedEvents } = await payToMakerAsync(poolMaker);
|
||||
expect(poolActivatedEvents).to.deep.eq([]);
|
||||
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||
const { fee: fee2, poolEarnedRewardsEvents } = await payToMakerAsync(poolMaker);
|
||||
expect(poolEarnedRewardsEvents).to.deep.eq([]);
|
||||
const actualPoolStats = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
|
||||
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
|
||||
const fees = BigNumber.sum(fee1, fee2);
|
||||
expect(actualPool.feesCollected).to.bignumber.eq(fees);
|
||||
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake);
|
||||
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||
expect(actualPoolStats.feesCollected).to.bignumber.eq(fees);
|
||||
expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake);
|
||||
expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||
const state = await getFinalizationStateAsync();
|
||||
expect(state.numActivePools).to.bignumber.eq(1);
|
||||
expect(state.poolsToFinalize).to.bignumber.eq(1);
|
||||
expect(state.totalFeesCollected).to.bignumber.eq(fees);
|
||||
expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||
});
|
||||
@@ -477,19 +478,19 @@ blockchainTests('Protocol Fees unit tests', env => {
|
||||
poolId,
|
||||
makers: [poolMaker],
|
||||
} = pool;
|
||||
const { fee, poolActivatedEvents } = await payToMakerAsync(poolMaker);
|
||||
expect(poolActivatedEvents.length).to.eq(1);
|
||||
expect(poolActivatedEvents[0].poolId).to.eq(poolId);
|
||||
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||
const { fee, poolEarnedRewardsEvents } = await payToMakerAsync(poolMaker);
|
||||
expect(poolEarnedRewardsEvents.length).to.eq(1);
|
||||
expect(poolEarnedRewardsEvents[0].poolId).to.eq(poolId);
|
||||
const actualPoolStats = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
|
||||
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
|
||||
expect(actualPool.feesCollected).to.bignumber.eq(fee);
|
||||
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake);
|
||||
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||
expect(actualPoolStats.feesCollected).to.bignumber.eq(fee);
|
||||
expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake);
|
||||
expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake);
|
||||
totalFees = totalFees.plus(fee);
|
||||
totalWeightedStake = totalWeightedStake.plus(expectedWeightedStake);
|
||||
}
|
||||
const state = await getFinalizationStateAsync();
|
||||
expect(state.numActivePools).to.bignumber.eq(pools.length);
|
||||
expect(state.poolsToFinalize).to.bignumber.eq(pools.length);
|
||||
expect(state.totalFeesCollected).to.bignumber.eq(totalFees);
|
||||
expect(state.totalWeightedStake).to.bignumber.eq(totalWeightedStake);
|
||||
});
|
||||
@@ -502,10 +503,10 @@ blockchainTests('Protocol Fees unit tests', env => {
|
||||
} = pool;
|
||||
await payToMakerAsync(poolMaker);
|
||||
await testContract.advanceEpoch.awaitTransactionSuccessAsync();
|
||||
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId);
|
||||
expect(actualPool.feesCollected).to.bignumber.eq(0);
|
||||
expect(actualPool.membersStake).to.bignumber.eq(0);
|
||||
expect(actualPool.weightedStake).to.bignumber.eq(0);
|
||||
const actualPoolStats = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
|
||||
expect(actualPoolStats.feesCollected).to.bignumber.eq(0);
|
||||
expect(actualPoolStats.membersStake).to.bignumber.eq(0);
|
||||
expect(actualPoolStats.weightedStake).to.bignumber.eq(0);
|
||||
});
|
||||
|
||||
describe('Multiple makers', () => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as _ from 'lodash';
|
||||
import {
|
||||
artifacts,
|
||||
IStakingEventsEpochEndedEventArgs,
|
||||
IStakingEventsStakingPoolActivatedEventArgs,
|
||||
IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs,
|
||||
ReadOnlyProxyContract,
|
||||
StakingProxyContract,
|
||||
TestCobbDouglasContract,
|
||||
@@ -76,13 +76,13 @@ export class StakingApiWrapper {
|
||||
|
||||
findActivePoolIdsAsync: async (epoch?: number): Promise<string[]> => {
|
||||
const _epoch = epoch !== undefined ? epoch : await this.stakingContract.currentEpoch.callAsync();
|
||||
const events = filterLogsToArguments<IStakingEventsStakingPoolActivatedEventArgs>(
|
||||
const events = filterLogsToArguments<IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs>(
|
||||
await this.stakingContract.getLogsAsync(
|
||||
TestStakingEvents.StakingPoolActivated,
|
||||
TestStakingEvents.StakingPoolEarnedRewardsInEpoch,
|
||||
{ fromBlock: BlockParamLiteral.Earliest, toBlock: BlockParamLiteral.Latest },
|
||||
{ epoch: new BigNumber(_epoch) },
|
||||
),
|
||||
TestStakingEvents.StakingPoolActivated,
|
||||
TestStakingEvents.StakingPoolEarnedRewardsInEpoch,
|
||||
);
|
||||
return events.map(e => e.poolId);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user