Core Contracts

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

Working Supply Incorrectly Set to Individual Boost Instead of Total Supply (Accounting Error)

Link to Affected Code:

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

function updateUserBoost(address user, address pool) external override {
// ...
poolBoost.workingSupply = newBoost; // Set working supply directly to new boost
}

Description:
The updateUserBoost function incorrectly sets the pool's workingSupply to a single user's new boost amount, when according to the PoolBoost struct documentation, workingSupply should be "the total working supply including boosts". This creates an accounting error where the working supply only reflects the last user's boost instead of the cumulative total supply with all boosts.

The issue is particularly severe because:

  1. The struct explicitly defines workingSupply as "total working supply including boosts"

  2. totalBoost is properly cumulative (adds/subtracts differences)

  3. But workingSupply overwrites the entire value with just one user's boost

Impact:

  1. Pool Accounting Corruption

  • Working supply only reflects last user's boost

  • Historical boost contributions are lost

  • Creates discrepancy between totalBoost and workingSupply

  1. Protocol Calculation Errors

  • Any protocol mechanisms using workingSupply will have incorrect inputs

  • Pool statistics and metrics become unreliable

  • Potential impact on boost-based calculations

Proof of Concept:

// Initial state
User A has 1000 tokens with 1.5x boost
poolBoost.workingSupply = 1500 // (1000 * 1.5)
poolBoost.totalBoost = 500 // (+500 boost)
// User B updates with 2000 tokens and 1.25x boost
updateUserBoost(userB, pool);
poolBoost.workingSupply = 2500 // Overwrites to just B's boost
poolBoost.totalBoost = 1000 // Correctly adds B's +500 boost
// Result: workingSupply (2500) != baseSupply + totalBoost (3000)
// Lost User A's contribution completely

Recommended Mitigation:

function updateUserBoost(address user, address pool) external override {
// ... existing checks ...
UserBoost storage userBoost = userBoosts[user][pool];
PoolBoost storage poolBoost = poolBoosts[pool];
uint256 oldBoost = userBoost.amount;
uint256 newBoost = _calculateBoost(user, pool, 10000);
userBoost.amount = newBoost;
// Update both metrics consistently
if (newBoost >= oldBoost) {
uint256 increase = newBoost - oldBoost;
poolBoost.totalBoost += increase;
poolBoost.workingSupply += increase;
} else {
uint256 decrease = oldBoost - newBoost;
poolBoost.totalBoost -= decrease;
poolBoost.workingSupply -= decrease;
}
emit BoostUpdated(user, pool, newBoost);
}
Updates

Lead Judging Commences

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