Core Contracts

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

veRAACToken Lock Override Leading to Loss of Funds

Summary

The lock function in the veRAACToken contract does not check whether a user already has an existing lock. Instead of updating the existing lock by increasing the locked amount and extending the duration, it completely overrides the previous lock. This results in the loss of previously locked funds, leading to significant user losses.


Vulnerability Details

When a user calls lock(amount, duration), the function does not verify whether the user already has a lock. Instead, it blindly creates a new lock, erasing the previous lock data. Consequently, previously locked funds are lost, and users receive new veRAACTokens based only on the latest lock, disregarding prior locks.

Code Reference:

function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
if (amount == 0) revert InvalidAmount();
if (amount > MAX_LOCK_AMOUNT) revert AmountExceedsLimit();
if (totalSupply() + amount > MAX_TOTAL_SUPPLY) revert TotalSupplyLimitExceeded();
if (duration < MIN_LOCK_DURATION || duration > MAX_LOCK_DURATION)
revert InvalidLockDuration();
// Transfer tokens first to ensure balance sufficiency
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// Calculate unlock time
uint256 unlockTime = block.timestamp + duration;
// **Vulnerable Code: Overwrites Existing Lock**
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
// Calculate new voting power
(int128 bias, int128 slope) = _votingState.calculateAndUpdatePower(
msg.sender,
amount,
unlockTime
);
// Update checkpoints
uint256 newPower = uint256(uint128(bias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Mint veTokens (incorrectly ignoring prior lock)
_mint(msg.sender, newPower);
emit LockCreated(msg.sender, amount, unlockTime);
}

Exploitation Scenario:

  1. A user locks 100 tokens for 1 year.

  2. Later, the user locks an additional 50 tokens for 2 years.

  3. The lock function overrides the previous lock and only considers the 50 tokens locked for 2 years, effectively erasing the initial 100 tokens locked for 1 year.

  4. The user loses the voting power and veRAACTokens associated with the first lock, leading to loss of funds and governance influence.


Impact

  • Loss of Funds: Users who lock tokens more than once will lose their previously locked tokens.

  • Incorrect Voting Power Calculation: Users who should have accrued more voting power will only receive voting power from the latest lock.


Suggested Fixes

  1. Check for Existing Locks and Update Instead of Overriding:

    • Modify the lock function to check if a user already has a lock and increase the locked amount instead of overriding it.

    • Example Fix:

    function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
    if (amount == 0) revert InvalidAmount();
    if (amount > MAX_LOCK_AMOUNT) revert AmountExceedsLimit();
    if (totalSupply() + amount > MAX_TOTAL_SUPPLY) revert TotalSupplyLimitExceeded();
    if (duration < MIN_LOCK_DURATION || duration > MAX_LOCK_DURATION)
    revert InvalidLockDuration();
    // Transfer tokens first to ensure balance sufficiency
    raacToken.safeTransferFrom(msg.sender, address(this), amount);
    // Retrieve existing lock, if any
    LockManager.Lock storage userLock = _lockState.locks[msg.sender];
    uint256 newAmount = userLock.amount + amount;
    uint256 newUnlockTime = block.timestamp + duration;
    // Update lock instead of overriding
    _lockState.updateLock(msg.sender, newAmount, newUnlockTime);
    _updateBoostState(msg.sender, newAmount);
    // Recalculate voting power
    (int128 bias, int128 slope) = _votingState.calculateAndUpdatePower(
    msg.sender,
    newAmount,
    newUnlockTime
    );
    uint256 newPower = uint256(uint128(bias));
    _checkpointState.writeCheckpoint(msg.sender, newPower);
    _mint(msg.sender, newPower - balanceOf(msg.sender));
    emit LockUpdated(msg.sender, newAmount, newUnlockTime);
    }
  2. Introduce an increaseLockAmount Function:

    • Allow users to increase their lock amount separately without overriding the lock duration.

  3. Warn Users in the UI:

    • If fixing via contract changes is delayed, provide warnings in the UI to inform users that multiple locks will override previous ones.

Updates

Lead Judging Commences

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

veRAACToken::lock called multiple times, by the same user, leads to loss of funds

Support

FAQs

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