Core Contracts

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

User boosts can be delegated to multiple pools/recipients simultaneously

Summary

The BoostController contract allows users to delegate their Curve-style rewards boost to pools via the delegateBoost() function. However, there is no restriction preventing a user from delegating their boost to multiple recipients simultaneously, effectively reusing the same boost multiple times.


Vulnerability Details

The function delegateBoost() in BoostController assigns boost delegation to a recipient pool in the following line:

UserBoost storage delegation = userBoosts[msg.sender][to];

This creates a new delegation entry per recipient but does not track the total amount of boost a user has already assigned.
Because of this, any user can call delegateBoost() multiple times with different to addresses, overcommitting their boost beyond what they actually have. The system currently only checks delegation per recipient but does not sum all active delegations for a user.


Impact

Severity: High

Users can exploit this loophole to delegate their boost to multiple pools at the same time, resulting in inflated rewards across multiple pools.This breaks the intended boost mechanics, leading to unfair distribution of rewards.

Attackers can abuse this to their advantage, increasing their reward share without actually holding the necessary boost allocation.


Tools Used

Manual code review


Recommendations

Implement a total delegation tracking mechanism to ensure that a user cannot delegate more boost than they actually own.

  • Firstly, add a mapping from user to delegated boost in the contract's state:

    mapping(address => uint256) public totalDelegatedAmount;

Next, modify the delegateBoost function as follows:

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 - totalDelegatedAmount[msg.sender] < amount) revert BoostAlreadyDelegated();
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;
+ totalDelegatedAmount[msg.sender] += amount;
emit BoostDelegated(msg.sender, to, amount, duration);
}

Finally, add the following line to the removeBoostDelegation function:

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);
+ totalDelegatedAmount[from] -= delegation.amount;
delete userBoosts[from][msg.sender];
}
Updates

Lead Judging Commences

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

BoostController::delegateBoost lacks total delegation tracking, allowing users to delegate the same veTokens multiple times to different pools for amplified influence and rewards

Support

FAQs

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