Core Contracts

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

Boost Delegation Doesn’t Work and Leaves Boost Power Unused

Summary

The delegateBoost function lets users give their boost power to someone else, but the contract doesn’t actually let the recipient use it. The delegated boost gets saved, but it’s ignored when calculating or applying boosts, making the delegation feature useless.

Vulnerability Details

The delegateBoost function lets one user (the delegator) give boost power to another user (the recipient).

function delegateBoost(
address to,
uint256 amount,
uint256 duration
) external override nonReentrant {
if (paused()) revert EmergencyPaused();
if (to == address(0)) revert InvalidPool();
if (amount == 0) revert InvalidBoostAmount();
if (duration < MIN_DELEGATION_DURATION || duration > MAX_DELEGATION_DURATION)
revert InvalidDelegationDuration();
uint256 userBalance = IERC20(address(veToken)).balanceOf(msg.sender);
if (userBalance < amount) revert InsufficientVeBalance();
UserBoost storage delegation = userBoosts[msg.sender][to];
if (delegation.amount > 0) revert BoostAlreadyDelegated();
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
emit BoostDelegated(msg.sender, to, amount, duration);
}
  • It checks if the delegator (msg.sender) has enough veTokens and saves the delegation in userBoosts[msg.sender][to] with:

    • amount: How much boost is given.

    • expiry: When it ends.

    • delegatedTo: Who gets it (to).

  • But that’s it, it doesn’t tell the rest of the contract to use this boost for the recipient.

The _calculateBoost function decides how much boost a user gets:

function _calculateBoost(
address user,
address pool,
uint256 amount
) internal view returns (uint256) {
if (amount == 0) revert InvalidBoostAmount();
if (!supportedPools[pool]) revert PoolNotSupported();
uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
uint256 totalSupply = IERC20(address(veToken)).totalSupply();
if (userBalance == 0 || totalSupply == 0) {
return amount;
}
// ... (boost calculation logic)
}
  • It only looks at userBalance—the veTokens the user owns themselves. It doesn’t check userBoosts[someone][user] to see if anyone delegated boost to this user.

  • Same thing in calculateBoost, which uses veToken.getVotingPower(user, block.timestamp)—still just the user’s own power.

  • The updateUserBoost function puts the boost into a pool:

    function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
    // ... (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;
    userBoost.lastUpdateTime = block.timestamp;
    if (newBoost >= oldBoost) {
    poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
    } else {
    poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
    }
    poolBoost.workingSupply = newBoost;
    // ... (events)
    }
    • newBoost comes from _calculateBoost, which ignores delegations.

    • It updates userBoosts[user][pool], but doesn’t look at userBoosts[delegator][user] to add any delegated boost.

The delegated boost gets stuck in userBoosts[msg.sender][to] and never reaches the recipient’s actual boost power. The contract has a `removeBoostDelegation function that subtracts the delegated amount from the recipient’s pool when it expires

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();
PoolBoost storage poolBoost = poolBoosts[msg.sender];
if (poolBoost.totalBoost >= delegation.amount) {
poolBoost.totalBoost -= delegation.amount;
}
if (poolBoost.workingSupply >= delegation.amount) {
poolBoost.workingSupply -= delegation.amount;
}
// ... (delete delegation)
}

This shows the delegated boost was meant to help the recipient’s pool—but since it’s never added anywhere, this subtraction either does nothing (if totals are too low) or messes up the pool unfairly.

Impact

  1. Users can delegate boost, but it doesn’t help the recipient get more rewards or power in pools. The whole point of delegation fails.

  2. People might delegate expecting it to work (since the contract lets them), then get upset when it does nothing.

  3. When delegations expire, removeBoostDelegation tries to subtract the boost from the recipient’s pool totals. Since it was never added, this could unfairly lower totals or just be ignored, depending on the numbers.

Tools Used

Manual Review

Recommendations

When delegateBoost runs, add the amount to the recipient’s userBoosts[to][pool] for a specific pool (needs a pool parameter)

function delegateBoost(address to, uint256 amount, uint256 duration, address pool) external {
// ... (existing checks)
UserBoost storage delegation = userBoosts[msg.sender][to];
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
// Apply to a pool
UserBoost storage recipientBoost = userBoosts[to][pool];
recipientBoost.amount += amount; // Add delegated boost
PoolBoost storage poolBoost = poolBoosts[pool];
poolBoost.totalBoost += amount;
poolBoost.workingSupply += amount;
}
Updates

Lead Judging Commences

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

BoostController::_calculateBoost ignores delegated veToken amounts when calculating boost, rendering the entire delegation mechanism non-functional and breaking reward distribution

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

BoostController::_calculateBoost ignores delegated veToken amounts when calculating boost, rendering the entire delegation mechanism non-functional and breaking reward distribution

Support

FAQs

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

Give us feedback!