Core Contracts

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

Boost System Fails Due to Incorrect Pool Working Supply Calculation in BoostController

Summary

The updateUserBoost function in the BoostController contract sets the workingSupply of a pool to just one user’s boost amount instead of adding up the boosts from all users. This messes up the pool’s total working supply, which can affect how rewards or voting power are calculated in the RAAC protocol.

Vulnerability Details

in the updateUserBoost function, there’s a line of code that updates poolBoost.workingSupply:

poolBoost.workingSupply = newBoost;

Here, newBoost is the new boost amount calculated for one user based on their veToken balance.


The workingSupply is supposed to show the total effective supply of a pool after applying boosts from all users. In systems like this, a boost (up to 2.5x) increases each user’s contribution to the pool’s working supply.

For example, if User A has a boost of 15,000 and User B has a boost of 12,000, the workingSupply should be something like 27,000 (their combined effective supply). But if User B updates their boost, workingSupply becomes 12,000, forgetting User A entirely.

Since workingSupply is the "working supply with boost," it should reflect the total effective supply of the pool after all users’ boosts (up to 2.5x their base amount) are applied. Overwriting it with one user’s boost breaks this logic.
For comparison, look at how totalBoost is updated in the same function:

if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
}

This part is correct, it adds or subtracts the difference between the old and new boost for the user, keeping a running total of all boosts in the pool.
However, in the updateUserBoost function:

function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
// ... (some checks and setup)
UserBoost storage userBoost = userBoosts[user][pool];
PoolBoost storage poolBoost = poolBoosts[pool];
uint256 oldBoost = userBoost.amount;
uint256 newBoost = _calculateBoost(user, pool, 10000); // New boost for this user
userBoost.amount = newBoost;
userBoost.lastUpdateTime = block.timestamp;
// Correct update for totalBoost
if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
}
poolBoost.workingSupply = newBoost; // Wrong! Overwrites the total with one user’s boost
poolBoost.lastUpdateTime = block.timestamp;
// ... (event emissions)
}

When this runs, workingSupply only reflects the latest user’s boost, not the sum of everyone’s boosts. In a system like this, workingSupply should show the total effective boost power across all users in the pool. Every time a user updates their boost, it should adjust the total by adding or subtracting their change, not replace it entirely.

Impact

  1. The workingSupply reported for a pool (via getPoolBoost) will be incorrect, showing only the last user’s boost instead of the total. This could mess up how rewards are calculated or how much voting power the pool has.

  2. If rewards depend on workingSupply, some users might get too much or too little because the total is wrong.

Tools Used

Manual Review

Recommendations

To fix this, change how workingSupply is updated so it adds up all users’ boosts correctly, like totalBoost does.

if (oldBoost == 0) {
// First time this user updates, just add their boost
poolBoost.workingSupply = poolBoost.workingSupply + newBoost;
} else if (newBoost >= oldBoost) {
// Boost went up, add the difference
poolBoost.workingSupply = poolBoost.workingSupply + (newBoost - oldBoost);
} else {
// Boost went down, subtract the difference
poolBoost.workingSupply = poolBoost.workingSupply - (oldBoost - newBoost);
}

Replace the line poolBoost.workingSupply = newBoost; with the code. It Adds the full boost if it’s the user’s first time (no old boost) and adjusts the total up or down based on the change, keeping other users’ contributions intact.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 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.

Give us feedback!