In the BaseGauge contract, rewards are calculated using the total staked tokens (totalSupply()) instead of the total boosted weights, which include extra reward power some users get. This leads to giving out too many or too few rewards, making the system unfair and unbalanced.
The problem starts with how rewards are split up. The contract uses two main functions:
How Much Each Token Earns (getRewardPerToken)
This decides how much reward each staked token gets over time.
It takes the rewardRate (tokens given out per second), multiplies by how much time has passed, and divides by totalSupply(), the total number of tokens staked.
The 1e18 is a big number to keep the math precise.
2. How Much a User Gets (earned):
This multiplies a user’s “weight” (getUserWeight) by the reward per token to figure out their share.
getUserWeight can give users a boost (like 2x their normal weight) based on extra rules.
getRewardPerToken uses totalSupply() (just the staked tokens).
earned uses getUserWeight (which includes boosts).
These two don’t line up when boosts change how much users should get.
Imagine two users:
User A stakes 100 tokens, no boost (boost = 1), so getUserWeight = 100.
User B stakes 100 tokens, 2x boost, so getUserWeight = 200.
Total staked (totalSupply()) = 200 tokens.
Total boosted weight = 100 + 200 = 300.
Over 7 days (604,800 seconds), the contract wants to give out 600 tokens (rewardRate = 600 / 604,800).
getRewardPerToken increases by:
Each staked token gets a reward value of 3e18.
User A: (100 * 3e18) / 1e18 = 300 tokens.
User B: (200 * 3e18) / 1e18 = 600 tokens.
Total given out = 300 + 600 = 900 tokens.
The plan was to give out 600 tokens, but it gave out 900. This happens because
totalSupply() (200) doesn’t account for boosts, while getUserWeight does.
User B’s 2x boost means they should get more than User A, but the total reward should still be 600. Dividing by totalSupply() (200) instead of total boosted weight (300) makes the reward per token too big.
The right way should be, If it used 300 (total boosted weight):
Reward per boosted weight = (600 * 1e18) / 300 = 2e18.
User A: (100 * 2e18) / 1e18 = 200.
User B: (200 * 2e18) / 1e18 = 400.
Total = 600, which is correct.
When boosts are high (like 2x), the contract gives out more tokens than it has, running out of rewards and possibly crashing.
If boosts are low (e.g., 0.5x), it gives out less than planned, leaving tokens unclaimed and users upset.
Users with big boosts get way more than they should compared to others, making the reward system uneven and confusing.
Manual Review
Track Total Boosted Weight by adding a new variable, totalBoostedWeight, to keep track of all users’ boosted weights together and then update it when users stake or withdraw:
Change getRewardPerToken to use totalBoostedWeight
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.