DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: high
Invalid

Ineffective Loop To Calculate rewardPerToken For Certain Epoch

Summary

Ineffective loop in FjordStaking::_checkEpochRollover because the same pendingRewardsPerToken is used for every iteration.

Vulnerability Details

function _checkEpochRollover() internal {
...;
uint256 pendingRewards = (currentBalance + totalVestedStaked + newVestedStaked) - totalStaked - newStaked - totalRewards;
uint256 pendingRewardsPerToken = (pendingRewards * PRECISION_18) / totalStaked;
totalRewards += pendingRewards;
for (uint16 i = lastEpochRewarded + 1; i < currentEpoch; i++) {
@> rewardPerToken[i] = rewardPerToken[lastEpochRewarded] + pendingRewardsPerToken;
emit RewardPerTokenChanged(i, rewardPerToken[i]);
}
...;
}

FjordStaking::_checkEpochRollover has a loop that isn't effective for more than 1 iterations. Since pendingRewardsPerToken is used for every iteration to be added in rewardPerToken[lastEpochRewarded] to produce rewardPerToken[i].

This becomes ineffective because the pendingRewards will run out after 1 epoch because it has been streamed to user's unclaimed reward. Meanwhile other staker who has staked after that 1 epoch will not receive any reward due to this miscalculation.

Example Scenario :

State:

  • pendingRewards = 50e18

  • totalStaked = 200e18

  • totalRewards = 1000e18

  • lastEpochRewarded = 3

  • currentEpoch = 6

  • rewardPerToken[3] = 10e18

Scenario:

  1. pendingRewardsPerToken = 0.25e18 (50e18 * 1e18 / 200e18)

  2. totalRewards = 1050e18 (1000e18 + 50e18)

  3. i = 4 (3 + 1)

  4. rewardPerToken[4] = 10.25e18 (10e18 rewardPerToken[3]) + 0.25e18(pendingRewardsPerToken)

  5. rewardPerToken[5] = 10.25e18 (10e18 rewardPerToken[3]) + 0.25e18(pendingRewardsPerToken)

As we can see, rewardPerToken[4] and rewardPerToken[5] has the same rewardPerToken or there is no value added at all. So the user who staked at 4 epoch will get no reward at all because rewardPerToken for 5th epoch is the same for 4th epoch with no value added.

Impact

User will not get the reward of what they staked at certain condition and this breaks the whole stake mechanism of the protocol.

Tools Used

Manual Review

Recommendations

function _checkEpochRollover() internal {
...;
uint256 pendingRewards = (currentBalance + totalVestedStaked + newVestedStaked) - totalStaked - newStaked - totalRewards;
uint256 pendingRewardsPerToken = (pendingRewards * PRECISION_18) / totalStaked;
totalRewards += pendingRewards;
+ uint256 dividedPendingRewardsPerToken = pendingRewardsPerToken / (currentEpoch - lastEpochRewarded + 1)
for (uint16 i = lastEpochRewarded + 1; i < currentEpoch; i++) {
- rewardPerToken[i] = rewardPerToken[lastEpochRewarded] + pendingRewardsPerToken;
+ rewardPerToken[i] = rewardPerToken[lastEpochRewarded] + dividedPendingRewardsPerToken;
emit RewardPerTokenChanged(i, rewardPerToken[i]);
}
...;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

nave765 Submitter
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.