Summary
BaseGauge::earned
incorrectly scales reward calculations by dividing by 1e18 when multiplying a BPS value with an e18 value, resulting in users receiving significantly fewer rewards than intended.
Vulnerability Details
[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/BaseGauge.sol#L585)
In BaseGauge::earned
, the final reward calculation improperly scales the rewards by dividing a BPS-scaled value by 1e18:
function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}
Example:
Initial Values:
getUserWeight(account) = 10000 (in BPS)
getRewardPerToken() = 1428571428571428192 (in e18)
userStates[account].rewardPerTokenPaid = 0
userStates[account].rewards = 0
Step by Step Calculation:
rewardDiff = getRewardPerToken() - rewardPerTokenPaid
= 1428571428571428192 - 0
= 1428571428571428192 (in e18)
weightedReward = 10000 * 1428571428571428192
= 14285714285714281920000
scaledReward = 14285714285714281920000 / 1e18
= 14285
finalReward = 14285 + 0
= 14285
Impact
Users receive significantly reduced rewards (by a factor of 1e18)
Core gauge reward mechanism is effectively broken
All reward calculations are orders of magnitude smaller than intended
Tools Used
Foundry
Recommendations
Modify the earned calculation to maintain proper e18 scaling throughout:
function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
- (getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
+ (getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 10000 // divide by BPS
) + userStates[account].rewards;
}