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;
}
// Look up the pool stats and aggregated stats for this epoch.
// Cache current epoch to reduce sloads.
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.
if (poolStats.feesCollected == 0) {
// Perform some initialization if this is the pool's first protocol fee in this epoch.
uint256 feesCollectedByPool = poolStatsByEpoch[poolId][currentEpoch_].feesCollected;
if (feesCollectedByPool == 0) {
// 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.
aggregatedStats.totalWeightedStake = aggregatedStats.totalWeightedStake.safeAdd(poolStats.weightedStake);
aggregatedStatsByEpoch[currentEpoch_].totalWeightedStake =
aggregatedStatsByEpoch[currentEpoch_].totalWeightedStake.safeAdd(weightedStakeInPool);
// 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 StakingPoolEarnedRewardsInEpoch(currentEpoch_, poolId);
}
// 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.
aggregatedStats.totalFeesCollected = aggregatedStats.totalFeesCollected.safeAdd(protocolFeePaid);
// Store the updated stats.
poolStatsByEpoch[poolId][currentEpoch_] = poolStats;
aggregatedStatsByEpoch[currentEpoch_] = aggregatedStats;
aggregatedStatsByEpoch[currentEpoch_].totalFeesCollected =
aggregatedStatsByEpoch[currentEpoch_].totalFeesCollected.safeAdd(protocolFeePaid);
}
/// @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.
/// @param epoch The closing epoch.
/// @param poolsToFinalize Number of pools that earned rewards during `epoch` and must be finalized.
/// @param epoch The epoch that ended.
/// @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 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`.
event EpochEnded(
uint256 indexed epoch,
uint256 poolsToFinalize,
uint256 numPoolsToFinalize,
uint256 rewardsAvailable,
uint256 totalFeesCollected,
uint256 totalWeightedStake

View File

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

View File

@@ -43,13 +43,13 @@ interface IStructs {
/// @param rewardsAvailable Rewards (ETH) available to the epoch
/// being finalized (the previous epoch). This is simply the balance
/// 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 totalWeightedStake The total fees collected for the epoch being finalized.
/// @param totalRewardsFinalized Amount of rewards that have been paid during finalization.
struct AggregatedStats {
uint256 rewardsAvailable;
uint256 poolsToFinalize;
uint256 numPoolsToFinalize;
uint256 totalFeesCollected;
uint256 totalWeightedStake;
uint256 totalRewardsFinalized;

View File

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

View File

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

View File

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

View File

@@ -369,7 +369,7 @@ blockchainTests('Protocol Fees unit tests', env => {
});
interface FinalizationState {
poolsToFinalize: BigNumber;
numPoolsToFinalize: BigNumber;
totalFeesCollected: BigNumber;
totalWeightedStake: BigNumber;
}
@@ -377,7 +377,7 @@ blockchainTests('Protocol Fees unit tests', env => {
async function getFinalizationStateAsync(): Promise<FinalizationState> {
const aggregatedStats = await testContract.getAggregatedStatsForCurrentEpoch.callAsync();
return {
poolsToFinalize: aggregatedStats.poolsToFinalize,
numPoolsToFinalize: aggregatedStats.numPoolsToFinalize,
totalFeesCollected: aggregatedStats.totalFeesCollected,
totalWeightedStake: aggregatedStats.totalWeightedStake,
};
@@ -415,7 +415,7 @@ blockchainTests('Protocol Fees unit tests', env => {
it('no pools to finalize to start', async () => {
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.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.weightedStake).to.bignumber.eq(expectedWeightedStake);
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.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.weightedStake).to.bignumber.eq(expectedWeightedStake);
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.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
});
@@ -490,7 +490,7 @@ blockchainTests('Protocol Fees unit tests', env => {
totalWeightedStake = totalWeightedStake.plus(expectedWeightedStake);
}
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.totalWeightedStake).to.bignumber.eq(totalWeightedStake);
});