Core Contracts

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

Incorrect `MAX_TOTAL_SUPPLY` check in the `lock` function of `veRAACToken` could harm locking functionality

Description

The lock function in veRAACToken contains an incorrect check for MAX_TOTAL_SUPPLY. Currently, the function compares totalSupply() + amount against MAX_TOTAL_SUPPLY, which is incorrect because the amount of veRAAC tokens minted depends on the lock duration.

This flawed check can unnecessarily revert valid transactions, preventing users from locking their RAAC tokens even when the actual veRAAC mint amount does not exceed MAX_TOTAL_SUPPLY.

For example:

  • Assume MAX_TOTAL_SUPPLY = 100_000_000e18

  • Assume totalSupply = 99_999_500e18

  • Bob tries to lock 1_000e18 for 2 years

  • The function incorrectly reverts, even though the actual mint amount is only 500e18, which should be allowed

Context

Impact

Low. This issue does not compromise security but could disrupt locking functionality.

Likelihood

Medium. The incorrect check is always present, but it only affects users in specific scenarios (i.e., when totalSupply() is close to MAX_TOTAL_SUPPLY).

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "../../lib/forge-std/src/Test.sol";
import {Governance, IGovernance} from "../../contracts/core/governance/proposals/Governance.sol";
import {TimelockController} from "../../contracts/core/governance/proposals/TimelockController.sol";
import {RAACToken} from "../../contracts/core/tokens/RAACToken.sol";
import {veRAACToken, IveRAACToken} from "../../contracts/core/tokens/veRAACToken.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract GovernanceTest is Test {
// Contracts
Governance governance;
TimelockController timelock;
RAACToken raac;
veRAACToken veRaac;
// Actors
address OWNER = makeAddr("owner");
address PROPOSER = makeAddr("proposer");
address EXECUTOR = makeAddr("executor");
address USER = makeAddr("user");
function setUp() public {
vm.startPrank(OWNER);
// Set proposers and executors
address[] memory proposers = new address[](1);
proposers[0] = PROPOSER;
address[] memory executors = new address[](1);
executors[0] = EXECUTOR;
// Deploy governance contracts
timelock = new TimelockController(2 days, proposers, executors, OWNER);
raac = new RAACToken(OWNER, 0, 0);
veRaac = new veRAACToken(address(raac));
governance = new Governance(address(veRaac), address(timelock));
// Grant proposer role to governance
timelock.grantRole(timelock.PROPOSER_ROLE(), address(governance));
timelock.grantRole(timelock.EXECUTOR_ROLE(), address(governance));
timelock.grantRole(timelock.CANCELLER_ROLE(), address(governance));
vm.stopPrank();
}
function test_wrongMaxTotalSupplyCheck_lock() public {
// Reach a total supply of 99_999_500e18 veTokens
uint256 lockAmount;
for (uint256 i; i < 10; ++i) {
address user = makeAddr(string.concat("user", Strings.toString(i)));
lockAmount = i == 9 ? 9_999_500e18 : 10_000_000e18; // MAX_LOCK_AMOUNT
deal(address(raac), user, lockAmount);
vm.startPrank(user);
raac.approve(address(veRaac), lockAmount);
veRaac.lock(lockAmount, veRaac.MAX_LOCK_DURATION());
vm.stopPrank();
}
console.log(veRaac.totalSupply());
// Assert that locking 1,000 RAAC for 2 years reverts, although it shouldn't
lockAmount = 1_000e18;
uint256 lockDuration = veRaac.MAX_LOCK_DURATION() / 2;
deal(address(raac), USER, lockAmount);
vm.startPrank(USER);
raac.approve(address(veRaac), lockAmount);
vm.expectRevert(IveRAACToken.TotalSupplyLimitExceeded.selector);
veRaac.lock(lockAmount, lockDuration);
vm.stopPrank();
// Assert that mint amount plus current supply does not exceed MAX_TOTAL_SUPPLY
uint256 expectedVeRaacs = veRaac.calculateVeAmount(lockAmount, lockDuration);
assertFalse(veRaac.totalSupply() + expectedVeRaacs > 100_000_000e18); // MAX_TOTAL_SUPPLY
}
}

Instructions

First, integrate Foundry by running the following commands in your terminal, in the project's root directory:

# Create required directories
mkdir out lib
# Add `forge-std` module to `lib`
git submodule add https://github.com/foundry-rs/forge-std lib/forge-std
# Create foundry.toml
touch foundry.toml

Next, configure Foundry by adding the following settings to foundry.toml:

[profile.default]
src = "contracts"
out = "out"
lib = "lib"

After that, create a foundry/ directory inside the test/ directory. Inside foundry/, create the following files:

  • GovernanceTest.t.sol

Finally, paste the provided (PoC) into GovernanceTest.t.sol and run:

forge test --mt test_wrongMaxTotalSupplyCheck_lock -vvv

Recommendation

Fix the MAX_TOTAL_SUPPLY check in the lock function by using newPower instead of amount.

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();
// Do the transfer first - this will revert with ERC20InsufficientBalance if user doesn't have enough tokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// Calculate unlock time
uint256 unlockTime = block.timestamp + duration;
// Create lock position
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
// Calculate initial voting power
(int128 bias, int128 slope) = _votingState.calculateAndUpdatePower(
msg.sender,
amount,
unlockTime
);
// Update checkpoints
uint256 newPower = uint256(uint128(bias));
+ if (totalSupply() + newPower > MAX_TOTAL_SUPPLY) revert TotalSupplyLimitExceeded();
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Mint veTokens
_mint(msg.sender, newPower);
emit LockCreated(msg.sender, amount, unlockTime);
}
Updates

Lead Judging Commences

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

Incorrect `MAX_TOTAL_SUPPLY` check in the `veRAACToken::lock/extend` function of `veRAACToken` could harm locking functionality

Support

FAQs

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