Core Contracts

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

MAX_TOTAL_SUPPLY enforcement can be bypassed allowing users to mint unlimited tokens

Summary

The veRAACToken::increase function allows users to increase their locked amount of RAAC tokens without enforcing the MAX_TOTAL_SUPPLY restriction, unlike veRAACToken::lock. This omission enables users to bypass the intended total supply cap, leading to an inflation of veRAAC tokens beyond the protocol’s designed limit. As a result, malicious users can manipulate governance voting power, unfairly claim more rewards, and destabilize the protocol's tokenomics.

Vulnerability Details

veRAACToken::increase allows a user with an existing lock to increase the amount they have locked. See below:

/**
* @notice Increases the amount of locked RAAC tokens
* @dev Adds more tokens to an existing lock without changing the unlock time
* @param amount The additional amount of RAAC tokens to lock
*/
function increase(uint256 amount) external nonReentrant whenNotPaused {
// Increase lock using LockManager
_lockState.increaseLock(msg.sender, amount);
_updateBoostState(msg.sender, locks[msg.sender].amount);
// Update voting power
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount + amount,
userLock.end
);
// Update checkpoints
uint256 newPower = uint256(uint128(newBias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Transfer additional tokens and mint veTokens
raacToken.safeTransferFrom(msg.sender, address(this), amount);
_mint(msg.sender, newPower - balanceOf(msg.sender));
emit LockIncreased(msg.sender, amount);
}

For a user to increase a lock, they need to first create a lock with veRAACToken::lock:

/**
* @notice Creates a new lock position for RAAC tokens
* @dev Locks RAAC tokens for a specified duration and mints veRAAC tokens representing voting power
* @param amount The amount of RAAC tokens to lock
* @param duration The duration to lock tokens for, in seconds
*/
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));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Mint veTokens
_mint(msg.sender, newPower);
emit LockCreated(msg.sender, amount, unlockTime);
}

There is a max supply implemented to set a maximum supply amount restriction on veRAACToken mints to ensure that the totalSupply doesn't exceed the specified amount.

uint256 private constant MAX_TOTAL_SUPPLY = 100_000_000e18; // 100M

This restriction is implemented when a user locks tokens as seen in veRAACToken::lock but the check is not enforced in veRAACToken::increase which allows for a situation where the total supply can exceed the expected amount with malicious users calling veRAACToken::lock with a small amount and then bypassing MAX_TOTAL_SUPPLY with veRAACToken::increase.

Proof Of Code (POC)

This test was run in the veRAACToken.test.js file in the "Lock Mechanism" describe block. For a successful test, temporarily set the MAX_TOTAL_SUPPLY variable to public visibility in veRAACToken.sol.

uint256 public constant MAX_TOTAL_SUPPLY = 100_000_000e18; // 100M

Also, increase the total amount of signers in the hardhat.config.cjs file as follows:

networks: {
hardhat: {
mining: {
auto: true,
interval: 0,
},
forking: {
url: process.env.BASE_RPC_URL,
},
chainId: 8453,
gasPrice: 120000000000, // 120 gwei
allowBlocksWithSameTimestamp: true,
accounts: {
count: 50,
},
},
it("no max supply check when user is increasing lock amount", async () => {
//c for testing purposes
const initialAmount = ethers.parseEther("100000000");
const initialLock = ethers.parseEther("1000");
const increaseAmount = ethers.parseEther("9999000");
const duration = 365 * 24 * 3600; // 1 year
for (const user of users.slice(0, 21)) {
await raacToken.mint(user.address, initialAmount);
await raacToken
.connect(user)
.approve(await veRAACToken.getAddress(), MaxUint256);
await veRAACToken.connect(user).lock(initialLock, duration);
await veRAACToken.connect(user).increase(increaseAmount);
}
//c get total supply
const totalSupply = await veRAACToken.totalSupply();
const maxTotalSupply = await veRAACToken.MAX_TOTAL_SUPPLY();
console.log("Total Supply: ", totalSupply);
console.log("Max Total Supply: ", maxTotalSupply);
//c the total supply will be more than the max due to the lack of total supply enforcement when a user increases their lock amount
assert(totalSupply > maxTotalSupply);
});

Impact

Voting Manipulation: Since veRAAC is used for governance, users can artificially increase their voting power beyond the intended limit, leading to unfair governance control.
Unfair Reward Distribution: Rewards may be incorrectly allocated due to an inaccurate total supply, reducing the rewards for honest users.
Economic Instability: Inflation of veRAAC tokens can devalue the token, affecting its market perception and utility.
Systemic Risk: If malicious actors continuously exploit this, it may destabilize governance mechanisms, affecting decision-making within the protocol.

Tools Used

Manual Review, Hardhat

Recommendations

To fix this vulnerability, enforce the max supply check inside veRAACToken::increase. Modify the function to verify that adding new locked tokens does not exceed MAX_TOTAL_SUPPLY before proceeding:

function increase(uint256 amount) external nonReentrant whenNotPaused {
uint256 newTotalSupply = totalSupply() + amount;
if (newTotalSupply > MAX_TOTAL_SUPPLY) revert TotalSupplyLimitExceeded(); // Enforce max supply
_lockState.increaseLock(msg.sender, amount);
_updateBoostState(msg.sender, locks[msg.sender].amount);
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState
.calculateAndUpdatePower(msg.sender, userLock.amount + amount, userLock.end);
uint256 newPower = uint256(uint128(newBias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
raacToken.safeTransferFrom(msg.sender, address(this), amount);
_mint(msg.sender, newPower - balanceOf(msg.sender));
emit LockIncreased(msg.sender, amount);
}
Updates

Lead Judging Commences

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

veRAACToken::increase doesn't check the token supply, making it possible to mint over the MAX

Support

FAQs

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

Give us feedback!