Core Contracts

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

Boost Application Breaks RewardPerToken Calculation

Description:

The BaseGauge contract calculates rewardPerToken using totalSupply(), which represents the total staked tokens. However, when applying a boost during reward distribution, the relationship between rewardPerToken and totalSupply() is broken, allowing users with high boosts to claim more rewards than what should be available per staked token.

Vulnerable Code:

function getRewardPerToken() public view returns (uint256) {
if (totalSupply() == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (
(lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalSupply()
);
}
function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}

Since rewardPerToken is calculated based on totalSupply(), but rewards are distributed using getUserWeight(account), which includes a boost factor, a boosted user can receive more rewards than intended.

Steps to Reproduce:

  1. Assume totalSupply = 100 and 100 reward tokens are being distributed.

  2. The expected reward per staked token is 1 (rewardPerToken = 100 / 100 = 1).

  3. A user stakes 10 tokens but has a 10x boost applied.

  4. Their effective weight becomes 10 * 10 = 100.

  5. earned(account) multiplies by this boosted weight, giving the user 100 rewards instead of 10.

  6. No rewards remain for other users despite their stake and boost value.

Impact:

  • The total rewards distributed exceed the intended rewardPerToken calculation.

Potential Fixes:

  1. Adjust Reward Calculation to Include Boosted Total Supply: Use totalBoostedWeight instead of totalSupply() in getRewardPerToken().

  2. Normalize Boosted Weight Contribution: Scale rewards so the sum of all distributed rewards matches rewardPerToken * totalSupply().

  3. Cap the Effect of Boost in Distribution: Ensure that boosted rewards do not exceed the total available reward pool.

Recommended Fix (Example Implementation):
Modify getRewardPerToken to use the total boosted weight:

function getRewardPerToken() public view returns (uint256) {
uint256 totalBoostedWeight = getTotalBoostedWeight(); // Sum of all boosted weights
if (totalBoostedWeight == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (
(lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalBoostedWeight
);
}

This ensures that rewardPerToken correctly accounts for the boosted distribution.

Updates

Lead Judging Commences

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

BaseGauge::earned calculates rewards using getUserWeight instead of staked balances, potentially allowing users to claim rewards by gaining weight without proper reward checkpoint updates

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

BaseGauge::earned calculates rewards using getUserWeight instead of staked balances, potentially allowing users to claim rewards by gaining weight without proper reward checkpoint updates

Support

FAQs

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

Give us feedback!