Core Contracts

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

Multiple Lock Creation Vulnerability in veRAACToken

Summary

The veRAACToken contract is designed as a vote-escrowed token where users lock RAAC tokens to receive voting power with time-weighted decay and boost capabilities. However, the contract currently permits the creation of multiple active lock positions for a single user via the lock() function. This behavior violates the standard ve-token model, in which each user should have only one active lock — and can lead to inconsistent voting power calculations, arithmetic overflows, and overall governance and reward distribution issues.

Vulnerability Details

  • Issue: The lock() function does not enforce a check to prevent users from creating a new lock when an existing active lock is present.

  • Expected Behavior: Users with an active lock (i.e., one that has not expired or been withdrawn) should only be able to modify their position using increase() to add additional tokens or extend() to lengthen the lock duration.

  • Current Behavior: Users can call lock() multiple times even while a previous lock is still active. This bypasses the intended single-lock invariant required by the ve-token model.

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();
// Missing check for existing active lock
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// ...
}

Impact

When multiple locks exist, this causes arithmetic overflow in the increase() function, making it unusable.

function increase(uint256 amount) external nonReentrant whenNotPaused {
// ... other code ...
_mint(msg.sender, newPower - balanceOf(msg.sender));// @audit
}

Users who create multiple locks can't use increase() function, their tokens get stuck in the contract until expiry and they must wait for lock expiry to withdraw.

POC

it.only(
"should allow users to create multiple locks", async () => {
const amount = ethers.parseEther("10");
const initialDuration = 365 * 24 * 3600; // 1 year
const extensionDuration = 180 * 24 * 3600; // 6 months
// user0 creates first lock
await veRAACToken.connect(users[0]).lock(amount, initialDuration);
// user0 creates another lock
await veRAACToken
.connect(users[0])
.lock(ethers.parseEther("10"), initialDuration);
// try to increase lock position by 1 token , this will revert with arithemetic overflow
await expect(
veRAACToken.connect(users[0]).increase(ethers.parseEther("1"))
).to.be.revertedWithPanic(
0x11
);
});

Tools Used

Manual Code Review

Recommendations

  • Enforce Single Active Lock per User:
    Modify the lock() function to check if the caller already has an active lock. If so, revert the transaction with an appropriate error message:

    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();
    // Enforce single active lock invariant
    LockManager.Lock memory userLock = _lockState.locks[msg.sender];
    if (userLock.amount > 0) {
    if (block.timestamp < userLock.end) {
    revert ExistingActiveLock("Use increase() or extend() to modify your lock");
    }
    revert WithdrawRequiredBeforeNewLock("Withdraw your expired lock first");
    }
    raacToken.safeTransferFrom(msg.sender, address(this), amount);
    // ... (proceed with lock creation logic)
    }
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!