Core Contracts

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

Single-User Overwrite of `workingSupply` in a Multi-User Pool

BoostController.sol

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

In BoostController, each call to updateUserBoost overwrites poolBoost.workingSupply with the single user’s new boost rather than accumulating. This leads to incorrect multi-user accounting for the pool’s total boost, effectively ignoring all but the last user who updated. Fixing it requires incremental addition or subtraction based on the user’s delta in boost, ensuring the final workingSupply is the sum of all user boosts.

Overview

In updateUserBoost(...), after the contract calculates the user’s new boost (newBoost), the code does:

userBoost.amount = newBoost;
...
if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
}
poolBoost.workingSupply = newBoost; // <— Overwrites entire working supply
poolBoost.lastUpdateTime = block.timestamp;

Observe that poolBoost.workingSupply is set to newBoostnot incremented or aggregated with other users’ boosts. If multiple users each call updateUserBoost for the same pool:

  1. First user sets poolBoost.workingSupply = newBoostUserA.

  2. Second user sets poolBoost.workingSupply = newBoostUserB, overwriting userA’s contribution.

  3. The final user call overwrites the entire workingSupply with only that user’s portion.

Thus, the pool is only tracking one user’s boost at a time, not a sum. If the protocol design intended to track the combined or accumulated boosts from many users, that is not happening—leading to inaccurate or minimal “working supply.”

Impact

  1. Incorrect Multi‑User Summations
    A real gauge or multi‑user scenario demands all participants’ boosts be aggregated so the pool’s total “working supply” or “boost power” is the sum of each user’s new boost. Instead, the contract only ends with whichever user last called updateUserBoost.

  2. Under-Rewarding or Over-Rewarding
    Because the pool is not tracking the total of all user boosts, any logic (in the protocol or a referencing gauge) that depends on poolBoost.workingSupply or poolBoost.totalBoost can be drastically wrong. Possibly only one user is recognized, or if the code tries to rely on workingSupply for reward distribution, it will be inaccurate.

  3. Expected Behavior Not Realized
    The documentation indicates a “shared pool” concept with many users delegating or boosting. This single-user overwrite breaks typical multi-user designs and undermines the entire concept of pooled boosts.

PoC / Demonstration

A minimal scenario:

  1. User A has an old boost of 0, calls updateUserBoost(A, pool) → newBoost = 100.

    • The function sets poolBoost.workingSupply = 100.

  2. User B calls updateUserBoost(B, pool) → newBoost = 200.

    • The code sets poolBoost.workingSupply = 200, discarding user A’s 100.

    • The final “workingSupply” is 200, ignoring user A’s contribution.

Hence if the system was supposed to track “300 total” for A+B, it only sees 200. The final user call overwrote the entire working supply.

Recommendation

  1. Aggregate workingSupply
    Instead of poolBoost.workingSupply = newBoost, do something like:

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

    This incrementally adjusts the pool’s supply by the user’s difference in boost from the old to new value—accumulating all users’ contributions.

  2. Multi‑User Testing
    Thoroughly test with multiple users to confirm that poolBoost.workingSupply accurately sums user boosts (like user A has 100, B has 200 → total 300).

  3. Clarify If “One User Per Pool”
    If the design truly intended only one user to boost each pool, then it must be documented. But the code strongly suggests a multi-user model, so the current approach is contradictory.

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!