Core Contracts

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

Incorrect workingSupply Calculation in BoostController Leads to Reward Distribution Manipulation

Relevant GitHub Links

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/boost/BoostController.sol#L198

Summary

In BoostController.sol, the workingSupply value in updateUserBoost() is directly overwritten with a single user's boost value instead of aggregating the boosts of all users in the pool. This causes incorrect gauge weights and reward distribution calculations.

Vulnerability Details

The updateUserBoost() function overwrites the pool's workingSupply with a single user's boost:

function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
// ...
UserBoost storage userBoost = userBoosts[user][pool];
PoolBoost storage poolBoost = poolBoosts[pool];
uint256 oldBoost = userBoost.amount;
uint256 newBoost = _calculateBoost(user, pool, 10000); // Base amount
userBoost.amount = newBoost;
userBoost.lastUpdateTime = block.timestamp;
// Update pool totals
if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
}
poolBoost.workingSupply = newBoost; // @audit Direct overwrite instead of aggregation
// ...
}

The workingSupply is used by the GaugeController to determine gauge weights and reward distribution. By setting it to a single user's boost value instead of the sum of all users' boosts, the pool's weight becomes severely undervalued.

Impact

  1. Incorrect gauge weights leading to unfair reward distribution

  2. Only the last user to update their boost affects the pool's weight

  3. Users lose entitled rewards as their boosts are ignored

  4. Potential for manipulation by timing boost updates

  5. Protocol incentive mechanism becomes dysfunctional

Tools Used

Manual Review

Recommendations

Update the workingSupply calculation to aggregate all users' boosts:

function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
// ...
// Update pool totals safely
if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
poolBoost.workingSupply = poolBoost.workingSupply + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
poolBoost.workingSupply = poolBoost.workingSupply - (oldBoost - newBoost);
}
// ...
}
Updates

Lead Judging Commences

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

BoostController::updateUserBoost overwrites workingSupply with single user's boost value instead of accumulating, breaking reward multipliers and allowing last updater to capture all benefits

Support

FAQs

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