Core Contracts

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

Lock State Validation Missing , Risk of Token Loss

Summary

The veRAACToken contract does not verify if a user already has an active lock before allowing them to create a new lock. This oversight could result in multiple locks being created for a single user, and more critically, users will not be able to withdraw previously locked tokens, potentially leading to permanent loss of their tokens.

Vulnerability Details

In the lock() function, there is no check to see if a user already has an active lock position. Without this check, a user can accidentally or intentionally create multiple locks, which may prevent them from being able to withdraw their previously locked tokens. If users cannot properly withdraw their tokens because their state is not properly managed, they may face permanent token loss.

Relevant Code:

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();
// Do the transfer first - this will revert with ERC20InsufficientBalance if user doesn't have enough tokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// Calculate unlock time
uint256 unlockTime = block.timestamp + duration;
// Create lock position
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
// Calculate initial 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
_mint(msg.sender, newPower);
emit LockCreated(msg.sender, amount, unlockTime);
}

Impact

  • Multiple Locks Per User: Without a check to ensure a user has only one active lock, users may create multiple locks. This could lead to complications in withdrawing their tokens as the contract might not be able to manage multiple locks effectively.

  • Permanent Token Loss: Users could suffer permanent loss of their tokens if they are unable to withdraw locked tokens due to conflicting lock states. The contract might not allow withdrawing tokens locked under multiple positions, leading to unintended and irreversible loss.

  • Voting Power and Boost Calculation: Users may incorrectly accumulate voting power or boosts based on multiple locks, leading to inaccurate rewards or governance influence.

  • Security Risk: The absence of validation could be exploited by users to bypass intended limitations on the contract, resulting in potential operational errors and confusion.

Tools Used

Manual code inspection

Recommendations

Ensure that the contract checks if a user already has an active lock before allowing a new one.

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();
+ // Check if the user already has an active lock
+ LockManager.Lock memory userLock = _lockState.locks[msg.sender];
+ if (userLock.exists) revert ExistingLockDetected(); // Prevent creating a new lock if one already exists
// Do the transfer first - this will revert with ERC20InsufficientBalance if user doesn't have enough tokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// Calculate unlock time
uint256 unlockTime = block.timestamp + duration;
// Create lock position
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
// Calculate initial 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
_mint(msg.sender, newPower);
emit LockCreated(msg.sender, amount, unlockTime);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month 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.