Core Contracts

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

Users will get wrong Reward Amounts Due to Ignoring Boosts in BaseGauge contract

Summary

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.

Vulnerability Details

The problem starts with how rewards are split up. The contract uses two main functions:

  1. How Much Each Token Earns (getRewardPerToken)

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

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):

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

This multiplies a user’s “weight” (getUserWeight) by the reward per token to figure out their share.

Here’s where the boost comes in

function getUserWeight(address account) public view virtual returns (uint256) {
uint256 baseWeight = _getBaseWeight(account);
return _applyBoost(account, baseWeight);
}

getUserWeight can give users a boost (like 2x their normal weight) based on extra rules.

The issue is:

  • 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.


Example to Show the Problem

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).

Reward Per Token:

getRewardPerToken increases by:

(604800 * (600 / 604800) * 1e18) / 200 = 3e18

Each staked token gets a reward value of 3e18.

User Rewards:

  • 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.

Impact

  1. When boosts are high (like 2x), the contract gives out more tokens than it has, running out of rewards and possibly crashing.

  2. If boosts are low (e.g., 0.5x), it gives out less than planned, leaving tokens unclaimed and users upset.

  3. Users with big boosts get way more than they should compared to others, making the reward system uneven and confusing.

Tools Used

Manual Review

Recommendations

  1. 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:

  2. Change getRewardPerToken to use totalBoostedWeight

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!