Core Contracts

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

MAX_TOTAL_LOCKED_AMOUN Not Enforced in `lock(...)` or `increase(...)`


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../contracts/veRAACToken.sol"; // Path to your veRAACToken contract
// A minimal mock RAAC token for testing
contract MockRAACToken is ERC20 {
constructor() ERC20("MockRAAC", "MRAAC") {
// Optionally mint a large supply for test
_mint(msg.sender, 2_000_000_000e18);
}
}
contract veRAACTokenGlobalLockTest is Test {
veRAACToken public veToken;
MockRAACToken public raacToken;
address user1 = address(0xBEEF1);
address user2 = address(0xBEEF2);
function setUp() public {
// Deploy mock RAAC token and veRAACToken
raacToken = new MockRAACToken();
veToken = new veRAACToken(address(raacToken));
// Distribute tokens to users
raacToken.transfer(user1, 1_000_000_000e18);
raacToken.transfer(user2, 1_000_000_000e18);
// Approve veRAACToken
vm.prank(user1);
raacToken.approve(address(veToken), type(uint256).max);
vm.prank(user2);
raacToken.approve(address(veToken), type(uint256).max);
}
function testExceedGlobalLockLimit() public {
// user1 locks 500M RAAC
vm.prank(user1);
veToken.lock(500_000_000e18, 365 days);
// user2 locks 600M RAAC, total locked = 1.1B > MAX_TOTAL_LOCKED_AMOUNT (1B)
// We expect a revert if the global limit was enforced, but it won't revert
vm.prank(user2);
veToken.lock(600_000_000e18, 365 days);
// If no revert => global limit is not enforced
// Optionally check some state if the contract had a totalLocked reference
// but the main point is "No revert => proof the limit isn't enforced"
}
}

Overview

The contract declares:

uint256 public constant MAX_TOTAL_LOCKED_AMOUNT = 1_000_000_000e18; // 1B
...
_lockState.maxTotalLocked = MAX_TOTAL_LOCKED_AMOUNT; // in _initializeLockParameters()

The _lockState is a LockManager.LockState, presumably storing a global count of the total RAAC locked. However, in the veRAACToken code snippet provided:

  • lock(...) checks MAX_TOTAL_SUPPLY for minted veRAAC, but does not check MAX_TOTAL_LOCKED_AMOUNT.

  • increase(...) likewise lacks any check for the global 1B lock limit, though it checks neither local nor library state for the aggregated locked amount.

If the LockManager library does not internally enforce this total locked limit, then there is no code preventing the sum of all user locks from surpassing 1_000_000_000e18. The presence of maxTotalLocked = MAX_TOTAL_LOCKED_AMOUNT in _initializeLockParameters() strongly implies it should be enforced somewhere, but no reference to an actual revert or check is present in the snippet.

Attack/Exploit Path

  1. Multiple Users Lock RAAC
    Each user calls lock(...) with large amounts. The code checks only their individual maximum (MAX_LOCK_AMOUNT) and the minted token cap (MAX_TOTAL_SUPPLY), not the global sum.

  2. Exceeding 1B
    Over time, as more users lock tokens, the total locked RAAC can exceed 1B if no underlying LockManager function reverts. Because the snippet shows no if (totalLocked + amount > MAX_TOTAL_LOCKED_AMOUNT) revert, the contract can keep accepting locks.

  3. Ignoring the maxTotalLocked
    The _lockState.maxTotalLocked field presumably is never validated, letting the global locked balance surpass the stated 1B RAAC limit.

Impact

  • Potentially Infinite Lock Growth: The protocol’s assumption of a 1B max locked RAAC can be violated, which may disrupt the economic or governance design (e.g., liquidity constraints, reward distributions, or risk profiles).

  • False Security: Other parts of the system or external integrators that rely on a “hard 1B limit” could miscalculate or fail if they assume it can’t be exceeded.

Recommended Fix

  • Check Before Every Lock
    In lock(...) and increase(...), call something like:

    if (_lockState.totalLocked + amount > MAX_TOTAL_LOCKED_AMOUNT) {
    revert ExceedsGlobalLockedLimit();
    }

    or ensure the LockManager library enforces this in createLock/increaseLock. The contract snippet must demonstrate that such a revert path actually exists, or the limit is meaningless.

  • Remove or Document
    If a 1B global limit is no longer intended, remove MAX_TOTAL_LOCKED_AMOUNT. Otherwise, ensure it is enforced at the library level or in the local code.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

veRAACToken::increase doesn't check the maximum total locked amount

`veRAACToken::lock` function doesn't check MAX_TOTAL_LOCKED_AMOUNT

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!