Core Contracts

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

Lack of Duplicate Delegation Check in `delegateBoost` Function

Summary

Context: BoostController.sol#212-L235

The delegateBoost function in BoostController allows users to delegate their boost to another address. However, while the function prevents a user from delegating to the same recipient multiple times, it does not check if the user has already delegated their boost to any other recipient.

This allows a single user to repeatedly delegate their boost to multiple recipients without revoking or reducing their original boost. This breaks the intended Curve-style boost mechanics, which assume a user’s boost can only be used or delegated once at any given time.

Vulnerability Details

The delegateBoost function only checks whether the sender has already delegated to the same recipient but does not check if the sender has delegated to other recipients. As a result, a single user can delegate their boost to multiple recipients, bypassing the intended boost allocation logic.

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

Exploitation Scenario:

  1. Alice has 1000 veToken and calls delegateBoost(Bob, 1000, 30 days).

  2. Alice then calls delegateBoost(Carol, 1000, 30 days), and again for David, Eve, etc.

  3. As a result, multiple recipients benefit from the same 1000 boost amount, creating an unfair advantage.

Impact

  • Boost Inflation: A user can repeatedly delegate the same boost amount to multiple recipients, leading to an artificial increase in the total effective boost.

  • Reward Manipulation: Attackers can exploit this issue to unfairly claim additional rewards that they are not entitled to.

  • Governance Abuse: If boost delegation influences voting power, this vulnerability could allow users to manipulate governance decisions.

  • Protocol Integrity Risk: The protocol assumes that a user’s boost is either used by themselves or delegated to one recipient at a time. This flaw breaks that assumption, potentially leading to unforeseen economic imbalances.

Tools Used

Manual code review

Recommendations

To fix this issue, introduce a mapping variable userTotalDelegated to ensure a user cannot delegate more than their available veToken balance.

Update Total Delegated Amount When delegating boost:

mapping(address => uint256) public userTotalDelegated;
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);
// Ensure total delegated amount does not exceed user's balance
if (userTotalDelegated[msg.sender] + amount > userBalance) {
revert InsufficientVeBalance();
}
// Update total delegated amount
userTotalDelegated[msg.sender] += amount;
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);
}

Update Total Delegated Amount When delegating boost:

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;
// Reduce user's total delegated amount
userTotalDelegated[from] -= delegation.amount;
emit DelegationRemoved(from, msg.sender, 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.