The calculateReward()
function in the FjordStaking contract assumes a linear increase in rewards between epochs, leading to inaccurate reward distributions when reward rates or staked amounts change significantly between epochs.
The FjordStaking contract is designed to distribute rewards to users who stake tokens. The reward calculation mechanism is primarily handled by the calculateReward()
function, which is called within the _redeem()
function to update users' unclaimed rewards.
The calculateReward()
function calculates rewards based on the difference in rewardPerToken
between two epochs:
This implementation assumes a linear increase in rewards between _fromEpoch
and _toEpoch
. However, this assumption can lead to significant inaccuracies if the reward rates or staked amounts change non-linearly between these epochs.
The _redeem()
function uses calculateReward()
to update unclaimedRewards
for a user:
The issue arises when a user does not claim rewards for multiple epochs, and there are significant changes in reward rates or staked amounts during this period. The linear calculation will not accurately reflect the actual rewards earned during each individual epoch.
The incorrect reward calculation can lead to an uneven distribution of rewards among stakers. Users who are aware of this issue could gain an advantage over those who are not, leading to potential manipulation of the reward system. This undermines the intended incentive structure and can result in users receiving fewer rewards than they should.
Consider the following scenario:
Alice stakes 100 tokens in epoch 1.
The reward rates for epochs 1, 2, and 3 are 10, 20, and 30 tokens respectively.
Alice doesn't claim her rewards until the end of epoch 3.
The calculateReward()
function is called in _redeem()
:
Assuming rewardPerToken[1] = 10
, rewardPerToken[2] = 30
, and rewardPerToken[3] = 60
:
However, the correct calculation should be:
In this scenario, Alice receives 5000 / PRECISION_18
tokens instead of the 6000 / PRECISION_18
tokens she should have received, resulting in a loss of 1000 / PRECISION_18
tokens.
Manual review
To address this issue, the calculateReward()
function should be modified to calculate rewards on a per-epoch basis. Here's a suggested fix:
This modification requires implementing two new functions:
getStakedAmountForEpoch(address user, uint16 epoch)
to retrieve the user's staked amount for a specific epoch.
getRewardRateForEpoch(uint16 epoch)
to get the reward rate for a specific epoch.
Additionally, the contract should be updated to maintain historical data of staked amounts and reward rates for each epoch. This will ensure accurate reward calculations regardless of when users claim their rewards.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.