more explicit sanity checks for computing balance in interval (previously all failed with div-by-zero)
typos
This commit is contained in:
@@ -51,6 +51,12 @@ library LibStakingRichErrors {
|
||||
PoolIsFull
|
||||
}
|
||||
|
||||
enum CumulativeRewardIntervalErrorCode {
|
||||
BeginEpochMustBeLessThanEndEpoch,
|
||||
BeginEpochDoesNotHaveReward,
|
||||
EndEpochDoesNotHaveReward
|
||||
}
|
||||
|
||||
// bytes4(keccak256("MiscalculatedRewardsError(uint256,uint256)"))
|
||||
bytes4 internal constant MISCALCULATED_REWARDS_ERROR_SELECTOR =
|
||||
0xf7806c4e;
|
||||
@@ -147,6 +153,10 @@ library LibStakingRichErrors {
|
||||
bytes internal constant INVALID_WETH_ASSET_DATA_ERROR =
|
||||
hex"24bf322c";
|
||||
|
||||
// bytes4(keccak256("CumulativeRewardIntervalError(uint8,bytes32,uint256,uint256)"))
|
||||
bytes4 internal constant CUMULATIVE_REWARD_INTERVAL_ERROR_SELECTOR =
|
||||
0x1f806d55;
|
||||
|
||||
// solhint-disable func-name-mixedcase
|
||||
function MiscalculatedRewardsError(
|
||||
uint256 totalRewardsPaid,
|
||||
@@ -455,4 +465,23 @@ library LibStakingRichErrors {
|
||||
{
|
||||
return INVALID_WETH_ASSET_DATA_ERROR;
|
||||
}
|
||||
|
||||
function CumulativeRewardIntervalError(
|
||||
CumulativeRewardIntervalErrorCode errorCode,
|
||||
bytes32 poolId,
|
||||
uint256 beginEpoch,
|
||||
uint256 endEpoch
|
||||
)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodeWithSelector(
|
||||
CUMULATIVE_REWARD_INTERVAL_ERROR_SELECTOR,
|
||||
errorCode,
|
||||
poolId,
|
||||
beginEpoch,
|
||||
endEpoch
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -190,7 +190,7 @@ contract MixinStake is
|
||||
// increment how much stake has been delegated to pool
|
||||
_incrementNextBalance(delegatedStakeByPoolId[poolId], amount);
|
||||
|
||||
// synchronizes reward state in the pool that the staker is undelegating from
|
||||
// synchronizes reward state in the pool that the staker is delegating to
|
||||
IStructs.StoredBalance memory finalDelegatedStakeToPoolByOwner = _loadAndSyncBalance(delegatedStakeToPoolByOwner[owner][poolId]);
|
||||
_syncRewardsForDelegator(poolId, owner, initDelegatedStakeToPoolByOwner, finalDelegatedStakeToPoolByOwner);
|
||||
}
|
||||
|
@@ -263,7 +263,7 @@ contract MixinCumulativeRewards is
|
||||
|
||||
/// @dev Computes a member's reward over a given epoch interval.
|
||||
/// @param poolId Uniqud Id of pool.
|
||||
/// @param memberStakeOverInterval Stake delegated to pool by meber over the interval.
|
||||
/// @param memberStakeOverInterval Stake delegated to pool by member over the interval.
|
||||
/// @param beginEpoch beginning of interval.
|
||||
/// @param endEpoch end of interval.
|
||||
/// @return rewards accumulated over interval [beginEpoch, endEpoch]
|
||||
@@ -277,19 +277,55 @@ contract MixinCumulativeRewards is
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
// sanity check
|
||||
// sanity check inputs
|
||||
if (memberStakeOverInterval == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// sanity check interval
|
||||
if (beginEpoch >= endEpoch) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.CumulativeRewardIntervalError(
|
||||
LibStakingRichErrors.CumulativeRewardIntervalErrorCode.BeginEpochMustBeLessThanEndEpoch,
|
||||
poolId,
|
||||
beginEpoch,
|
||||
endEpoch
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// sanity check begin reward
|
||||
IStructs.Fraction memory beginReward = cumulativeRewardsByPool[poolId][beginEpoch];
|
||||
if (!_isCumulativeRewardSet(beginReward)) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.CumulativeRewardIntervalError(
|
||||
LibStakingRichErrors.CumulativeRewardIntervalErrorCode.BeginEpochDoesNotHaveReward,
|
||||
poolId,
|
||||
beginEpoch,
|
||||
endEpoch
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// sanity check end reward
|
||||
IStructs.Fraction memory endReward = cumulativeRewardsByPool[poolId][endEpoch];
|
||||
if (!_isCumulativeRewardSet(endReward)) {
|
||||
LibRichErrors.rrevert(
|
||||
LibStakingRichErrors.CumulativeRewardIntervalError(
|
||||
LibStakingRichErrors.CumulativeRewardIntervalErrorCode.EndEpochDoesNotHaveReward,
|
||||
poolId,
|
||||
beginEpoch,
|
||||
endEpoch
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// compute reward
|
||||
IStructs.Fraction memory beginRatio = cumulativeRewardsByPool[poolId][beginEpoch];
|
||||
IStructs.Fraction memory endRatio = cumulativeRewardsByPool[poolId][endEpoch];
|
||||
uint256 reward = LibFractions.scaleFractionalDifference(
|
||||
endRatio.numerator,
|
||||
endRatio.denominator,
|
||||
beginRatio.numerator,
|
||||
beginRatio.denominator,
|
||||
endReward.numerator,
|
||||
endReward.denominator,
|
||||
beginReward.numerator,
|
||||
beginReward.denominator,
|
||||
memberStakeOverInterval
|
||||
);
|
||||
return reward;
|
||||
|
@@ -34,7 +34,7 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
|
||||
});
|
||||
|
||||
describe('Tracking Cumulative Rewards (CR)', () => {
|
||||
it('should set CR hen a pool is created is epoch 0', async () => {
|
||||
it('should set CR when a pool is created at epoch 0', async () => {
|
||||
await simulation.runTestAsync([], [TestAction.CreatePool], [{ event: 'SetCumulativeReward', epoch: 0 }]);
|
||||
});
|
||||
it('should set CR and Most Recent CR when a pool is created in epoch >0', async () => {
|
||||
@@ -166,7 +166,7 @@ blockchainTests.resets('Cumulative Reward Tracking', env => {
|
||||
],
|
||||
);
|
||||
});
|
||||
it('should set CR and update Most Recent CR when redelegating, plus remove the CR that is no longer depends on.', async () => {
|
||||
it('should set CR and update Most Recent CR when redelegating, plus remove the CR that it no longer depends on.', async () => {
|
||||
await simulation.runTestAsync(
|
||||
[
|
||||
TestAction.CreatePool, // creates CR in epoch 0
|
||||
|
@@ -620,7 +620,9 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
membersRewardVaultBalance: rewardsForDelegator[1],
|
||||
});
|
||||
});
|
||||
it('Should collect fees correctly when there is a payout for `currentEpochBalance` but not `nextEpochBalance`', async () => {
|
||||
it('Should collect fees correctly when re-delegating after un-delegating', async () => {
|
||||
// Note - there are two ranges over which payouts are computed (see _computeRewardBalanceOfDelegator).
|
||||
// This triggers the first range (rewards for `delegatedStake.currentEpoch`), but not the second.
|
||||
// first staker delegates (epoch 0)
|
||||
const rewardForDelegator = toBaseUnitAmount(10);
|
||||
const stakeAmount = toBaseUnitAmount(4);
|
||||
@@ -640,7 +642,6 @@ blockchainTests.resets('Testing Rewards', env => {
|
||||
);
|
||||
// this should go to the delegator
|
||||
await payProtocolFeeAndFinalize(rewardForDelegator);
|
||||
|
||||
// delegate stake ~ this will result in a payout where rewards are computed on
|
||||
// the balance's `currentEpochBalance` field but not the `nextEpochBalance` field.
|
||||
await stakers[0].moveStakeAsync(
|
||||
|
Reference in New Issue
Block a user