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:
Greg Hysen
2019-10-21 10:07:06 -07:00
parent e7dc7167d0
commit 6617ad9531
13 changed files with 222 additions and 274 deletions

View File

@@ -81,49 +81,46 @@ contract MixinExchangeFees is
return; return;
} }
// Look up the pool for this epoch. // Look up the pool stats and aggregated stats for this epoch.
uint256 currentEpoch_ = currentEpoch; uint256 currentEpoch_ = currentEpoch;
mapping (bytes32 => IStructs.ActivePool) storage activePoolsThisEpoch = IStructs.PoolStats memory poolStats = poolStatsByEpoch[poolId][currentEpoch_];
_getActivePoolsFromEpoch(currentEpoch_); IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[currentEpoch_];
IStructs.ActivePool memory pool = activePoolsThisEpoch[poolId];
// If the pool was previously inactive in this epoch, initialize it. // If the pool was previously inactive in this epoch, initialize it.
if (pool.feesCollected == 0) { if (poolStats.feesCollected == 0) {
// Compute member and total weighted stake. // Compute member and total weighted stake.
(pool.membersStake, pool.weightedStake) = _computeMembersAndWeightedStake(poolId, poolStake); (poolStats.membersStake, poolStats.weightedStake) = _computeMembersAndWeightedStake(poolId, poolStake);
// Increase the total weighted stake. // Increase the total weighted stake.
totalWeightedStakeThisEpoch = totalWeightedStakeThisEpoch.safeAdd(pool.weightedStake); aggregatedStats.totalWeightedStake = aggregatedStats.totalWeightedStake.safeAdd(poolStats.weightedStake);
// Increase the number of active pools. // Increase the number of active pools.
numActivePoolsThisEpoch = numActivePoolsThisEpoch.safeAdd(1); aggregatedStats.poolsToFinalize = aggregatedStats.poolsToFinalize.safeAdd(1);
// Emit an event so keepers know what pools to pass into // Emit an event so keepers know what pools earned rewards this epoch.
// `finalize()`. emit StakingPoolEarnedRewardsInEpoch(currentEpoch_, poolId);
emit StakingPoolActivated(currentEpoch_, poolId);
} }
// Credit the fees to the pool. // Credit the fees to the pool.
pool.feesCollected = pool.feesCollected.safeAdd(protocolFeePaid); poolStats.feesCollected = poolStats.feesCollected.safeAdd(protocolFeePaid);
// Increase the total fees collected this epoch. // Increase the total fees collected this epoch.
totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch.safeAdd(protocolFeePaid); aggregatedStats.totalFeesCollected = aggregatedStats.totalFeesCollected.safeAdd(protocolFeePaid);
// Store the pool. // Store the updated stats.
activePoolsThisEpoch[poolId] = pool; poolStatsByEpoch[poolId][currentEpoch_] = poolStats;
aggregatedStatsByEpoch[currentEpoch_] = aggregatedStats;
} }
/// @dev Get information on an active staking pool in this epoch. /// @dev Get information on an active staking pool in this epoch.
/// @param poolId Pool Id to query. /// @param poolId Pool Id to query.
/// @return pool ActivePool struct. /// @return PoolStats struct for pool id.
function getActiveStakingPoolThisEpoch(bytes32 poolId) function getStakingPoolStatsThisEpoch(bytes32 poolId)
external external
view view
returns (IStructs.ActivePool memory pool) returns (IStructs.PoolStats memory)
{ {
pool = _getActivePoolFromEpoch(currentEpoch, poolId); return poolStatsByEpoch[poolId][currentEpoch];
return pool;
} }
/// @dev Computes the members and weighted stake for a pool at the current /// @dev Computes the members and weighted stake for a pool at the current

View File

@@ -101,25 +101,15 @@ contract MixinStorage is
// Denominator for cobb douglas alpha factor. // Denominator for cobb douglas alpha factor.
uint32 public cobbDouglasAlphaDenominator; uint32 public cobbDouglasAlphaDenominator;
/* Finalization states */ /* State for finalization */
/// @dev The total fees collected in the current epoch, built up iteratively /// @dev Stats for each pool that generated fees with sufficient stake to earn rewards.
/// in `payProtocolFee()`. /// See `_minimumPoolStake` in MixinParams.
uint256 public totalFeesCollectedThisEpoch; mapping (bytes32 => mapping (uint256 => IStructs.PoolStats)) public poolStatsByEpoch;
/// @dev The total weighted stake in the current epoch, built up iteratively /// @dev Aggregated stats across all pools that generated fees with sufficient stake to earn rewards.
/// in `payProtocolFee()`. /// See `_minimumPoolStake` in MixinParams.
uint256 public totalWeightedStakeThisEpoch; mapping (uint256 => IStructs.AggregatedStats) public aggregatedStatsByEpoch;
/// @dev State information for each active pool in an epoch.
/// In practice, we only store state for `currentEpoch % 2`.
mapping (uint256 => mapping (bytes32 => IStructs.ActivePool)) internal _activePoolsByEpoch;
/// @dev Number of pools activated in the current epoch.
uint256 public numActivePoolsThisEpoch;
/// @dev State for unfinalized rewards.
IStructs.UnfinalizedState public unfinalizedState;
/// @dev The WETH balance of this contract that is reserved for pool reward payouts. /// @dev The WETH balance of this contract that is reserved for pool reward payouts.
uint256 public wethReservedForPoolRewards; uint256 public wethReservedForPoolRewards;

View File

@@ -43,24 +43,23 @@ interface IStakingEvents {
address exchangeAddress address exchangeAddress
); );
/// @dev Emitted by MixinExchangeFees when a pool pays protocol fees /// @dev Emitted by MixinExchangeFees when a pool will earn rewards.
/// for the first time in an epoch. /// @param epoch The epoch in which the pool earned rewards.
/// @param epoch The epoch in which the pool was activated.
/// @param poolId The ID of the pool. /// @param poolId The ID of the pool.
event StakingPoolActivated( event StakingPoolEarnedRewardsInEpoch(
uint256 indexed epoch, uint256 indexed epoch,
bytes32 indexed poolId bytes32 indexed poolId
); );
/// @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 closing epoch.
/// @param numActivePools Number of active pools in the closing epoch. /// @param poolsToFinalize Number of pools to finalize in the closing epoch.
/// @param rewardsAvailable Rewards available to all active pools. /// @param rewardsAvailable Rewards available to all active pools.
/// @param totalWeightedStake Total weighted stake across all active pools. /// @param totalWeightedStake Total weighted stake across all active pools.
/// @param totalFeesCollected Total fees collected across all active pools. /// @param totalFeesCollected Total fees collected across all active pools.
event EpochEnded( event EpochEnded(
uint256 indexed epoch, uint256 indexed epoch,
uint256 numActivePools, uint256 poolsToFinalize,
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 activePoolsThisEpoch() function poolsToFinalizeThisEpoch()
external external
view view
returns (bytes32[] memory); returns (bytes32[] memory);

View File

@@ -29,29 +29,27 @@ interface IStructs {
uint96 lastSetTimestamp; uint96 lastSetTimestamp;
} }
/// @dev Status for a pool that actively traded during the current epoch. /// @dev Stats for a pool that actively traded.
/// (see MixinExchangeFees).
/// @param feesCollected Fees collected in ETH by this pool. /// @param feesCollected Fees collected in ETH by this pool.
/// @param weightedStake Amount of weighted stake in the pool. /// @param weightedStake Amount of weighted stake in the pool.
/// @param membersStake Amount of non-operator stake in the pool. /// @param membersStake Amount of non-operator stake in the pool.
struct ActivePool { struct PoolStats {
uint256 feesCollected; uint256 feesCollected;
uint256 weightedStake; uint256 weightedStake;
uint256 membersStake; uint256 membersStake;
} }
/// @dev Holds state for unfinalized epoch rewards. /// @dev Holds stats aggregated across a set of pools.
/// @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 poolsRemaining The number of active pools in the last /// @param poolsToFinalize The number of pools that have yet to be finalized through `finalizePools()`.
/// epoch 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 UnfinalizedState { struct AggregatedStats {
uint256 rewardsAvailable; uint256 rewardsAvailable;
uint256 poolsRemaining; uint256 poolsToFinalize;
uint256 totalFeesCollected; uint256 totalFeesCollected;
uint256 totalWeightedStake; uint256 totalWeightedStake;
uint256 totalRewardsFinalized; uint256 totalRewardsFinalized;

View File

@@ -38,56 +38,54 @@ contract MixinFinalizer is
/// If there were no active pools in the closing epoch, the epoch /// If there were no active pools in the closing epoch, the epoch
/// will be instantly finalized here. Otherwise, `finalizePool()` /// will be instantly finalized here. Otherwise, `finalizePool()`
/// should be called on each active pool afterwards. /// should be called on each active pool afterwards.
/// @return poolsRemaining The number of unfinalized pools. /// @return poolsToFinalize The number of unfinalized pools.
function endEpoch() function endEpoch()
external external
returns (uint256 poolsRemaining) returns (uint256)
{ {
uint256 closingEpoch = currentEpoch; uint256 closingEpoch = currentEpoch;
IStructs.UnfinalizedState memory state = unfinalizedState; uint256 prevEpoch = closingEpoch.safeSub(1);
// Make sure the previous epoch has been fully finalized. // Make sure the previous epoch has been fully finalized.
if (state.poolsRemaining != 0) { uint256 poolsToFinalizeFromPrevEpoch = aggregatedStatsByEpoch[prevEpoch].poolsToFinalize;
if (poolsToFinalizeFromPrevEpoch != 0) {
LibRichErrors.rrevert( LibRichErrors.rrevert(
LibStakingRichErrors.PreviousEpochNotFinalizedError( LibStakingRichErrors.PreviousEpochNotFinalizedError(
closingEpoch.safeSub(1), prevEpoch,
state.poolsRemaining poolsToFinalizeFromPrevEpoch
) )
); );
} }
// Convert all ETH to WETH // 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(); _wrapEth();
// Set up unfinalized state. // Load aggregated stats for the epoch we're ending.
state.rewardsAvailable = _getAvailableWethBalance(); IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[closingEpoch];
state.poolsRemaining = poolsRemaining = numActivePoolsThisEpoch; aggregatedStatsByEpoch[closingEpoch].rewardsAvailable =
state.totalFeesCollected = totalFeesCollectedThisEpoch; aggregatedStats.rewardsAvailable = _getAvailableWethBalance();
state.totalWeightedStake = totalWeightedStakeThisEpoch;
state.totalRewardsFinalized = 0;
unfinalizedState = state;
// Emit an event. // Emit an event.
emit EpochEnded( emit EpochEnded(
closingEpoch, closingEpoch,
state.poolsRemaining, aggregatedStats.poolsToFinalize,
state.rewardsAvailable, aggregatedStats.rewardsAvailable,
state.totalFeesCollected, aggregatedStats.totalFeesCollected,
state.totalWeightedStake aggregatedStats.totalWeightedStake
); );
// Reset current epoch state.
totalFeesCollectedThisEpoch = 0;
totalWeightedStakeThisEpoch = 0;
numActivePoolsThisEpoch = 0;
// Advance the epoch. This will revert if not enough time has passed. // Advance the epoch. This will revert if not enough time has passed.
_goToNextEpoch(); _goToNextEpoch();
// If there were no active pools, the epoch is already finalized. // If there were no active pools, the epoch is already finalized.
if (poolsRemaining == 0) { if (aggregatedStats.poolsToFinalize == 0) {
emit EpochFinalized(closingEpoch, 0, state.rewardsAvailable); emit EpochFinalized(closingEpoch, 0, aggregatedStats.rewardsAvailable);
} }
return aggregatedStats.poolsToFinalize;
} }
/// @dev Instantly finalizes a single pool that was active in the previous /// @dev Instantly finalizes a single pool that was active in the previous
@@ -99,29 +97,28 @@ contract MixinFinalizer is
function finalizePool(bytes32 poolId) function finalizePool(bytes32 poolId)
external external
{ {
// Load the finalization and pool state into memory. // Compute relevant epochs
IStructs.UnfinalizedState memory state = unfinalizedState;
// Noop if all active pools have been finalized.
if (state.poolsRemaining == 0) {
return;
}
uint256 currentEpoch_ = currentEpoch; uint256 currentEpoch_ = currentEpoch;
uint256 prevEpoch = currentEpoch_.safeSub(1); uint256 prevEpoch = currentEpoch_.safeSub(1);
IStructs.ActivePool memory pool = _getActivePoolFromEpoch(prevEpoch, poolId);
// Noop if the pool was not active or already finalized (has no fees). // Load the aggregated stats into memory; noop if no pools to finalize.
if (pool.feesCollected == 0) { IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[prevEpoch];
if (aggregatedStats.poolsToFinalize == 0) {
return; return;
} }
// Clear the pool state so we don't finalize it again, and to recoup // Noop if the pool was not active or already finalized (has no fees).
IStructs.PoolStats memory poolStats = poolStatsByEpoch[poolId][prevEpoch];
if (poolStats.feesCollected == 0) {
return;
}
// Clear the pool stats so we don't finalize it again, and to recoup
// some gas. // some gas.
delete _getActivePoolsFromEpoch(prevEpoch)[poolId]; delete poolStatsByEpoch[poolId][prevEpoch];
// Compute the rewards. // Compute the rewards.
uint256 rewards = _getUnfinalizedPoolRewardsFromState(pool, state); uint256 rewards = _getUnfinalizedPoolRewardsFromPoolStats(poolStats, aggregatedStats);
// Pay the operator and update rewards for the pool. // Pay the operator and update rewards for the pool.
// Note that we credit at the CURRENT epoch even though these rewards // Note that we credit at the CURRENT epoch even though these rewards
@@ -129,7 +126,7 @@ contract MixinFinalizer is
(uint256 operatorReward, uint256 membersReward) = _syncPoolRewards( (uint256 operatorReward, uint256 membersReward) = _syncPoolRewards(
poolId, poolId,
rewards, rewards,
pool.membersStake poolStats.membersStake
); );
// Emit an event. // Emit an event.
@@ -143,22 +140,22 @@ contract MixinFinalizer is
uint256 totalReward = operatorReward.safeAdd(membersReward); uint256 totalReward = operatorReward.safeAdd(membersReward);
// Increase `totalRewardsFinalized`. // Increase `totalRewardsFinalized`.
unfinalizedState.totalRewardsFinalized = aggregatedStatsByEpoch[prevEpoch].totalRewardsFinalized =
state.totalRewardsFinalized = aggregatedStats.totalRewardsFinalized =
state.totalRewardsFinalized.safeAdd(totalReward); aggregatedStats.totalRewardsFinalized.safeAdd(totalReward);
// Decrease the number of unfinalized pools left. // Decrease the number of unfinalized pools left.
unfinalizedState.poolsRemaining = aggregatedStatsByEpoch[prevEpoch].poolsToFinalize =
state.poolsRemaining = aggregatedStats.poolsToFinalize =
state.poolsRemaining.safeSub(1); aggregatedStats.poolsToFinalize.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 (state.poolsRemaining == 0) { if (aggregatedStats.poolsToFinalize == 0) {
emit EpochFinalized( emit EpochFinalized(
prevEpoch, prevEpoch,
state.totalRewardsFinalized, aggregatedStats.totalRewardsFinalized,
state.rewardsAvailable.safeSub(state.totalRewardsFinalized) aggregatedStats.rewardsAvailable.safeSub(aggregatedStats.totalRewardsFinalized)
); );
} }
} }
@@ -177,44 +174,10 @@ contract MixinFinalizer is
uint256 membersStake uint256 membersStake
) )
{ {
IStructs.ActivePool memory pool = _getActivePoolFromEpoch( uint256 prevEpoch = currentEpoch.safeSub(1);
currentEpoch.safeSub(1), IStructs.PoolStats memory poolStats = poolStatsByEpoch[poolId][prevEpoch];
poolId reward = _getUnfinalizedPoolRewardsFromPoolStats(poolStats, aggregatedStatsByEpoch[prevEpoch]);
); membersStake = poolStats.membersStake;
reward = _getUnfinalizedPoolRewardsFromState(pool, unfinalizedState);
membersStake = pool.membersStake;
}
/// @dev Get an active pool from an epoch by its ID.
/// @param epoch The epoch the pool was/will be active in.
/// @param poolId The ID of the pool.
/// @return pool The pool with ID `poolId` that was active in `epoch`.
function _getActivePoolFromEpoch(
uint256 epoch,
bytes32 poolId
)
internal
view
returns (IStructs.ActivePool memory pool)
{
pool = _getActivePoolsFromEpoch(epoch)[poolId];
return pool;
}
/// @dev Get a mapping of active pools from an epoch.
/// This uses the formula `epoch % 2` as the epoch index in order
/// to reuse state, because we only need to remember, at most, two
/// epochs at once.
/// @return activePools The pools that were active in `epoch`.
function _getActivePoolsFromEpoch(
uint256 epoch
)
internal
view
returns (mapping (bytes32 => IStructs.ActivePool) storage activePools)
{
activePools = _activePoolsByEpoch[epoch % 2];
return activePools;
} }
/// @dev Converts the entire ETH balance of this contract into WETH. /// @dev Converts the entire ETH balance of this contract into WETH.
@@ -247,10 +210,10 @@ contract MixinFinalizer is
view view
{ {
uint256 prevEpoch = currentEpoch.safeSub(1); uint256 prevEpoch = currentEpoch.safeSub(1);
IStructs.ActivePool memory pool = _getActivePoolFromEpoch(prevEpoch, poolId); IStructs.PoolStats memory poolStats = poolStatsByEpoch[poolId][prevEpoch];
// A pool that has any fees remaining has not been finalized // A pool that has any fees remaining has not been finalized
if (pool.feesCollected != 0) { if (poolStats.feesCollected != 0) {
LibRichErrors.rrevert( LibRichErrors.rrevert(
LibStakingRichErrors.PoolNotFinalizedError( LibStakingRichErrors.PoolNotFinalizedError(
poolId, poolId,
@@ -261,12 +224,12 @@ contract MixinFinalizer is
} }
/// @dev Computes the reward owed to a pool during finalization. /// @dev Computes the reward owed to a pool during finalization.
/// @param pool The active pool. /// @param poolStats Stats for a specific pool.
/// @param state The current state of finalization. /// @param aggregatedStats Stats aggregated across all pools.
/// @return rewards Unfinalized rewards for this pool. /// @return rewards Unfinalized rewards for the input pool.
function _getUnfinalizedPoolRewardsFromState( function _getUnfinalizedPoolRewardsFromPoolStats(
IStructs.ActivePool memory pool, IStructs.PoolStats memory poolStats,
IStructs.UnfinalizedState memory state IStructs.AggregatedStats memory aggregatedStats
) )
private private
view view
@@ -274,17 +237,17 @@ contract MixinFinalizer is
{ {
// There can't be any rewards if the pool was active or if it has // There can't be any rewards if the pool was active or if it has
// no stake. // no stake.
if (pool.feesCollected == 0) { if (poolStats.feesCollected == 0) {
return rewards; return rewards;
} }
// Use the cobb-douglas function to compute the total reward. // Use the cobb-douglas function to compute the total reward.
rewards = LibCobbDouglas.cobbDouglas( rewards = LibCobbDouglas.cobbDouglas(
state.rewardsAvailable, aggregatedStats.rewardsAvailable,
pool.feesCollected, poolStats.feesCollected,
state.totalFeesCollected, aggregatedStats.totalFeesCollected,
pool.weightedStake, poolStats.weightedStake,
state.totalWeightedStake, aggregatedStats.totalWeightedStake,
cobbDouglasAlphaNumerator, cobbDouglasAlphaNumerator,
cobbDouglasAlphaDenominator cobbDouglasAlphaDenominator
); );
@@ -292,7 +255,7 @@ contract MixinFinalizer is
// Clip the reward to always be under // Clip the reward to always be under
// `rewardsAvailable - totalRewardsPaid`, // `rewardsAvailable - totalRewardsPaid`,
// in case cobb-douglas overflows, which should be unlikely. // in case cobb-douglas overflows, which should be unlikely.
uint256 rewardsRemaining = state.rewardsAvailable.safeSub(state.totalRewardsFinalized); uint256 rewardsRemaining = aggregatedStats.rewardsAvailable.safeSub(aggregatedStats.totalRewardsFinalized);
if (rewardsRemaining < rewards) { if (rewardsRemaining < rewards) {
rewards = rewardsRemaining; rewards = rewardsRemaining;
} }

View File

@@ -74,20 +74,19 @@ contract TestFinalizer is
external external
{ {
require(feesCollected > 0, "FEES_MUST_BE_NONZERO"); require(feesCollected > 0, "FEES_MUST_BE_NONZERO");
mapping (bytes32 => IStructs.ActivePool) storage activePools = _getActivePoolsFromEpoch( uint256 currentEpoch_ = currentEpoch;
currentEpoch IStructs.PoolStats memory poolStats = poolStatsByEpoch[poolId][currentEpoch_];
); require(poolStats.feesCollected == 0, "POOL_ALREADY_ADDED");
IStructs.ActivePool memory pool = activePools[poolId];
require(pool.feesCollected == 0, "POOL_ALREADY_ADDED");
_operatorSharesByPool[poolId] = operatorShare; _operatorSharesByPool[poolId] = operatorShare;
activePools[poolId] = IStructs.ActivePool({ poolStatsByEpoch[poolId][currentEpoch_] = IStructs.PoolStats({
feesCollected: feesCollected, feesCollected: feesCollected,
membersStake: membersStake, membersStake: membersStake,
weightedStake: weightedStake weightedStake: weightedStake
}); });
totalFeesCollectedThisEpoch += feesCollected;
totalWeightedStakeThisEpoch += weightedStake; aggregatedStatsByEpoch[currentEpoch_].totalFeesCollected += feesCollected;
numActivePoolsThisEpoch += 1; aggregatedStatsByEpoch[currentEpoch_].totalWeightedStake += weightedStake;
aggregatedStatsByEpoch[currentEpoch_].poolsToFinalize += 1;
} }
/// @dev Drain the balance of this contract. /// @dev Drain the balance of this contract.
@@ -131,13 +130,21 @@ contract TestFinalizer is
); );
} }
/// @dev Expose `_getActivePoolFromEpoch`. /// @dev Expose pool stats for the input epoch.
function getActivePoolFromEpoch(uint256 epoch, bytes32 poolId) function getPoolStatsFromEpoch(uint256 epoch, bytes32 poolId)
external external
view view
returns (IStructs.ActivePool memory pool) returns (IStructs.PoolStats memory)
{ {
pool = _getActivePoolFromEpoch(epoch, poolId); return poolStatsByEpoch[poolId][epoch];
}
function getAggregatedStatsForPreviousEpoch()
external
view
returns (IStructs.AggregatedStats memory)
{
return aggregatedStatsByEpoch[currentEpoch - 1];
} }
/// @dev Overridden to log and transfer to receivers. /// @dev Overridden to log and transfer to receivers.

View File

@@ -86,6 +86,14 @@ contract TestProtocolFees is
return true; return true;
} }
function getAggregatedStatsForCurrentEpoch()
external
view
returns (IStructs.AggregatedStats memory)
{
return aggregatedStatsByEpoch[currentEpoch];
}
/// @dev Overridden to use test pools. /// @dev Overridden to use test pools.
function getStakingPoolIdOfMaker(address makerAddress) function getStakingPoolIdOfMaker(address makerAddress)
public public

View File

@@ -275,45 +275,21 @@ contract TestStorageLayoutAndConstants is
offset := 0x0 offset := 0x0
assertSlotAndOffset( assertSlotAndOffset(
totalFeesCollectedThisEpoch_slot, poolStatsByEpoch_slot,
totalFeesCollectedThisEpoch_offset, poolStatsByEpoch_offset,
slot, slot,
offset offset
) )
slot := add(slot, 0x1) slot := add(slot, 0x1)
assertSlotAndOffset( assertSlotAndOffset(
totalWeightedStakeThisEpoch_slot, aggregatedStatsByEpoch_slot,
totalWeightedStakeThisEpoch_offset, aggregatedStatsByEpoch_offset,
slot, slot,
offset offset
) )
slot := add(slot, 0x1) slot := add(slot, 0x1)
assertSlotAndOffset(
_activePoolsByEpoch_slot,
_activePoolsByEpoch_offset,
slot,
offset
)
slot := add(slot, 0x1)
assertSlotAndOffset(
numActivePoolsThisEpoch_slot,
numActivePoolsThisEpoch_offset,
slot,
offset
)
slot := add(slot, 0x1)
assertSlotAndOffset(
unfinalizedState_slot,
unfinalizedState_offset,
slot,
offset
)
slot := add(slot, 0x5)
assertSlotAndOffset( assertSlotAndOffset(
wethReservedForPoolRewards_slot, wethReservedForPoolRewards_slot,
wethReservedForPoolRewards_offset, wethReservedForPoolRewards_offset,

View File

@@ -238,7 +238,7 @@ export class FinalizerActor extends BaseActor {
private async _getRewardByPoolIdAsync(poolIds: string[]): Promise<RewardByPoolId> { private async _getRewardByPoolIdAsync(poolIds: string[]): Promise<RewardByPoolId> {
const activePools = await Promise.all( const activePools = await Promise.all(
poolIds.map(async poolId => poolIds.map(async poolId =>
this._stakingApiWrapper.stakingContract.getActiveStakingPoolThisEpoch.callAsync(poolId), this._stakingApiWrapper.stakingContract.getStakingPoolStatsThisEpoch.callAsync(poolId),
), ),
); );
const totalRewards = await this._stakingApiWrapper.utils.getAvailableRewardsBalanceAsync(); const totalRewards = await this._stakingApiWrapper.utils.getAvailableRewardsBalanceAsync();

View File

@@ -87,21 +87,14 @@ blockchainTests.resets('Finalizer unit tests', env => {
interface UnfinalizedState { interface UnfinalizedState {
rewardsAvailable: Numberish; rewardsAvailable: Numberish;
poolsRemaining: number; poolsToFinalize: Numberish;
totalFeesCollected: Numberish; totalFeesCollected: Numberish;
totalWeightedStake: Numberish; totalWeightedStake: Numberish;
totalRewardsFinalized: Numberish; totalRewardsFinalized: Numberish;
} }
async function getUnfinalizedStateAsync(): Promise<UnfinalizedState> { async function getUnfinalizedStateAsync(): Promise<UnfinalizedState> {
const r = await testContract.unfinalizedState.callAsync(); return testContract.getAggregatedStatsForPreviousEpoch.callAsync();
return {
rewardsAvailable: r[0],
poolsRemaining: r[1].toNumber(),
totalFeesCollected: r[2],
totalWeightedStake: r[3],
totalRewardsFinalized: r[4],
};
} }
async function finalizePoolsAsync(poolIds: string[]): Promise<LogEntry[]> { async function finalizePoolsAsync(poolIds: string[]): Promise<LogEntry[]> {
@@ -142,16 +135,16 @@ blockchainTests.resets('Finalizer unit tests', env => {
async function assertFinalizationLogsAndBalancesAsync( async function assertFinalizationLogsAndBalancesAsync(
rewardsAvailable: Numberish, rewardsAvailable: Numberish,
activePools: ActivePoolOpts[], poolsToFinalize: 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 = 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 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(activePools, poolRewards); const [totalOperatorRewards, totalMembersRewards] = getTotalSplitRewards(poolsToFinalize, poolRewards);
// Assert the `RewardsPaid` logs. // Assert the `RewardsPaid` logs.
const rewardsPaidEvents = getRewardsPaidEvents(finalizationLogs); const rewardsPaidEvents = getRewardsPaidEvents(finalizationLogs);
@@ -203,13 +196,13 @@ blockchainTests.resets('Finalizer unit tests', env => {
async function calculatePoolRewardsAsync( async function calculatePoolRewardsAsync(
rewardsAvailable: Numberish, rewardsAvailable: Numberish,
activePools: ActivePoolOpts[], poolsToFinalize: ActivePoolOpts[],
): Promise<BigNumber[]> { ): Promise<BigNumber[]> {
const totalFees = BigNumber.sum(...activePools.map(p => p.feesCollected)); const totalFees = BigNumber.sum(...poolsToFinalize.map(p => p.feesCollected));
const totalStake = BigNumber.sum(...activePools.map(p => p.weightedStake)); const totalStake = BigNumber.sum(...poolsToFinalize.map(p => p.weightedStake));
const poolRewards = _.times(activePools.length, () => constants.ZERO_AMOUNT); const poolRewards = _.times(poolsToFinalize.length, () => constants.ZERO_AMOUNT);
for (const i of _.times(activePools.length)) { for (const i of _.times(poolsToFinalize.length)) {
const pool = activePools[i]; const pool = poolsToFinalize[i];
const feesCollected = new BigNumber(pool.feesCollected); const feesCollected = new BigNumber(pool.feesCollected);
if (feesCollected.isZero()) { if (feesCollected.isZero()) {
continue; continue;
@@ -307,32 +300,48 @@ blockchainTests.resets('Finalizer unit tests', env => {
expect(events).to.deep.eq([]); 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 () => { it('prepares unfinalized state', async () => {
// Add a pool so there is state to clear. // Add a pool so there is state to clear.
const pool = await addActivePoolAsync(); const pool = await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
return assertUnfinalizedStateAsync({ return assertUnfinalizedStateAsync({
poolsRemaining: 1, poolsToFinalize: 1,
rewardsAvailable: INITIAL_BALANCE, rewardsAvailable: INITIAL_BALANCE,
totalFeesCollected: pool.feesCollected, totalFeesCollected: pool.feesCollected,
totalWeightedStake: pool.weightedStake, 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 () => { it('reverts if the prior epoch is unfinalized', async () => {
await addActivePoolAsync(); await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
@@ -382,7 +391,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
const pool = _.sample(pools) as ActivePoolOpts; const pool = _.sample(pools) as ActivePoolOpts;
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
await finalizePoolsAsync([pool.poolId]); await finalizePoolsAsync([pool.poolId]);
const poolState = await testContract.getActivePoolFromEpoch.callAsync( const poolState = await testContract.getPoolStatsFromEpoch.callAsync(
stakingConstants.INITIAL_EPOCH, stakingConstants.INITIAL_EPOCH,
pool.poolId, pool.poolId,
); );

View File

@@ -15,7 +15,7 @@ import * as _ from 'lodash';
import { import {
artifacts, artifacts,
IStakingEventsEvents, IStakingEventsEvents,
IStakingEventsStakingPoolActivatedEventArgs, IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs,
TestProtocolFeesContract, TestProtocolFeesContract,
TestProtocolFeesERC20ProxyTransferFromEventArgs, TestProtocolFeesERC20ProxyTransferFromEventArgs,
TestProtocolFeesEvents, TestProtocolFeesEvents,
@@ -152,7 +152,7 @@ blockchainTests('Protocol Fees unit tests', env => {
}); });
async function getProtocolFeesAsync(poolId: string): Promise<BigNumber> { async function getProtocolFeesAsync(poolId: string): Promise<BigNumber> {
return (await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId)).feesCollected; return (await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId)).feesCollected;
} }
describe('ETH fees', () => { describe('ETH fees', () => {
@@ -369,21 +369,22 @@ blockchainTests('Protocol Fees unit tests', env => {
}); });
interface FinalizationState { interface FinalizationState {
numActivePools: BigNumber; poolsToFinalize: BigNumber;
totalFeesCollected: BigNumber; totalFeesCollected: BigNumber;
totalWeightedStake: BigNumber; totalWeightedStake: BigNumber;
} }
async function getFinalizationStateAsync(): Promise<FinalizationState> { async function getFinalizationStateAsync(): Promise<FinalizationState> {
const aggregatedStats = await testContract.getAggregatedStatsForCurrentEpoch.callAsync();
return { return {
numActivePools: await testContract.numActivePoolsThisEpoch.callAsync(), poolsToFinalize: aggregatedStats.poolsToFinalize,
totalFeesCollected: await testContract.totalFeesCollectedThisEpoch.callAsync(), totalFeesCollected: aggregatedStats.totalFeesCollected,
totalWeightedStake: await testContract.totalWeightedStakeThisEpoch.callAsync(), totalWeightedStake: aggregatedStats.totalWeightedStake,
}; };
} }
interface PayToMakerResult { interface PayToMakerResult {
poolActivatedEvents: IStakingEventsStakingPoolActivatedEventArgs[]; poolEarnedRewardsEvents: IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs[];
fee: BigNumber; fee: BigNumber;
} }
@@ -395,13 +396,13 @@ blockchainTests('Protocol Fees unit tests', env => {
new BigNumber(_fee), new BigNumber(_fee),
{ from: exchangeAddress, value: _fee }, { from: exchangeAddress, value: _fee },
); );
const events = filterLogsToArguments<IStakingEventsStakingPoolActivatedEventArgs>( const events = filterLogsToArguments<IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs>(
receipt.logs, receipt.logs,
IStakingEventsEvents.StakingPoolActivated, IStakingEventsEvents.StakingPoolEarnedRewardsInEpoch,
); );
return { return {
fee: new BigNumber(_fee), 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 () => { it('no active pools to start', async () => {
const state = await getFinalizationStateAsync(); 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.totalFeesCollected).to.bignumber.eq(0);
expect(state.totalWeightedStake).to.bignumber.eq(0); expect(state.totalWeightedStake).to.bignumber.eq(0);
}); });
it('pool is not registered to start', async () => { it('pool is not registered to start', async () => {
const { poolId } = await createTestPoolAsync(); 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.feesCollected).to.bignumber.eq(0);
expect(pool.membersStake).to.bignumber.eq(0); expect(pool.membersStake).to.bignumber.eq(0);
expect(pool.weightedStake).to.bignumber.eq(0); expect(pool.weightedStake).to.bignumber.eq(0);
@@ -433,16 +434,16 @@ blockchainTests('Protocol Fees unit tests', env => {
poolId, poolId,
makers: [poolMaker], makers: [poolMaker],
} = pool; } = pool;
const { fee, poolActivatedEvents } = await payToMakerAsync(poolMaker); const { fee, poolEarnedRewardsEvents } = await payToMakerAsync(poolMaker);
expect(poolActivatedEvents.length).to.eq(1); expect(poolEarnedRewardsEvents.length).to.eq(1);
expect(poolActivatedEvents[0].poolId).to.eq(poolId); expect(poolEarnedRewardsEvents[0].poolId).to.eq(poolId);
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId); const actualPoolStats = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake); const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
expect(actualPool.feesCollected).to.bignumber.eq(fee); expect(actualPoolStats.feesCollected).to.bignumber.eq(fee);
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake); expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake);
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake); expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake);
const state = await getFinalizationStateAsync(); 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.totalFeesCollected).to.bignumber.eq(fee);
expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake); expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
}); });
@@ -454,16 +455,16 @@ blockchainTests('Protocol Fees unit tests', env => {
makers: [poolMaker], makers: [poolMaker],
} = pool; } = pool;
const { fee: fee1 } = await payToMakerAsync(poolMaker); const { fee: fee1 } = await payToMakerAsync(poolMaker);
const { fee: fee2, poolActivatedEvents } = await payToMakerAsync(poolMaker); const { fee: fee2, poolEarnedRewardsEvents } = await payToMakerAsync(poolMaker);
expect(poolActivatedEvents).to.deep.eq([]); expect(poolEarnedRewardsEvents).to.deep.eq([]);
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId); const actualPoolStats = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake); const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
const fees = BigNumber.sum(fee1, fee2); const fees = BigNumber.sum(fee1, fee2);
expect(actualPool.feesCollected).to.bignumber.eq(fees); expect(actualPoolStats.feesCollected).to.bignumber.eq(fees);
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake); expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake);
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake); expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake);
const state = await getFinalizationStateAsync(); 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.totalFeesCollected).to.bignumber.eq(fees);
expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake); expect(state.totalWeightedStake).to.bignumber.eq(expectedWeightedStake);
}); });
@@ -477,19 +478,19 @@ blockchainTests('Protocol Fees unit tests', env => {
poolId, poolId,
makers: [poolMaker], makers: [poolMaker],
} = pool; } = pool;
const { fee, poolActivatedEvents } = await payToMakerAsync(poolMaker); const { fee, poolEarnedRewardsEvents } = await payToMakerAsync(poolMaker);
expect(poolActivatedEvents.length).to.eq(1); expect(poolEarnedRewardsEvents.length).to.eq(1);
expect(poolActivatedEvents[0].poolId).to.eq(poolId); expect(poolEarnedRewardsEvents[0].poolId).to.eq(poolId);
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId); const actualPoolStats = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake); const expectedWeightedStake = toWeightedStake(pool.operatorStake, pool.membersStake);
expect(actualPool.feesCollected).to.bignumber.eq(fee); expect(actualPoolStats.feesCollected).to.bignumber.eq(fee);
expect(actualPool.membersStake).to.bignumber.eq(pool.membersStake); expect(actualPoolStats.membersStake).to.bignumber.eq(pool.membersStake);
expect(actualPool.weightedStake).to.bignumber.eq(expectedWeightedStake); expect(actualPoolStats.weightedStake).to.bignumber.eq(expectedWeightedStake);
totalFees = totalFees.plus(fee); totalFees = totalFees.plus(fee);
totalWeightedStake = totalWeightedStake.plus(expectedWeightedStake); totalWeightedStake = totalWeightedStake.plus(expectedWeightedStake);
} }
const state = await getFinalizationStateAsync(); 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.totalFeesCollected).to.bignumber.eq(totalFees);
expect(state.totalWeightedStake).to.bignumber.eq(totalWeightedStake); expect(state.totalWeightedStake).to.bignumber.eq(totalWeightedStake);
}); });
@@ -502,10 +503,10 @@ blockchainTests('Protocol Fees unit tests', env => {
} = pool; } = pool;
await payToMakerAsync(poolMaker); await payToMakerAsync(poolMaker);
await testContract.advanceEpoch.awaitTransactionSuccessAsync(); await testContract.advanceEpoch.awaitTransactionSuccessAsync();
const actualPool = await testContract.getActiveStakingPoolThisEpoch.callAsync(poolId); const actualPoolStats = await testContract.getStakingPoolStatsThisEpoch.callAsync(poolId);
expect(actualPool.feesCollected).to.bignumber.eq(0); expect(actualPoolStats.feesCollected).to.bignumber.eq(0);
expect(actualPool.membersStake).to.bignumber.eq(0); expect(actualPoolStats.membersStake).to.bignumber.eq(0);
expect(actualPool.weightedStake).to.bignumber.eq(0); expect(actualPoolStats.weightedStake).to.bignumber.eq(0);
}); });
describe('Multiple makers', () => { describe('Multiple makers', () => {

View File

@@ -9,7 +9,7 @@ import * as _ from 'lodash';
import { import {
artifacts, artifacts,
IStakingEventsEpochEndedEventArgs, IStakingEventsEpochEndedEventArgs,
IStakingEventsStakingPoolActivatedEventArgs, IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs,
ReadOnlyProxyContract, ReadOnlyProxyContract,
StakingProxyContract, StakingProxyContract,
TestCobbDouglasContract, TestCobbDouglasContract,
@@ -76,13 +76,13 @@ export class StakingApiWrapper {
findActivePoolIdsAsync: async (epoch?: number): Promise<string[]> => { findActivePoolIdsAsync: async (epoch?: number): Promise<string[]> => {
const _epoch = epoch !== undefined ? epoch : await this.stakingContract.currentEpoch.callAsync(); const _epoch = epoch !== undefined ? epoch : await this.stakingContract.currentEpoch.callAsync();
const events = filterLogsToArguments<IStakingEventsStakingPoolActivatedEventArgs>( const events = filterLogsToArguments<IStakingEventsStakingPoolEarnedRewardsInEpochEventArgs>(
await this.stakingContract.getLogsAsync( await this.stakingContract.getLogsAsync(
TestStakingEvents.StakingPoolActivated, TestStakingEvents.StakingPoolEarnedRewardsInEpoch,
{ fromBlock: BlockParamLiteral.Earliest, toBlock: BlockParamLiteral.Latest }, { fromBlock: BlockParamLiteral.Earliest, toBlock: BlockParamLiteral.Latest },
{ epoch: new BigNumber(_epoch) }, { epoch: new BigNumber(_epoch) },
), ),
TestStakingEvents.StakingPoolActivated, TestStakingEvents.StakingPoolEarnedRewardsInEpoch,
); );
return events.map(e => e.poolId); return events.map(e => e.poolId);
}, },