Core Contracts

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

Wrong precision applied to boosted amount; Users receive less rewards than they should

Summary

BaseGauge::_applyBoost returns an 1e4 boosted value instead of an 1e18 value, reducing the amount of rewards users receive.

Vulnerability Details

BaseGauge::_applyBoost() should calculate the user's boosted amount eligible for rewards.

function _applyBoost(address account, uint256 baseWeight) internal view virtual returns (uint256) {
..
uint256 boost = BoostCalculator.calculateBoost(
veBalance,
totalVeSupply,
params
);
@> return (baseWeight * boost) / 1e18;//@audit it should be divided by 1e4
}

BoostCalculator.calculateBoost function calculates the boost multiplier based on the veToken balances and min/maxBoost.
It returns a boost value in basis points.

Then, the boost is multiplied by baseWeight and divided by 1e18.
baseWeight is an 1e18 amount. The returned boosted amount returned by getUserWeight is in basis points: 1e18 * 1e4 / 1e18. (1)

The boosted amount is multiplied by getRewardPerTokenreturned value.

function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}

For simplicity let's consider userStates[account].rewardPerTokenPaid == 0 and userStates[account].rewards == 0.
The earned function can be simplified to : getUserWeight(account) * getRewardPerToken() / 1e18

Let's examine the precision of the value returned by getRewardPerToken :

function getRewardPerToken() public view returns (uint256) {
if (totalSupply() == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (
(lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalSupply()
);
}

Again, for simplicity let's suppose rewardPerTokenStored == 0.

rewardRate represents the reward amount per second, in rewardToken precision, updated when notifyRewardAmount is called.

Supposing both rewardToken and stakingToken have 18 decimals, then getRewardPerToken returns an 1e18 value : deltaTime * rewardRate * 1e18 / totalSupply(). (2).

From (1) and (2) it can be conclude that earned function returns an amount with 4 decimal precision.
Instead it should return an amount with 18 decimals.
This value is saved in state.rewards and claimed by user.

Impact

Users receive less rewards.

Tools Used

Recommendations

If both stakingToken and rewardToken used have 18 decimals, _applyBoost must be updated to return an 1e18 amount.

function _applyBoost(address account, uint256 baseWeight) internal view virtual returns (uint256) {
- return (baseWeight * boost) / 1e18;
+ return (baseWeight * boost) / 1e4;
}
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!