Core Contracts

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

Indefinite Extension of Delegation in function delegateBoos() in BoostController.sol

Summary

The delegateBoost function allows users to extend their delegation indefinitely by repeatedly calling it without any restrictions. This could be used to lock boosts forever, preventing natural expiry and potentially abusing rewards, governance, or other delegation benefits.

Vulnerability Details

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 Can Be Extended Indefinitely
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
emit BoostDelegated(msg.sender, to, amount, duration);
}

Expected Behavior (Without Exploit)

  1. User delegates 100 tokens → Expires in 30 days.

  2. After 30 days, the boost naturally expires.

  3. User must wait before re-delegating or meet a reset condition.

Exploit Scenario (Indefinite Extension)

  1. User delegates 100 tokens (expires in 30 days).

  2. Before expiration (e.g., on Day 29), user calls delegateBoost() again with the same parameters.

  3. The expiry resets for another 30 days.

  4. The user repeats this indefinitely, never allowing the boost to expire.

1. User Delegates Boost Initially

  • A user (msg.sender) calls delegateBoost() to delegate a boost (amount) to another address (to).

  • The function stores this delegation in userBoosts[msg.sender][to], setting:

    • amount = delegated boost value

    • expiry = block.timestamp + duration (boost expiration time)

2. Delegation is Meant to Expire After duration

  • The expected behavior is that once block.timestamp reaches expiry, the boost should expire naturally.

  • After expiration, the user should need to wait or meet specific conditions before re-delegating.

3. Exploit: User Can Repeatedly Extend Delegation Indefinitely

  • The contract does not prevent a user from calling delegateBoost() again just before the previous delegation expires.

  • Since the function overwrites the existing delegation entry (userBoosts[msg.sender][to]), it resets the expiry time.

  • The user can continuously call delegateBoost() before expiry, ensuring the delegation never expires.

4. Root Cause

  • No check ensures that delegation must expire before re-delegation is allowed.

  • No cooldown period between consecutive delegations.

  • No maximum lifetime cap on delegation extensions.

Impact

  • Perpetual Boosting: The user never loses the boost effect, potentially exploiting governance, rewards, or voting systems.

  • Unfair Advantage: If the boost provides yield farming or governance power, the user retains benefits indefinitely, bypassing natural delegation limits.

  • Storage Bloat & Inefficiency: Constant overwriting of delegation wastes gas and clogs contract execution unnecessarily.

Tools Used

Manual Review

Recommendations

Prevent Delegation Extension Before Expiry

  • if (delegation.amount > 0 && block.timestamp < delegation.expiry) revert BoostAlreadyDelegated();

Introduce a Cooldown Period After Expiry Indefinite Extension of Delegation

  • if (block.timestamp < delegation.expiry + COOLDOWN_PERIOD) revert CooldownNotElapsed();

function delegateBoost( // @audit - check if it's reasonable to delegate boost to multiple addresses for same caller
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();
// Prevent Delegation Extension Before Expiry
++ if (delegation.amount > 0 && block.timestamp < delegation.expiry) {
++ revert BoostAlreadyDelegated();
++ }
// Introduce a Cooldown Period After Expiry
++ if (block.timestamp < delegation.expiry + COOLDOWN_PERIOD)
++ revert CooldownNotElapsed();
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
emit BoostDelegated(msg.sender, to, amount, duration);
}
Updates

Lead Judging Commences

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

BoostController getter functions return stale delegation information without validating expiry, potentially misleading users and external systems about active boost values

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

BoostController getter functions return stale delegation information without validating expiry, potentially misleading users and external systems about active boost values

Support

FAQs

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

Give us feedback!