The getRewardPerToken function does not account for total boosted balances, leading to an inflated reward per token. This causes early claimers to receive more rewards than they should, at the expense of later claimers.
The BaseGauge::getRewardPerToken calculates the current reward per token as follows:
The rewardRate (per second) is multiplied by deltaTime, scaled by 1e18 and divided by `totalSupply()`, which represents the total staked tokens.
The earned function then calculates a user’s accumulated rewards. For simplicity let's consider userStates[account].rewardPerTokenPaid == 0 and userStates[account].rewards == 0.
The earned function is simplified to userWeight * rewardPerToken / 1e18
In getUserWeight a multiplier factor is applied to user's weight such that boostedWeight >= weight,
Thus, the effective formula for calculating rewards becomes: boostedReward * rewardPerToken / 1e18.
Note: current implementation may be confusing because the boost is incorrectly applied to user's weight. Instead it should have been applied to user's staked amount in gauge to encourage liquidity provision. This issue is reported in a separate submission and mentioned here to add some clarity.
The problem lies in the calculation of rewardPerToken. When computing rewardPerToken, the contract uses the flat, non-boosted totalSupply. However, when computing a user's rewards, the boosted weight is applied. This discrepancy results in an overestimation of rewardPerToken, causing early claimers to receive disproportionately high rewards while depleting the available rewards for later claimers.
For example, assume:
gauge has 1000 reward tokens to be distributed
user A stakes 100 tokens with a 1.5x boost
user B stakes 100 tokens with no boost
Total staked (totalSupply) = 200 (but total boosted weight = 250).
The rewardPerToken is calculated as 1000 / 200 = 5. (for the entire period).
Afte the period ended, user B claims rewards:
userB rewards = 100 * 5 = 500, claiming half of the rewards, when, in fact it should have claimed only
100 * (1000 / 250) = 400.
The system does not fairly distribute rewards based on actual liquidity provision.
Users who claim earlier receive an inflated portion of the rewards pool at the expense of late claimers.
To fix this issue, getRewardPerToken should consider the total boosted weight instead of the flat totalSupply. This ensures that reward distribution aligns correctly with users' weighted contributions.
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.