Some readability improvements and minor optimizations to staking finalization.
This commit is contained in:
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -65,7 +65,7 @@ interface IStorage {
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function poolsToFinalizeThisEpoch()
|
||||
function numPoolsToFinalizeThisEpoch()
|
||||
external
|
||||
view
|
||||
returns (bytes32[] memory);
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -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.
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
});
|
||||
|
Reference in New Issue
Block a user