Core Contracts

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

The working supply of a pool is updated incorrectly when updating user's boost in a pool

Summary

In BoostController.sol, the boost value for a user in a specific pool can be updated via updateUserBoost()function. However, in this function, the total working supply of the respective pool is updated to be equals to the user's new boost.

Vulnerability Details

function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
if (paused()) revert EmergencyPaused();
if (user == address(0)) revert InvalidPool();
if (!supportedPools[pool]) revert PoolNotSupported();
UserBoost storage userBoost = userBoosts[user][pool];
PoolBoost storage poolBoost = poolBoosts[pool];
uint256 oldBoost = userBoost.amount;
// Calculate new boost based on current veToken balance
uint256 newBoost = _calculateBoost(user, pool, 10000); // Base amount
userBoost.amount = newBoost;
userBoost.lastUpdateTime = block.timestamp;
// Update pool totals safely
if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
}
poolBoost.workingSupply = newBoost; // Set working supply directly to new boost
poolBoost.lastUpdateTime = block.timestamp;
emit BoostUpdated(user, pool, newBoost);
emit PoolBoostUpdated(pool, poolBoost.totalBoost, poolBoost.workingSupply);
}

As seen in the updateUserBoost()above, _calculateBoost()is called in line 11 to determine the user's new boost. This function will take the user's base amount multiplied by the time weighted boost via BoostCalculator.calculateTimeWeightedBoost()to return boosted amount of the user.

Then, in line 22, the pool's working supply is updated to equal to newBoost. This is incorrect as the pool-wide working supply is now only accounting for a single user's boosted amount.

Additionally, this value of poolBoost.workingSupplyis checked for in removeBoostDelegation():

function removeBoostDelegation(address from) external override nonReentrant {
UserBoost storage delegation = userBoosts[from][msg.sender];
if (delegation.delegatedTo != msg.sender) revert DelegationNotFound();
if (delegation.expiry > block.timestamp) revert InvalidDelegationDuration();
// Update pool boost totals before removing delegation
PoolBoost storage poolBoost = poolBoosts[msg.sender];
if (poolBoost.totalBoost >= delegation.amount) {
poolBoost.totalBoost -= delegation.amount;
}
if (poolBoost.workingSupply >= delegation.amount) {
poolBoost.workingSupply -= delegation.amount;
}
poolBoost.lastUpdateTime = block.timestamp;
emit DelegationRemoved(from, msg.sender, delegation.amount);
delete userBoosts[from][msg.sender];
}

Due to an incorrect value of pool's working supply, the delegation amount will also not be accounted for correctly.

Impact

Tracking of the pool boost metrics is inherently incorrect and constantly and unneccesarily updates each time a user's boost is updated. This will then affect the basis of pool-specific boost calculations.

Tools Used

Manual

Recommendations

When updating a user's boost, ensure the user's old boosted amount is deducted from pool's working supply, then adds the user's new boost.

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!