Core Contracts

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

Lock Function Overrides Existing Lock, Preventing Users from Withdrawing Full Amount

Summary:

The lock function in the veRAACToken contract lacks validation to prevent users from locking funds if they already have an active lock. This allows a user to override their existing lock with a new one, resulting in the previous lock's amount being lost. Consequently, the user cannot withdraw the full amount of tokens they initially locked.

Vulnerability Details:

The lock function allows users to create a new lock position. However, it does not check if the user already has an active lock. If a user calls lock multiple times, the existing lock is overwritten with the new lock's details (amount and duration). This means the tokens associated with the previous lock are effectively lost for withdrawal purposes, although the user might still receive veRAAC tokens based on the combined locked amount.

[ https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/tokens/veRAACToken.sol#L212 ]

function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
// ... other validations
_lockState.createLock(msg.sender, amount, duration); // <-- Overwrites existing lock if any
// ...
}

Impact:

  • Loss of Locked Funds: Users can lose a portion of their locked tokens, as the amount from the previous lock is not carried over when a new lock is created.

  • Withdrawal Issues: Users cannot withdraw the full amount of tokens they initially locked, as the withdraw function only considers the amount of the last lock created.

  • Incorrect veRAAC Calculation: The user might receive veRAAC tokens based on the cumulative amount locked across multiple calls to lock, even though they can only withdraw the amount from the last lock. This leads to an imbalance between veRAAC holdings and actual withdrawable RAAC.

Proof Of Concept (PoC):

  1. Alice locks 100 RAAC tokens for 365 days using the lock function.

  2. Alice, calls lock again with another 100 RAAC tokens for 365 days, to increase her lock.

  3. The LockManager.createLock function overwrites Alice's initial lock of 100 tokens with the new lock of 100 tokens. The first 100 RAAC are now lost for withdrawal.

  4. When Alice's lock expires, she calls withdraw. She only receives 100 RAAC tokens (from the second lock), not the 200 RAAC she intended to lock in total. Her 50 veRAAC tokens (calculated based on 200 RAAC) are burned.

Proof Of Code:

  1. Use this guide to intergrate foundry into your project: foundry

  2. Create a new file FortisAudits.t.sol in the test directory.

  3. Add the following gist code to the file: Gist Code

  4. Run the test using forge test --mt test_ForitsAudits_LockOverridesExsitingLock -vvvv.

function test_ForitsAudits_LockOverridesExsitingLock() public {
uint256 amount = 100e18;
uint256 duration = 365 days;
vm.startPrank(initialOwner);
raacToken.setMinter(initialOwner);
raacToken.mint(anon, amount * 2);
vm.stopPrank();
console.log("------- First Lock -------");
console.log("Alice Locks %d RAACToken for 365 days", raacToken.balanceOf(anon)/2);
vm.startPrank(anon);
raacToken.approve(address(veraacToken), amount * 2);
veraacToken.lock(amount, duration);
console.log("Alice voting power is %d", veraacToken.balanceOf(anon));
console.log("------- Second Lock -------");
console.log("Alice Locks %d RAACToken for 365 days", raacToken.balanceOf(anon));
veraacToken.lock(amount, duration);
console.log("Alice voting power is %d", veraacToken.balanceOf(anon));
skip(365 days + 1);
console.log("------- Withdraw Lock -------");
veraacToken.withdraw();
console.log("Alice voting power after withdraw %d", veraacToken.balanceOf(anon));
console.log("Alice RAACToken balance after withdraw %d (includes transfer fee)", raacToken.balanceOf(anon));
console.log("Alice Lost the first lock amount");
vm.stopPrank();
}
[PASS] test_ForitsAudits_LockOverridesExsitingLock() (gas: 659161)
Logs:
------- First Lock -------
Alice Locks 100000000000000000000 RAACToken for 365 days
Alice voting power is 25000000000000000000
------- Second Lock -------
Alice Locks 100000000000000000000 RAACToken for 365 days
Alice voting power is 50000000000000000000
------- Withdraw Lock -------
Alice voting power after withdraw 0
Alice RAACToken balance after withdraw 98800000000000000000 (includes transfer fee)
Alice Lost the first lock amount

Tools Used:

Manual code review.

Recommended Mitigation:

Add a check in the lock function to ensure that the user does not already have an active lock. If a user tries to lock tokens while they already have an active lock, revert with an appropriate error message. This prevents users from accidentally overwriting their existing locks and ensures they can withdraw the full amount of their locked tokens.

// In veRAACToken.sol
+ error ExistingLockActive();
function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
// Check if user already has an active lock
+ if (_lockState.locks[msg.sender].exists && block.timestamp < _lockState.locks[msg.sender].end) {
+ revert ExistingLockActive();
+ }
// ... other validations
_lockState.createLock(msg.sender, amount, duration);
// ...
}
Updates

Lead Judging Commences

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

Give us feedback!