Core Contracts

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

updateUserBoost::BoostController is Public and Can Be Called by Anyone, Leading to Gas DoS Risks

Finding description and impact

The updateUserBoost function is publicly accessible and can be called by anyone. This means that:

  • A malicious actor can repeatedly call this function on behalf of another user, causing unnecessary gas fees.

  • Spamming updateUserBoost could lead to increased gas costs for users when they perform legitimate transactions.

  • The contract’s gas efficiency could be impacted if multiple users are constantly forced into unnecessary state updates.

Impact:

  • Denial-of-service (DoS) via gas exhaustion: Attackers can spam this function to make users waste gas.

  • Unwanted user experience degradation: Users will notice frequent, unexpected boosts being updated on their behalf.

  • Potential chain congestion: If an attacker automates calling updateUserBoost for multiple users, it could result in unnecessary blockchain load.

Relevant Code

The function updateUserBoost is currently external and lacks a restriction on who can call it:

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;
uint256 newBoost = _calculateBoost(user, pool, 10000);
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;
poolBoost.lastUpdateTime = block.timestamp;
emit BoostUpdated(user, pool, newBoost);
emit PoolBoostUpdated(pool, poolBoost.totalBoost, poolBoost.workingSupply);
}

Attack Scenario

  • Gas Drain Attack: A malicious bot can repeatedly call updateUserBoost(user, pool), forcing a user’s boost to be recalculated unnecessarily, draining gas.

  • User Frustration: If a user notices unexpected updates and gas fees, they may lose trust in the system.

  • Potential Network Congestion: If spammed at scale, it could lead to network bloat and unnecessary state changes.

Recommended Mitigation Steps

To prevent arbitrary external actors from calling updateUserBoost on behalf of users, restrict access:

Option 1: Restrict Calls to the User Themselves or an Authorized Role

Modify the function to allow only the user or an authorized role (e.g., MANAGER_ROLE) to call it:

function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
if (msg.sender != user && !hasRole(MANAGER_ROLE, msg.sender)) revert Unauthorized(); // Restriction added
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;
uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
uint256 newBoost = _calculateBoost(user, pool, userBalance);
userBoost.amount = newBoost;
userBoost.lastUpdateTime = block.timestamp;
if (newBoost >= oldBoost) {
poolBoost.totalBoost += (newBoost - oldBoost);
} else {
poolBoost.totalBoost -= (oldBoost - newBoost);
}
poolBoost.workingSupply = newBoost;
poolBoost.lastUpdateTime = block.timestamp;
emit BoostUpdated(user, pool, newBoost);
emit PoolBoostUpdated(pool, poolBoost.totalBoost, poolBoost.workingSupply);
}

Option 2: Add a Cooldown Period to Prevent Spamming

If the function is intended to be open-access, implement a time delay (e.g., 1 hour) using lastUpdateTime before allowing another update:

uint256 public constant BOOST_UPDATE_DELAY = 1 hours; // Set a minimum delay
function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
if (msg.sender != user && !hasRole(MANAGER_ROLE, msg.sender)) revert Unauthorized(); // Restriction
UserBoost storage userBoost = userBoosts[user][pool];
if (block.timestamp < userBoost.lastUpdateTime + BOOST_UPDATE_DELAY) revert UpdateTooFrequent(); // Time delay check
if (paused()) revert EmergencyPaused();
if (user == address(0)) revert InvalidPool();
if (!supportedPools[pool]) revert PoolNotSupported();
PoolBoost storage poolBoost = poolBoosts[pool];
uint256 oldBoost = userBoost.amount;
uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
uint256 newBoost = _calculateBoost(user, pool, userBalance);
userBoost.amount = newBoost;
userBoost.lastUpdateTime = block.timestamp;
if (newBoost >= oldBoost) {
poolBoost.totalBoost += (newBoost - oldBoost);
} else {
poolBoost.totalBoost -= (oldBoost - newBoost);
}
poolBoost.workingSupply = newBoost;
poolBoost.lastUpdateTime = block.timestamp;
emit BoostUpdated(user, pool, newBoost);
emit PoolBoostUpdated(pool, poolBoost.totalBoost, poolBoost.workingSupply);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Lack of quality

Support

FAQs

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