Core Contracts

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

Broken Delegation System Due to Incorrect Pool Address Handling

Summary

The boost delegation system in the BoostController::delegateBoost and BoostController::delegateBoost has a critical design flaw where delegations are not associated with specific pools, and delegation removal incorrectly uses recipient addresses as pool addresses. This breaks the boost accounting system and leads to incorrect state updates.

The BoostController::delegateBoost also allow double delegation.

Vulnerability Details

The issue arises because:

In the BoostController::delegateBoost there is no reference to the pool that will be used to delegate the boost to the recipient.

function delegateBoost(
address to,
uint256 amount,
uint256 duration
) external override nonReentrant {
// ... checks ...
UserBoost storage delegation = userBoosts[msg.sender][to];
//@audit no pool to delegate to the to address.
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
//@audit MISSING: Should reduce delegator's available boost
// MISSING: Should track delegated amounts
}

msg.sender is the delegation recipient (user address), poolBoosts mapping expects a valid pool address, this will updates metrics on non-existent or wrong pool.

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();
// @audit : uses msg.sender (delegation recipient) as pool address
PoolBoost storage poolBoost = poolBoosts[msg.sender];
if (poolBoost.totalBoost >= delegation.amount) {
poolBoost.totalBoost -= delegation.amount;
}
if (poolBoost.workingSupply >= delegation.amount) {
poolBoost.workingSupply -= delegation.amount;
}
}

The issues are connected because:

  1. Delegations aren't associated with specific pools and the pool metrics are updated using wrong addresses

  2. System can not track pool-specific delegations because there is not pool associted to the boost

Impact

  • Broken Delegation-Pool Association: since delegations aren't linked to any pool, there is no way to track which pool's boost is being delegated and then the system will not be able to properly manage pool-specific delegations and will not also be able to enforce pool-specific boost limits. which will create disconnection between delegation and pool accounting systems

  • Protocol Functionality Impact:
    Delegation system becomes effectively isolated from pool system and can't enforce pool-specific boost rules on delegations, therefore , there is no way to properly account for delegated boost in pool metrics this breaks intended boost delegation mechanics.

    function delegateBoost(address to, uint256 amount, uint256 duration) external {
    // ... checks ...
    UserBoost storage delegation = userBoosts[msg.sender][to]; // No pool association
    delegation.amount = amount;
    // No pool boost updates
    }
  • Unable to Remove Delegations Properly:

    poolBoosts[msg.sender] will always be empty/uninitialized, so no actual pool boost reduction occurs

Tools Used

  • Manual code review

Recommendations

Redesign the delegation system to properly reference pools addding the pool address to the params in the delegateBoost function:

// Add pool parameter to track which pool the delegation is for
function delegateBoost(
address to,
+ address pool,
uint256 amount,
uint256 duration
) external override nonReentrant {
if (paused()) revert EmergencyPaused();
if (to == address(0)) revert InvalidPool();
if (!supportedPools[pool]) revert PoolNotSupported();
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();
// Store delegation with pool reference
UserBoost storage delegation = userBoosts[msg.sender][pool];
if (delegation.amount > 0) revert BoostAlreadyDelegated();
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
// Update pool boost totals
+ PoolBoost storage poolBoost = poolBoosts[pool];
+ poolBoost.totalBoost += amount;
+ poolBoost.workingSupply += amount;
+ poolBoost.lastUpdateTime = block.timestamp;
// add a logic to stop delegator to be able to delegate if they already delegated all of their tokens
// to pevent double spending
emit BoostDelegated(msg.sender, to, pool, amount, duration);
}
function removeBoostDelegation(address from, address pool) external override nonReentrant {
if (!supportedPools[pool]) revert PoolNotSupported();
UserBoost storage delegation = userBoosts[from][pool];
if (delegation.delegatedTo != msg.sender) revert DelegationNotFound();
if (delegation.expiry > block.timestamp) revert InvalidDelegationDuration();
// Update correct pool's boost totals
PoolBoost storage poolBoost = poolBoosts[pool];
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, pool, delegation.amount);
delete userBoosts[from][pool];
}
function removeBoostDelegation(address from, address pool) external override nonReentrant {
if (!supportedPools[pool]) revert PoolNotSupported();
UserBoost storage delegation = userBoosts[from][pool];
if (delegation.delegatedTo != msg.sender) revert DelegationNotFound();
if (delegation.expiry > block.timestamp) revert InvalidDelegationDuration();
// Update correct pool's boost totals
+ PoolBoost storage poolBoost = poolBoosts[pool];
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, pool, delegation.amount);
delete userBoosts[from][pool];
}
Updates

Lead Judging Commences

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

BoostController's delegation system fundamentally broken due to missing pool associations, treating recipient addresses as pools and never properly updating pool boost metrics

Support

FAQs

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