Core Contracts

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

Any user can boost any pool infinitely

Summary

Any given user can boost a pool infinitely due to some missing sanity checks and access control.

Vulnerability Details

The updateUserBoost() function inside BoostController is used to "update the boost value for a user in a specific pool". The function has no access control and any user can call it with any arbitrary user/pool combo:

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);
}

The issue is that any user can pass an arbitrary nAddress/pool combo as long as the address is not 0 and the pool is supported. If the passed user address has no veRAAC balance, the _calculateBoost() function will default to returning amount as newBoost:

function _calculateBoost(
address user,
address pool,
uint256 amount
) internal view returns (uint256) {
if (amount == 0) revert InvalidBoostAmount();
if (!supportedPools[pool]) revert PoolNotSupported();
// Get current weights without modifying state
(uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower) = updateTotalWeight();
uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
uint256 totalSupply = IERC20(address(veToken)).totalSupply();
if (userBalance == 0 || totalSupply == 0) {
return amount;
}
...
}

Since the initial function passes 10_000 as amount, the pool will be boosted with that amount. The user can repeat this call with any arbitrary address (regardless if it's a user of the protocol or not) for a supported pool and boost it with 10k each time.

Impact

Any user can boost a pool infinitely by passing random addresses and a supported pool which will artificially bring up the reward yield of the pool. A side impact is that a user can also specify to boost a pool with the address of a user from the protocol which might not want to boost that pool but instead use their boost on another pool.

Tools Used

Manual Review

Recommendations

At the least implement access control so that only Alice can boost a pool with her address and not other people for her.

Updates

Lead Judging Commences

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

BoostController::updateUserBoost lacks caller validation, allowing anyone to force delegation of any user's boost to any pool without consent, hijacking voting power

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

BoostController::updateUserBoost lacks caller validation, allowing anyone to force delegation of any user's boost to any pool without consent, hijacking voting power

Support

FAQs

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

Give us feedback!