Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

`_applyBoost` math is flawed, messing rewards accounting

Summary

_applyBoost math is flawed, messing rewards accounting

Vulnerability Details

User rewards are calculated with earned, where it calls getUserWeight in order to calculate how much rewards each individual user should get

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/gauges/BaseGauge.sol#L583

function earned(address account) public view returns (uint256) {
// 10k * (rewardPerTokenGlobal - rewardPerTokenUser) / 1e18
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}
function getUserWeight(address account) public view virtual returns (uint256) {
uint256 baseWeight = _getBaseWeight(account);
return _applyBoost(account, baseWeight);
}

Where after getting the user weight we apply a boost (I know _getBaseWeight doesn't work, but that's another bug). Where inside the boost we perform the flawed math. More precisely the return is flawed as it will return in 4 decimals (boost order of magnitude), instead of 18 (weight order of magnitude) - return (baseWeight * boost) / 1e18;

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/gauges/BaseGauge.sol#L229

function _applyBoost(address account, uint256 baseWeight) internal view virtual returns (uint256) {
if (baseWeight == 0) return 0;
IERC20 veToken = IERC20(IGaugeController(controller).veRAACToken());
uint256 veBalance = veToken.balanceOf(account);
uint256 totalVeSupply = veToken.totalSupply();
// Create BoostParameters struct from boostState
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost,
minBoost: boostState.minBoost,
boostWindow: boostState.boostWindow,
totalWeight: boostState.totalWeight,
totalVotingPower: boostState.totalVotingPower,
votingPower: boostState.votingPower
});
// 10k + (veBalance * 1e18 / totalVeSupply) * 15k / 1e18
uint256 boost = BoostCalculator.calculateBoost(
veBalance,
totalVeSupply,
params
);
// baseWeight * 10k / 1e18
return (baseWeight * boost) / 1e18;
}

Example:

  1. User weight is 100e18

  2. BoostCalculator.calculateBoost calcualtes his boost to be 12k

  3. The return will math out -> (baseWeight * boost) / 1e18 = 100e18 * 12000 / 1e18 = 1.2e6

  4. getUserWeight will get a tiny weight and when that weight is used in earn the following will occur

// 1.2e6 * (0.1e18) / 1e18 = 0.12e6 rewards
return (getUserWeight(account) * (getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18

Impact

Users receive orders of magnitude less rewards, due to a wrong formula inside _applyBoost

Tools Used

Manual review

Recommendations

Change the math to:

- return (baseWeight * boost) / 1e18;
+ return (baseWeight * boost) / 10000;
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

BaseGauge reward calculations divide by 1e18 despite using 1e4 precision weights, causing all user weights to round down to zero and preventing reward distribution

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!