Core Contracts

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

User can update pool boost without having any veRAAC token

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

It can be seen that _calculateBoost()is called with amountparameter is 10000.

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;
}
// Create parameters struct for calculation
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost,
minBoost: boostState.minBoost,
boostWindow: boostState.boostWindow,
totalWeight: totalWeight,
totalVotingPower: totalVotingPower,
votingPower: votingPower
});
(uint256 boostBasisPoints, uint256 boostedAmount) = BoostCalculator.calculateTimeWeightedBoost(
params,
userBalance,
totalSupply,
amount
);
if (boostedAmount < amount) {
return amount;
}
@> uint256 maxBoostAmount = amount * MAX_BOOST / 10000;
if (boostedAmount > maxBoostAmount) {
return maxBoostAmount;
}
return boostedAmount;
}

If user do not have any veRAAC token, it will return amount= 10000, which is significant amount, because based on this formula: maxBoostAmount = amount * MAX_BOOST / 10000;, maximum amount can be updated per call is 25000 only

Impact

Pool get free total boost, while it shouldn't be

Recommendations

Updates

Lead Judging Commences

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

BoostController::updateUserBoost uses hardcoded 10000 base amount, storing basis points instead of actual boosted amount

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

BoostController::updateUserBoost uses hardcoded 10000 base amount, storing basis points instead of actual boosted amount

Support

FAQs

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