Merge pull request #2276 from 0xProject/cleanup/staking/finalizationDataStructures

Refactored finalization state.
This commit is contained in:
Greg Hysz
2019-10-22 12:37:50 -07:00
committed by GitHub
16 changed files with 229 additions and 312 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 storage poolStatsPtr = poolStatsByEpoch[poolId][currentEpoch_];
_getActivePoolsFromEpoch(currentEpoch_); IStructs.AggregatedStats storage aggregatedStatsPtr = aggregatedStatsByEpoch[currentEpoch_];
IStructs.ActivePool memory pool = activePoolsThisEpoch[poolId]; // Perform some initialization if this is the pool's first protocol fee in this epoch.
uint256 feesCollectedByPool = poolStatsPtr.feesCollected;
// If the pool was previously inactive in this epoch, initialize it. if (feesCollectedByPool == 0) {
if (pool.feesCollected == 0) {
// Compute member and total weighted stake. // Compute member and total weighted stake.
(pool.membersStake, pool.weightedStake) = _computeMembersAndWeightedStake(poolId, poolStake); (uint256 membersStakeInPool, uint256 weightedStakeInPool) = _computeMembersAndWeightedStake(poolId, poolStake);
poolStatsPtr.membersStake = membersStakeInPool;
poolStatsPtr.weightedStake = weightedStakeInPool;
// Increase the total weighted stake. // Increase the total weighted stake.
totalWeightedStakeThisEpoch = totalWeightedStakeThisEpoch.safeAdd(pool.weightedStake); aggregatedStatsPtr.totalWeightedStake = aggregatedStatsPtr.totalWeightedStake.safeAdd(weightedStakeInPool);
// Increase the number of active pools. // Increase the number of pools to finalize.
numActivePoolsThisEpoch = numActivePoolsThisEpoch.safeAdd(1); aggregatedStatsPtr.numPoolsToFinalize = aggregatedStatsPtr.numPoolsToFinalize.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); poolStatsPtr.feesCollected = feesCollectedByPool.safeAdd(protocolFeePaid);
// Increase the total fees collected this epoch. // Increase the total fees collected this epoch.
totalFeesCollectedThisEpoch = totalFeesCollectedThisEpoch.safeAdd(protocolFeePaid); aggregatedStatsPtr.totalFeesCollected = aggregatedStatsPtr.totalFeesCollected.safeAdd(protocolFeePaid);
// Store the pool.
activePoolsThisEpoch[poolId] = pool;
} }
/// @dev Get information on an active staking pool in this epoch. /// @dev Get stats on a 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 starts earning rewards in an epoch.
/// 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 epoch that ended.
/// @param numActivePools Number of active pools in the closing epoch. /// @param numPoolsToFinalize Number of pools that earned rewards during `epoch` and must be finalized.
/// @param rewardsAvailable Rewards available to all active pools. /// @param rewardsAvailable Rewards available to all pools that earned rewards during `epoch`.
/// @param totalWeightedStake Total weighted stake across all active pools. /// @param totalWeightedStake Total weighted stake across all pools that earned rewards during `epoch`.
/// @param totalFeesCollected Total fees collected across all active pools. /// @param totalFeesCollected Total fees collected across all pools that earned rewards during `epoch`.
event EpochEnded( event EpochEnded(
uint256 indexed epoch, uint256 indexed epoch,
uint256 numActivePools, uint256 numPoolsToFinalize,
uint256 rewardsAvailable, uint256 rewardsAvailable,
uint256 totalFeesCollected, uint256 totalFeesCollected,
uint256 totalWeightedStake uint256 totalWeightedStake

View File

@@ -65,11 +65,6 @@ interface IStorage {
view view
returns (uint256); returns (uint256);
function activePoolsThisEpoch()
external
view
returns (bytes32[] memory);
function validExchanges(address exchangeAddress) function validExchanges(address exchangeAddress)
external external
view view

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 earned rewards.
/// (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 numPoolsToFinalize 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 numPoolsToFinalize;
uint256 totalFeesCollected; uint256 totalFeesCollected;
uint256 totalWeightedStake; uint256 totalWeightedStake;
uint256 totalRewardsFinalized; uint256 totalRewardsFinalized;

View File

@@ -33,11 +33,9 @@ library LibCobbDouglas {
/// 0 <= alphaNumerator / alphaDenominator <= 1 /// 0 <= alphaNumerator / alphaDenominator <= 1
/// @param totalRewards collected over an epoch. /// @param totalRewards collected over an epoch.
/// @param fees Fees attributed to the the staking pool. /// @param fees Fees attributed to the the staking pool.
/// @param totalFees Total fees collected across all active staking pools in /// @param totalFees Total fees collected across all pools that earned rewards.
/// the epoch.
/// @param stake Stake attributed to the staking pool. /// @param stake Stake attributed to the staking pool.
/// @param totalStake Total stake across all active staking pools in the /// @param totalStake Total stake across all pools that earned rewards.
/// epoch.
/// @param alphaNumerator Numerator of `alpha` in the cobb-douglas function. /// @param alphaNumerator Numerator of `alpha` in the cobb-douglas function.
/// @param alphaDenominator Denominator of `alpha` in the cobb-douglas /// @param alphaDenominator Denominator of `alpha` in the cobb-douglas
/// function. /// function.

View File

@@ -72,7 +72,7 @@ contract MixinStakeBalances is
/// @dev Returns the total stake for a given staker. /// @dev Returns the total stake for a given staker.
/// @param staker of stake. /// @param staker of stake.
/// @return Total active stake for staker. /// @return Total ZRX staked by `staker`.
function getTotalStake(address staker) function getTotalStake(address staker)
public public
view view

View File

@@ -35,93 +35,83 @@ 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 there were no active pools in the closing epoch, the epoch /// @return numPoolsToFinalize The number of unfinalized pools.
/// will be instantly finalized here. Otherwise, `finalizePool()`
/// should be called on each active pool afterwards.
/// @return poolsRemaining The number of unfinalized pools.
function endEpoch() function endEpoch()
external external
returns (uint256 poolsRemaining) returns (uint256)
{ {
uint256 closingEpoch = currentEpoch; uint256 currentEpoch_ = currentEpoch;
IStructs.UnfinalizedState memory state = unfinalizedState; uint256 prevEpoch = currentEpoch_.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 numPoolsToFinalizeFromPrevEpoch = aggregatedStatsByEpoch[prevEpoch].numPoolsToFinalize;
if (numPoolsToFinalizeFromPrevEpoch != 0) {
LibRichErrors.rrevert( LibRichErrors.rrevert(
LibStakingRichErrors.PreviousEpochNotFinalizedError( LibStakingRichErrors.PreviousEpochNotFinalizedError(
closingEpoch.safeSub(1), prevEpoch,
state.poolsRemaining numPoolsToFinalizeFromPrevEpoch
) )
); );
} }
// Convert all ETH to WETH // 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(); aggregatedStatsByEpoch[currentEpoch_].rewardsAvailable = _getAvailableWethBalance();
state.poolsRemaining = poolsRemaining = numActivePoolsThisEpoch; IStructs.AggregatedStats memory aggregatedStats = aggregatedStatsByEpoch[currentEpoch_];
state.totalFeesCollected = totalFeesCollectedThisEpoch;
state.totalWeightedStake = totalWeightedStakeThisEpoch;
state.totalRewardsFinalized = 0;
unfinalizedState = state;
// Emit an event. // Emit an event.
emit EpochEnded( emit EpochEnded(
closingEpoch, currentEpoch_,
state.poolsRemaining, aggregatedStats.numPoolsToFinalize,
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 are no pools to finalize then the epoch is finalized.
if (poolsRemaining == 0) { if (aggregatedStats.numPoolsToFinalize == 0) {
emit EpochFinalized(closingEpoch, 0, state.rewardsAvailable); emit EpochFinalized(currentEpoch_, 0, aggregatedStats.rewardsAvailable);
} }
return aggregatedStats.numPoolsToFinalize;
} }
/// @dev Instantly finalizes a single pool that was active in the previous /// @dev Instantly finalizes a single pool that earned rewards in the previous
/// epoch, crediting it rewards for members and withdrawing operator's /// epoch, crediting it rewards for members and withdrawing operator's
/// rewards as WETH. This can be called by internal functions that need /// rewards as WETH. This can be called by internal functions that need
/// to finalize a pool immediately. Does nothing if the pool is already /// to finalize a pool immediately. Does nothing if the pool is already
/// finalized or was not active in the previous epoch. /// finalized or did not earn rewards in the previous epoch.
/// @param poolId The pool ID to finalize. /// @param poolId The pool ID to finalize.
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.numPoolsToFinalize == 0) {
return; return;
} }
// Clear the pool state so we don't finalize it again, and to recoup // Noop if the pool did not earn rewards 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 +119,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 +133,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].numPoolsToFinalize =
state.poolsRemaining = aggregatedStats.numPoolsToFinalize =
state.poolsRemaining.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 (state.poolsRemaining == 0) { if (aggregatedStats.numPoolsToFinalize == 0) {
emit EpochFinalized( emit EpochFinalized(
prevEpoch, prevEpoch,
state.totalRewardsFinalized, aggregatedStats.totalRewardsFinalized,
state.rewardsAvailable.safeSub(state.totalRewardsFinalized) aggregatedStats.rewardsAvailable.safeSub(aggregatedStats.totalRewardsFinalized)
); );
} }
} }
@@ -177,44 +167,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 +203,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,30 +217,29 @@ 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
returns (uint256 rewards) returns (uint256 rewards)
{ {
// There can't be any rewards if the pool was active or if it has // There can't be any rewards if the pool did not collect any fees.
// no stake. if (poolStats.feesCollected == 0) {
if (pool.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 +247,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

@@ -94,7 +94,7 @@ contract TestDelegatorRewards is
currentEpoch += 1; currentEpoch += 1;
} }
/// @dev Create and delegate stake that is active in the current epoch. /// @dev Create and delegate stake to the current epoch.
/// Only used to test purportedly unreachable states. /// Only used to test purportedly unreachable states.
/// Also withdraws pending rewards. /// Also withdraws pending rewards.
function delegateStakeNow( function delegateStakeNow(

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_].numPoolsToFinalize += 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; numPoolsToFinalize: 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;
@@ -288,7 +281,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
}); });
}); });
it('immediately finalizes if there are no active pools', async () => { it('immediately finalizes if there are no pools to finalize', async () => {
const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync(); const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
assertEpochFinalizedEvent(receipt.logs, { assertEpochFinalizedEvent(receipt.logs, {
epoch: stakingConstants.INITIAL_EPOCH, epoch: stakingConstants.INITIAL_EPOCH,
@@ -297,7 +290,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
}); });
}); });
it('does not immediately finalize if there is an active pool', async () => { it('does not immediately finalize if there is a pool to finalize', async () => {
await addActivePoolAsync(); await addActivePoolAsync();
const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync(); const receipt = await testContract.endEpoch.awaitTransactionSuccessAsync();
const events = filterLogsToArguments<IStakingEventsEpochFinalizedEventArgs>( const events = filterLogsToArguments<IStakingEventsEpochFinalizedEventArgs>(
@@ -307,32 +300,32 @@ 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, numPoolsToFinalize: 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('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();
@@ -346,7 +339,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
}); });
describe('_finalizePool()', () => { describe('_finalizePool()', () => {
it('does nothing if there were no active pools', async () => { it('does nothing if there were no pools to finalize', async () => {
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const poolId = hexRandom(); const poolId = hexRandom();
const logs = await finalizePoolsAsync([poolId]); const logs = await finalizePoolsAsync([poolId]);
@@ -382,7 +375,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,
); );
@@ -436,7 +429,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
return expect(getCurrentEpochAsync()).to.become(stakingConstants.INITIAL_EPOCH.plus(2)); return expect(getCurrentEpochAsync()).to.become(stakingConstants.INITIAL_EPOCH.plus(2));
}); });
it('does not reward a pool that was only active 2 epochs ago', async () => { it('does not reward a pool that only earned rewards 2 epochs ago', async () => {
const pool1 = await addActivePoolAsync(); const pool1 = await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
await finalizePoolsAsync([pool1.poolId]); await finalizePoolsAsync([pool1.poolId]);
@@ -448,7 +441,7 @@ blockchainTests.resets('Finalizer unit tests', env => {
expect(rewardsPaidEvents).to.deep.eq([]); expect(rewardsPaidEvents).to.deep.eq([]);
}); });
it('does not reward a pool that was only active 3 epochs ago', async () => { it('does not reward a pool that only earned rewards 3 epochs ago', async () => {
const pool1 = await addActivePoolAsync(); const pool1 = await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
await finalizePoolsAsync([pool1.poolId]); await finalizePoolsAsync([pool1.poolId]);
@@ -503,18 +496,18 @@ blockchainTests.resets('Finalizer unit tests', env => {
return assertUnfinalizedPoolRewardsAsync(poolId, ZERO_REWARDS); return assertUnfinalizedPoolRewardsAsync(poolId, ZERO_REWARDS);
}); });
it('returns empty if pool was not active', async () => { it('returns empty if pool did not earn rewards', async () => {
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
const poolId = hexRandom(); const poolId = hexRandom();
return assertUnfinalizedPoolRewardsAsync(poolId, ZERO_REWARDS); return assertUnfinalizedPoolRewardsAsync(poolId, ZERO_REWARDS);
}); });
it('returns empty if pool is active only in the current epoch', async () => { it('returns empty if pool is earned rewards only in the current epoch', async () => {
const pool = await addActivePoolAsync(); const pool = await addActivePoolAsync();
return assertUnfinalizedPoolRewardsAsync(pool.poolId, ZERO_REWARDS); return assertUnfinalizedPoolRewardsAsync(pool.poolId, ZERO_REWARDS);
}); });
it('returns empty if pool was only active in the 2 epochs ago', async () => { it('returns empty if pool only earned rewards in the 2 epochs ago', async () => {
const pool = await addActivePoolAsync(); const pool = await addActivePoolAsync();
await testContract.endEpoch.awaitTransactionSuccessAsync(); await testContract.endEpoch.awaitTransactionSuccessAsync();
await finalizePoolsAsync([pool.poolId]); await finalizePoolsAsync([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; numPoolsToFinalize: 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(), numPoolsToFinalize: aggregatedStats.numPoolsToFinalize,
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,
}; };
} }
@@ -412,37 +413,37 @@ blockchainTests('Protocol Fees unit tests', env => {
.plus(operatorStake); .plus(operatorStake);
} }
it('no active pools to start', async () => { it('no pools to finalize to start', async () => {
const state = await getFinalizationStateAsync(); const state = await getFinalizationStateAsync();
expect(state.numActivePools).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);
}); });
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);
}); });
it('activates a active pool the first time it earns a fee', async () => { it('correctly emits event for pool the first time it earns a fee', async () => {
const pool = await createTestPoolAsync(); const pool = await createTestPoolAsync();
const { const {
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.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);
}); });
@@ -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.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);
}); });
@@ -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.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);
}); });
@@ -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);
}, },