Some readability improvements and minor optimizations to staking finalization.

This commit is contained in:
Greg Hysen
2019-10-22 06:48:23 -07:00
parent ce8fd44234
commit d91a7fc663
8 changed files with 56 additions and 79 deletions

View File

@@ -81,35 +81,35 @@ contract MixinExchangeFees is
return; return;
} }
// Look up the pool stats and aggregated stats for this epoch. // Cache current epoch to reduce sloads.
uint256 currentEpoch_ = currentEpoch; uint256 currentEpoch_ = currentEpoch;
IStructs.PoolStats memory poolStats = poolStatsByEpoch[poolId][currentEpoch_];
IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[currentEpoch_];
// Perform some initialization if this is the first protocol fee collected in this epoch. // Perform some initialization if this is the pool's first protocol fee in this epoch.
if (poolStats.feesCollected == 0) { uint256 feesCollectedByPool = poolStatsByEpoch[poolId][currentEpoch_].feesCollected;
if (feesCollectedByPool == 0) {
// Compute member and total weighted stake. // Compute member and total weighted stake.
(poolStats.membersStake, poolStats.weightedStake) = _computeMembersAndWeightedStake(poolId, poolStake); (uint256 membersStakeInPool, uint256 weightedStakeInPool) = _computeMembersAndWeightedStake(poolId, poolStake);
poolStatsByEpoch[poolId][currentEpoch_].membersStake = membersStakeInPool;
poolStatsByEpoch[poolId][currentEpoch_].weightedStake = weightedStakeInPool;
// Increase the total weighted stake. // Increase the total weighted stake.
aggregatedStats.totalWeightedStake = aggregatedStats.totalWeightedStake.safeAdd(poolStats.weightedStake); aggregatedStatsByEpoch[currentEpoch_].totalWeightedStake =
aggregatedStatsByEpoch[currentEpoch_].totalWeightedStake.safeAdd(weightedStakeInPool);
// Increase the number of pools to finalize. // Increase the number of pools to finalize.
aggregatedStats.poolsToFinalize = aggregatedStats.poolsToFinalize.safeAdd(1); aggregatedStatsByEpoch[currentEpoch_].numPoolsToFinalize =
aggregatedStatsByEpoch[currentEpoch_].numPoolsToFinalize.safeAdd(1);
// Emit an event so keepers know what pools earned rewards this epoch. // Emit an event so keepers know what pools earned rewards this epoch.
emit StakingPoolEarnedRewardsInEpoch(currentEpoch_, poolId); emit StakingPoolEarnedRewardsInEpoch(currentEpoch_, poolId);
} }
// Credit the fees to the pool. // Credit the fees to the pool.
poolStats.feesCollected = poolStats.feesCollected.safeAdd(protocolFeePaid); poolStatsByEpoch[poolId][currentEpoch_].feesCollected = feesCollectedByPool.safeAdd(protocolFeePaid);
// Increase the total fees collected this epoch. // Increase the total fees collected this epoch.
aggregatedStats.totalFeesCollected = aggregatedStats.totalFeesCollected.safeAdd(protocolFeePaid); aggregatedStatsByEpoch[currentEpoch_].totalFeesCollected =
aggregatedStatsByEpoch[currentEpoch_].totalFeesCollected.safeAdd(protocolFeePaid);
// Store the updated stats.
poolStatsByEpoch[poolId][currentEpoch_] = poolStats;
aggregatedStatsByEpoch[currentEpoch_] = aggregatedStats;
} }
/// @dev Get stats on a staking pool in this epoch. /// @dev Get stats on a staking pool in this epoch.

View File

@@ -52,14 +52,14 @@ interface IStakingEvents {
); );
/// @dev Emitted by MixinFinalizer when an epoch has ended. /// @dev Emitted by MixinFinalizer when an epoch has ended.
/// @param epoch The closing epoch. /// @param epoch The epoch that ended.
/// @param poolsToFinalize Number of pools that earned rewards during `epoch` and must be finalized. /// @param numPoolsToFinalize Number of pools that earned rewards during `epoch` and must be finalized.
/// @param rewardsAvailable Rewards available to all pools that earned rewards during `epoch`. /// @param rewardsAvailable Rewards available to all pools that earned rewards during `epoch`.
/// @param totalWeightedStake Total weighted stake across all pools that earned rewards during `epoch`. /// @param totalWeightedStake Total weighted stake across all pools that earned rewards during `epoch`.
/// @param totalFeesCollected Total fees collected across all pools that earned rewards during `epoch`. /// @param totalFeesCollected Total fees collected across all pools that earned rewards during `epoch`.
event EpochEnded( event EpochEnded(
uint256 indexed epoch, uint256 indexed epoch,
uint256 poolsToFinalize, uint256 numPoolsToFinalize,
uint256 rewardsAvailable, uint256 rewardsAvailable,
uint256 totalFeesCollected, uint256 totalFeesCollected,
uint256 totalWeightedStake uint256 totalWeightedStake

View File

@@ -65,7 +65,7 @@ interface IStorage {
view view
returns (uint256); returns (uint256);
function poolsToFinalizeThisEpoch() function numPoolsToFinalizeThisEpoch()
external external
view view
returns (bytes32[] memory); returns (bytes32[] memory);

View File

@@ -43,13 +43,13 @@ interface IStructs {
/// @param rewardsAvailable Rewards (ETH) available to the epoch /// @param rewardsAvailable Rewards (ETH) available to the epoch
/// being finalized (the previous epoch). This is simply the balance /// being finalized (the previous epoch). This is simply the balance
/// of the contract at the end of the epoch. /// of the contract at the end of the epoch.
/// @param poolsToFinalize The number of pools that have yet to be finalized through `finalizePools()`. /// @param numPoolsToFinalize The number of pools that have yet to be finalized through `finalizePools()`.
/// @param totalFeesCollected The total fees collected for the epoch being finalized. /// @param totalFeesCollected The total fees collected for the epoch being finalized.
/// @param totalWeightedStake The total fees collected for the epoch being finalized. /// @param totalWeightedStake The total fees collected for the epoch being finalized.
/// @param totalRewardsFinalized Amount of rewards that have been paid during finalization. /// @param totalRewardsFinalized Amount of rewards that have been paid during finalization.
struct AggregatedStats { struct AggregatedStats {
uint256 rewardsAvailable; uint256 rewardsAvailable;
uint256 poolsToFinalize; uint256 numPoolsToFinalize;
uint256 totalFeesCollected; uint256 totalFeesCollected;
uint256 totalWeightedStake; uint256 totalWeightedStake;
uint256 totalRewardsFinalized; uint256 totalRewardsFinalized;

View File

@@ -35,43 +35,36 @@ contract MixinFinalizer is
/// @dev Begins a new epoch, preparing the prior one for finalization. /// @dev Begins a new epoch, preparing the prior one for finalization.
/// Throws if not enough time has passed between epochs or if the /// Throws if not enough time has passed between epochs or if the
/// previous epoch was not fully finalized. /// previous epoch was not fully finalized.
/// If no pools earned rewards in the closing epoch, the epoch /// @return numPoolsToFinalize The number of unfinalized pools.
/// will be instantly finalized here. Otherwise, `finalizePool()`
/// should be called on these pools afterward calling this function.
/// @return poolsToFinalize The number of unfinalized pools.
function endEpoch() function endEpoch()
external external
returns (uint256) returns (uint256)
{ {
uint256 closingEpoch = currentEpoch; uint256 currentEpoch_ = currentEpoch;
uint256 prevEpoch = closingEpoch.safeSub(1); uint256 prevEpoch = currentEpoch_.safeSub(1);
// Make sure the previous epoch has been fully finalized. // Make sure the previous epoch has been fully finalized.
uint256 poolsToFinalizeFromPrevEpoch = aggregatedStatsByEpoch[prevEpoch].poolsToFinalize; uint256 numPoolsToFinalizeFromPrevEpoch = aggregatedStatsByEpoch[prevEpoch].numPoolsToFinalize;
if (poolsToFinalizeFromPrevEpoch != 0) { if (numPoolsToFinalizeFromPrevEpoch != 0) {
LibRichErrors.rrevert( LibRichErrors.rrevert(
LibStakingRichErrors.PreviousEpochNotFinalizedError( LibStakingRichErrors.PreviousEpochNotFinalizedError(
prevEpoch, prevEpoch,
poolsToFinalizeFromPrevEpoch numPoolsToFinalizeFromPrevEpoch
) )
); );
} }
// Since it is finalized, we no longer need stats for the previous epoch.
delete aggregatedStatsByEpoch[prevEpoch];
// Convert all ETH to WETH; the WETH balance of this contract is the total rewards. // Convert all ETH to WETH; the WETH balance of this contract is the total rewards.
_wrapEth(); _wrapEth();
// Load aggregated stats for the epoch we're ending. // Load aggregated stats for the epoch we're ending.
IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[closingEpoch]; aggregatedStatsByEpoch[currentEpoch_].rewardsAvailable = _getAvailableWethBalance();
aggregatedStatsByEpoch[closingEpoch].rewardsAvailable = IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[currentEpoch_];
aggregatedStats.rewardsAvailable = _getAvailableWethBalance();
// Emit an event. // Emit an event.
emit EpochEnded( emit EpochEnded(
closingEpoch, currentEpoch_,
aggregatedStats.poolsToFinalize, aggregatedStats.numPoolsToFinalize,
aggregatedStats.rewardsAvailable, aggregatedStats.rewardsAvailable,
aggregatedStats.totalFeesCollected, aggregatedStats.totalFeesCollected,
aggregatedStats.totalWeightedStake aggregatedStats.totalWeightedStake
@@ -81,11 +74,11 @@ contract MixinFinalizer is
_goToNextEpoch(); _goToNextEpoch();
// If there are no pools to finalize then the epoch is finalized. // If there are no pools to finalize then the epoch is finalized.
if (aggregatedStats.poolsToFinalize == 0) { if (aggregatedStats.numPoolsToFinalize == 0) {
emit EpochFinalized(closingEpoch, 0, aggregatedStats.rewardsAvailable); emit EpochFinalized(currentEpoch_, 0, aggregatedStats.rewardsAvailable);
} }
return aggregatedStats.poolsToFinalize; return aggregatedStats.numPoolsToFinalize;
} }
/// @dev Instantly finalizes a single pool that earned rewards in the previous /// @dev Instantly finalizes a single pool that earned rewards in the previous
@@ -103,7 +96,7 @@ contract MixinFinalizer is
// Load the aggregated stats into memory; noop if no pools to finalize. // Load the aggregated stats into memory; noop if no pools to finalize.
IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[prevEpoch]; IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[prevEpoch];
if (aggregatedStats.poolsToFinalize == 0) { if (aggregatedStats.numPoolsToFinalize == 0) {
return; return;
} }
@@ -145,13 +138,13 @@ contract MixinFinalizer is
aggregatedStats.totalRewardsFinalized.safeAdd(totalReward); aggregatedStats.totalRewardsFinalized.safeAdd(totalReward);
// Decrease the number of unfinalized pools left. // Decrease the number of unfinalized pools left.
aggregatedStatsByEpoch[prevEpoch].poolsToFinalize = aggregatedStatsByEpoch[prevEpoch].numPoolsToFinalize =
aggregatedStats.poolsToFinalize = aggregatedStats.numPoolsToFinalize =
aggregatedStats.poolsToFinalize.safeSub(1); aggregatedStats.numPoolsToFinalize.safeSub(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.poolsToFinalize == 0) { if (aggregatedStats.numPoolsToFinalize == 0) {
emit EpochFinalized( emit EpochFinalized(
prevEpoch, prevEpoch,
aggregatedStats.totalRewardsFinalized, aggregatedStats.totalRewardsFinalized,

View File

@@ -86,7 +86,7 @@ contract TestFinalizer is
aggregatedStatsByEpoch[currentEpoch_].totalFeesCollected += feesCollected; aggregatedStatsByEpoch[currentEpoch_].totalFeesCollected += feesCollected;
aggregatedStatsByEpoch[currentEpoch_].totalWeightedStake += weightedStake; aggregatedStatsByEpoch[currentEpoch_].totalWeightedStake += weightedStake;
aggregatedStatsByEpoch[currentEpoch_].poolsToFinalize += 1; aggregatedStatsByEpoch[currentEpoch_].numPoolsToFinalize += 1;
} }
/// @dev Drain the balance of this contract. /// @dev Drain the balance of this contract.

View File

@@ -87,7 +87,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
interface UnfinalizedState { interface UnfinalizedState {
rewardsAvailable: Numberish; rewardsAvailable: Numberish;
poolsToFinalize: Numberish; numPoolsToFinalize: Numberish;
totalFeesCollected: Numberish; totalFeesCollected: Numberish;
totalWeightedStake: Numberish; totalWeightedStake: Numberish;
totalRewardsFinalized: Numberish; totalRewardsFinalized: Numberish;
@@ -135,16 +135,16 @@ blockchainTests.resets('Finalizer unit tests', env => {
async function assertFinalizationLogsAndBalancesAsync( async function assertFinalizationLogsAndBalancesAsync(
rewardsAvailable: Numberish, rewardsAvailable: Numberish,
poolsToFinalize: ActivePoolOpts[], numPoolsToFinalize: ActivePoolOpts[],
finalizationLogs: LogEntry[], finalizationLogs: LogEntry[],
): Promise<void> { ): Promise<void> {
const currentEpoch = await getCurrentEpochAsync(); const currentEpoch = await getCurrentEpochAsync();
// Compute the expected rewards for each pool. // Compute the expected rewards for each pool.
const poolsWithStake = poolsToFinalize.filter(p => !new BigNumber(p.weightedStake).isZero()); const poolsWithStake = numPoolsToFinalize.filter(p => !new BigNumber(p.weightedStake).isZero());
const poolRewards = await calculatePoolRewardsAsync(rewardsAvailable, poolsWithStake); const poolRewards = await calculatePoolRewardsAsync(rewardsAvailable, poolsWithStake);
const totalRewards = BigNumber.sum(...poolRewards); const totalRewards = BigNumber.sum(...poolRewards);
const rewardsRemaining = new BigNumber(rewardsAvailable).minus(totalRewards); const rewardsRemaining = new BigNumber(rewardsAvailable).minus(totalRewards);
const [totalOperatorRewards, totalMembersRewards] = getTotalSplitRewards(poolsToFinalize, poolRewards); const [totalOperatorRewards, totalMembersRewards] = getTotalSplitRewards(numPoolsToFinalize, poolRewards);
// Assert the `RewardsPaid` logs. // Assert the `RewardsPaid` logs.
const rewardsPaidEvents = getRewardsPaidEvents(finalizationLogs); const rewardsPaidEvents = getRewardsPaidEvents(finalizationLogs);
@@ -196,13 +196,13 @@ blockchainTests.resets('Finalizer unit tests', env => {
async function calculatePoolRewardsAsync( async function calculatePoolRewardsAsync(
rewardsAvailable: Numberish, rewardsAvailable: Numberish,
poolsToFinalize: ActivePoolOpts[], numPoolsToFinalize: ActivePoolOpts[],
): Promise<BigNumber[]> { ): Promise<BigNumber[]> {
const totalFees = BigNumber.sum(...poolsToFinalize.map(p => p.feesCollected)); const totalFees = BigNumber.sum(...numPoolsToFinalize.map(p => p.feesCollected));
const totalStake = BigNumber.sum(...poolsToFinalize.map(p => p.weightedStake)); const totalStake = BigNumber.sum(...numPoolsToFinalize.map(p => p.weightedStake));
const poolRewards = _.times(poolsToFinalize.length, () => constants.ZERO_AMOUNT); const poolRewards = _.times(numPoolsToFinalize.length, () => constants.ZERO_AMOUNT);
for (const i of _.times(poolsToFinalize.length)) { for (const i of _.times(numPoolsToFinalize.length)) {
const pool = poolsToFinalize[i]; const pool = numPoolsToFinalize[i];
const feesCollected = new BigNumber(pool.feesCollected); const feesCollected = new BigNumber(pool.feesCollected);
if (feesCollected.isZero()) { if (feesCollected.isZero()) {
continue; continue;
@@ -305,7 +305,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
const pool = await addActivePoolAsync(); const pool = await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
return assertUnfinalizedStateAsync({ return assertUnfinalizedStateAsync({
poolsToFinalize: 1, numPoolsToFinalize: 1,
rewardsAvailable: INITIAL_BALANCE, rewardsAvailable: INITIAL_BALANCE,
totalFeesCollected: pool.feesCollected, totalFeesCollected: pool.feesCollected,
totalWeightedStake: pool.weightedStake, totalWeightedStake: pool.weightedStake,
@@ -326,22 +326,6 @@ blockchainTests.resets('Finalizer unit tests', env => {
]); ]);
}); });
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 () => { it('reverts if the prior epoch is unfinalized', async () => {
await addActivePoolAsync(); await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();

View File

@@ -369,7 +369,7 @@ blockchainTests('Protocol Fees unit tests', env => {
}); });
interface FinalizationState { interface FinalizationState {
poolsToFinalize: BigNumber; numPoolsToFinalize: BigNumber;
totalFeesCollected: BigNumber; totalFeesCollected: BigNumber;
totalWeightedStake: BigNumber; totalWeightedStake: BigNumber;
} }
@@ -377,7 +377,7 @@ blockchainTests('Protocol Fees unit tests', env => {
async function getFinalizationStateAsync(): Promise<FinalizationState> { async function getFinalizationStateAsync(): Promise<FinalizationState> {
const aggregatedStats = await testContract.getAggregatedStatsForCurrentEpoch.callAsync(); const aggregatedStats = await testContract.getAggregatedStatsForCurrentEpoch.callAsync();
return { return {
poolsToFinalize: aggregatedStats.poolsToFinalize, numPoolsToFinalize: aggregatedStats.numPoolsToFinalize,
totalFeesCollected: aggregatedStats.totalFeesCollected, totalFeesCollected: aggregatedStats.totalFeesCollected,
totalWeightedStake: aggregatedStats.totalWeightedStake, totalWeightedStake: aggregatedStats.totalWeightedStake,
}; };
@@ -415,7 +415,7 @@ blockchainTests('Protocol Fees unit tests', env => {
it('no pools to finalize to start', async () => { it('no pools to finalize to start', async () => {
const state = await getFinalizationStateAsync(); const state = await getFinalizationStateAsync();
expect(state.poolsToFinalize).to.bignumber.eq(0); expect(state.numPoolsToFinalize).to.bignumber.eq(0);
expect(state.totalFeesCollected).to.bignumber.eq(0); expect(state.totalFeesCollected).to.bignumber.eq(0);
expect(state.totalWeightedStake).to.bignumber.eq(0); expect(state.totalWeightedStake).to.bignumber.eq(0);
}); });
@@ -443,7 +443,7 @@ blockchainTests('Protocol Fees unit tests', env => {
expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake); expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake);
expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake); expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake);
const state = await getFinalizationStateAsync(); const state = await getFinalizationStateAsync();
expect(state.poolsToFinalize).to.bignumber.eq(1); expect(state.numPoolsToFinalize).to.bignumber.eq(1);
expect(state.totalFeesCollected).to.bignumber.eq(fee); expect(state.totalFeesCollected).to.bignumber.eq(fee);
expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake); expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
}); });
@@ -464,7 +464,7 @@ blockchainTests('Protocol Fees unit tests', env => {
expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake); expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake);
expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake); expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake);
const state = await getFinalizationStateAsync(); const state = await getFinalizationStateAsync();
expect(state.poolsToFinalize).to.bignumber.eq(1); expect(state.numPoolsToFinalize).to.bignumber.eq(1);
expect(state.totalFeesCollected).to.bignumber.eq(fees); expect(state.totalFeesCollected).to.bignumber.eq(fees);
expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake); expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
}); });
@@ -490,7 +490,7 @@ blockchainTests('Protocol Fees unit tests', env => {
totalWeightedStake = totalWeightedStake.plus(expectedWeightedStake); totalWeightedStake = totalWeightedStake.plus(expectedWeightedStake);
} }
const state = await getFinalizationStateAsync(); const state = await getFinalizationStateAsync();
expect(state.poolsToFinalize).to.bignumber.eq(pools.length); expect(state.numPoolsToFinalize).to.bignumber.eq(pools.length);
expect(state.totalFeesCollected).to.bignumber.eq(totalFees); expect(state.totalFeesCollected).to.bignumber.eq(totalFees);
expect(state.totalWeightedStake).to.bignumber.eq(totalWeightedStake); expect(state.totalWeightedStake).to.bignumber.eq(totalWeightedStake);
}); });