Core Contracts

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

Missing PoolBoost Update in delegateBoost::BoostController.sol

Summary

delegateBoost::BoostController.sol allows a user (msg.sender) to delegate a portion of their veToken balance (amount) to a pool (to) for a specified duration, recording this in userBoosts[msg.sender][to]. However, it does not update the corresponding PoolBoost storage (poolBoosts[to]), which tracks the total boost and working supply for the pool. In contrast, removeBoostDelegation updates poolBoosts[msg.sender] (where msg.sender is the pool) by subtracting the delegated amount when a delegation is removed. This inconsistency means that delegateBoost fails to reflect the delegated boost in the pool’s aggregate state, potentially breaking the protocol’s boost tracking and application logic.

Vulnerability Details

delegateBoost::BoostController.sol

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

removeBoostDelegation::BoostController.sol

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;
}
poolBoost.lastUpdateTime = block.timestamp;
emit DelegationRemoved(from, msg.sender, delegation.amount);
delete userBoosts[from][msg.sender];
}

Root Cause

  • Omitted Update: delegateBoost records the delegation in userBoosts[msg.sender][to] but does not increment poolBoosts[to].totalBoost or poolBoosts[to].workingSupply, leaving the pool’s aggregate boost state unchanged.

  • Inconsistent Design: removeBoostDelegation updates poolBoosts[msg.sender] to reflect the removal of a delegation, implying PoolBoost tracks active boosts. The absence of a corresponding update in delegateBoost breaks this symmetry.

Impact

  • Stale Pool Boost State

  • Inconsistency with Removal: removeBoostDelegation deducts from poolBoosts[msg.sender], but without prior increments in delegateBoost, these values may already be inaccurate or zero, leading to underflow checks passing trivially or state desync

Tools Used

Manual

Recommendations

Update PoolBoost in delegateBoost:

Add poolBoosts[to] updates to reflect the delegated amount:

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;
+ PoolBoost storage poolBoost = poolBoosts[to];
+ poolBoost.totalBoost += amount;
+ poolBoost.workingSupply += amount; // Adjust based on protocol logic
+ poolBoost.lastUpdateTime = block.timestamp;
emit BoostDelegated(msg.sender, to, amount, duration);
}
Updates

Lead Judging Commences

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

BoostController removes pool boost on delegation removal without adding it on delegation creation, leading to accounting inconsistencies and potential underflows

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

BoostController removes pool boost on delegation removal without adding it on delegation creation, leading to accounting inconsistencies and potential underflows

Support

FAQs

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