Core Contracts

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

veRAACToken lock() Function Fails to Handle Existing Locks Leading to Permanent Token Lock

Summary

The veRAACToken.lock() function does not check for existing locks before creating a new one. When a user calls lock() while having tokens already locked, the new lock overwrites the existing lock data but the original locked tokens become permanently locked in the contract.

Vulnerability Details

The root cause of the issue is that the veRAACToken.lock() function lacks check for user's current active lock position. It overwrites the existing lock data.

contracts/core/tokens/veRAACToken.sol#L212C1-L244C6

function lock(uint256 amount, uint256 duration) external nonReentrant whenNotPaused {
...
// Issue: no check for existing lock, the lock position will directly updated with the new one
// User's asset transferred into the previous lock position cannot be withdraw anymore
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
...
}

The original locked tokens remain in the contract but become inaccessible because:

  • The new lock's amount only tracks the newly deposited tokens

  • The withdraw() function only returns the amount recorded in the current lock

  • There's no function to reclaim lost tokens

PoC

The following PoC demonstrates that a user locks twice but the locked and withdrawable amount will be set as the latest locked amount.

it("loses funds when creating new lock while having existing lock", async () => {
const firstAmount = ethers.parseEther("1000");
const secondAmount = ethers.parseEther("500");
const duration = 365 * 24 * 3600; // 1 year
// First lock
await veRAACToken.connect(users[0]).lock(firstAmount, duration);
// Record contract's RAAC balance after first lock
const contractBalanceAfterFirstLock = await raacToken.balanceOf(await veRAACToken.getAddress());
const reportedLockAmountAfterFirstLock = (await veRAACToken.getLockPosition(users[0].address)).amount;
console.log("RAACToken balance locked in the veRAACToken after first lock:", contractBalanceAfterFirstLock);
console.log("Reported locked amount in user's lock position after first lock:", reportedLockAmountAfterFirstLock);
expect(contractBalanceAfterFirstLock).to.equal(firstAmount);
// Create second lock (this will trap the first locked amount)
await veRAACToken.connect(users[0]).lock(secondAmount, duration);
// Check contract's actual RAAC balance vs reported locked amount in user's lock state
const contractBalanceAfterSecondLock = await raacToken.balanceOf(await veRAACToken.getAddress());
const reportedLockAmountAfterSecondLock = (await veRAACToken.getLockPosition(users[0].address)).amount;
console.log("RAACToken balance locked in the veRAACToken after second lock:", contractBalanceAfterSecondLock);
console.log("Reported locked amount in user's lock position after second lock:", reportedLockAmountAfterSecondLock);
// Contract holds both amounts but only reports the second amount
expect(contractBalanceAfterSecondLock).to.equal(firstAmount + secondAmount);
expect(reportedLockAmountAfterSecondLock).to.equal(secondAmount);
// Advance time past lock duration
await time.increase(duration + 1);
// User withdraws - they only get the second amount back
await veRAACToken.connect(users[0]).withdraw();
// Check final balances
const contractFinalBalance = await raacToken.balanceOf(await veRAACToken.getAddress());
expect(contractFinalBalance).to.equal(firstAmount); // First amount remains trapped
});

The PoC can be run by appending to the original veRAACToken.test.js test file.

Impact

The impact is high as this issue can lead to permanent loss of user funds.

Tools Used

Manual code review

Recommendations

Add a check in lock() for existing locks and revert if found.

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.