Description
The veRAACToken::lock
function fails to validate existing active locks (minimum 1 year duration) before creating new positions. This allows users to overwrite previous locks, resulting in:
Trapped RAAC tokens - Previous locked amounts remain inaccessible but count toward totalLocked
Voting Power Duplication - New veRAAC tokens minted without burning existing balance
Protocol Metric Corruption - LockState.totalLocked
becomes artificially inflated
Proof of Concept
(1) Initial lock with maximum duration:
veRAACToken::lock(1000e18, 1460 days)
-> _lockState.totalLocked = 1000e18
-> balanceOf(user) = 1000e18 (max duration = full multiplier)
(2) Second lock after 1 day with minimum duration:
veRAACToken::lock(2000e18, 365 days)
-> Overwrites lock.amount to 2000e18
-> _lockState.totalLocked = 3000e18 (1000+2000)
-> balanceOf(user) = 1000e18 + 500e18 (2000 * 1/4)
(3) After 1 year + 1 day:
veRAACToken::withdraw() returns 2000e18 RAAC
-> Contract retains original 1000e18 RAAC
-> totalLocked remains 3000e18 (1000e18 unaccounted)
Test case to demonstrate vulnerability:
In veRAACToken.test.js
, add this test and run npx hardhat test --grep "traps funds when overwriting locks"
it("traps funds when overwriting locks", async () => {
const maxLock = ethers.parseEther("1000");
const minLock = ethers.parseEther("2000");
const maxDuration = 1460 * 24 * 3600;
const minDuration = 365 * 24 * 3600;
await veRAACToken.connect(users[0]).lock(maxLock, maxDuration);
await time.increase(86400);
await raacToken.mint(users[0].address, minLock);
await veRAACToken.connect(users[0]).lock(minLock, minDuration);
const finalLock = await veRAACToken.getLockPosition(users[0].address);
expect(finalLock.amount).to.equal(minLock);
await time.increase(minDuration + 1);
await veRAACToken.connect(users[0]).withdraw();
const contractBalance = await raacToken.balanceOf(
await veRAACToken.getAddress()
);
expect(contractBalance).to.equal(maxLock);
await expect(
veRAACToken.connect(users[0]).withdraw()
).to.be.revertedWithCustomError(veRAACToken, "LockNotFound");
});
Impact
High Severity - Direct protocol impacts:
🔒 Users permanently lose access to overwritten RAAC deposits
📈 Inflated totalLocked
misrepresents protocol TVL
⚖️ Duplicate voting power distorts governance
📉 Protocol accounting becomes irreparable
Recommendation
contracts/core/tokens/veRAACToken.sol
function lock(...) external {
+ LockManager.Lock memory currentLock = _lockState.locks[msg.sender];
+ if (currentLock.exists && currentLock.end > block.timestamp) {
+ revert ActiveLockExists();
+ }
_lockState.createLock(msg.sender, amount, duration);
}