Core Contracts

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

Overwriting Existing Locks in veRAACToken Leads to Lost Funds and Incorrect Power Distribution

Summary

The veRAACToken contract's locking mechanism incorrectly handles new lock creation when a user already has an existing lock. Instead of rejecting the transaction or merging the locks, it silently overwrites the existing lock, leading to lost funds, incorrect total locked amounts, and permanently unminted voting power.

Vulnerability Details

The veRAACToken contract implements a vote-escrowed token system where users can lock their RAAC tokens to receive veRaac. The longer the lock duration, the more voting power they receive. This is done through the lock function:

function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
// ... validation checks ...
raacToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 unlockTime = block.timestamp + duration;
/* @audit-issue : this will overwrite any previous lock for that user */
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
// Calculate initial voting power
(int128 bias, int128 slope) = _votingState.calculateAndUpdatePower(msg.sender, amount, unlockTime);
}

The issue occurs in the LockManager library's createLock function:

function createLock(LockState storage state, address user, uint256 amount, uint256 duration)
internal
returns (uint256 end)
{
// ... duration validation ...
end = block.timestamp + duration;
// Directly overwrites any existing lock
state.locks[user] = Lock({amount: amount, end: end, exists: true});
state.totalLocked += amount;
emit LockCreated(user, amount, end);
return end;
}
  • When a user calls lock while having an existing lock, the function simply overwrites the old lock,the previous lock's amount and duration are completely discarded and no check is performed to see if a lock already exists

For example:

  1. User locks 100 RAAC for 1 year

    • totalLocked = 100

    • Voting power minted based on 100 RAAC

  2. User calls createLock again with 50 RAAC for 6 months

    • Previous 100 RAAC are lost

    • totalLocked = 150

    • Original voting power remains

    • New voting power is minted for 50 RAAC

Impact

  1. Users lose their previously locked tokens

  2. Protocol's totalLocked accounting becomes inflated

  3. Excess voting power remains in circulation forever

  4. System-wide voting power calculations become inaccurate

Tools Used

  • Manual Review

  • Code Analysis

Recommendations

  • Add existence check and revert if user already has a lock:

function createLock(LockState storage state, address user, uint256 amount, uint256 duration)
internal
returns (uint256 end)
{
+ if (state.locks[user].exists) revert LockAlreadyExists();
end = block.timestamp + duration;
state.locks[user] = Lock({amount: amount, end: end, exists: true});
state.totalLocked += amount;
emit LockCreated(user, amount, end);
return end;
}
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.