The BaseGauge::_updateReward function is triggered on every state-changing public function (i.e. stake, withdraw, getReward). It is responsible for rewards accumulation for a particular user. However, it calculates rewards based on the user's veToken balance instead of the amount of stakedToken they have actually staked. This flaw allows users who stake fewer tokens to receive disproportionately higher rewards.
If we look at the BaseGauge::earned function:
source
Here, earned() relies on getUserWeight() to determine rewards, which is based on the gauge’s weight multiplied by the user's boost multiplier. The boost multiplier, in turn, depends on the user's veToken balance. The more veToken they have, the higher the boost multiplier.
The getRewardPerToken() function calculates the current reward accumulated:
source
Since getRewardPerToken() depends on totalSupply(), which represents the total amount of stakedToken in the contract, a larger totalSupply() results in a smaller reward accumulation per token. This allows a user with a small stakedToken balance but a high veToken balance to receive disproportionately high rewards, undermining the intended staking logic.
When user1 stakes only 1 wei into the gauge (i.e. await raacGauge.connect(user1).stake(minAmountToStake)), this is the output
When user1 stakes 2e18 (i.e. await raacGauge.connect(user1).stake(maxAmountToStake)), this is the output:
5000057869n > 1666695601n thus, with smaller stake amount the bigger is the reward.
This issue distorts the staking mechanism, allowing users to unfairly maximize their rewards without staking proportionally.
Manual Research, VSCode
Modify the reward calculation logic to factor in the actual stakedToken balance of users, ensuring a fair distribution of 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.