Core Contracts

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

Lack of `MAX_TOTAL_SUPPLY` Check in `increase()` Leading to Supply Inflation

Summary

The increase() function in veRAACToken.sol lacks a MAX_TOTAL_SUPPLY check, which can lead to unchecked supply growth. Given that MAX_TOTAL_SUPPLY (100 million tokens) is typically 1/10 of MAX_TOTAL_LOCKED_AMOUNT (1 billion tokens), and totalSupply() is expected to be 0.25 to 1.0 times _lockState.totalLocked, this means totalSupply() could significantly exceed MAX_TOTAL_SUPPLY under certain conditions.

This dilutes users' share in key calculations and allows large players to disproportionately benefit from the system.

Vulnerability Details

Unlike lock(), the MAX_TOTAL_SUPPLY check is missing in increase() and extend():

veRAACToken.sol#L246-L305

/**
* @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);
}
/**
* @notice Extends the duration of an existing lock
* @dev Increases the lock duration which results in updated voting power
* @param newDuration The new total duration extension for the lock, in seconds
*/
function extend(uint256 newDuration) external nonReentrant whenNotPaused {
// Extend lock using LockManager
uint256 newUnlockTime = _lockState.extendLock(msg.sender, newDuration);
// Update voting power
LockManager.Lock memory userLock = _lockState.locks[msg.sender];
(int128 newBias, int128 newSlope) = _votingState.calculateAndUpdatePower(
msg.sender,
userLock.amount,
newUnlockTime
);
// Update checkpoints
uint256 oldPower = balanceOf(msg.sender);
uint256 newPower = uint256(uint128(newBias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Update veToken balance
if (newPower > oldPower) {
_mint(msg.sender, newPower - oldPower);
} else if (newPower < oldPower) {
_burn(msg.sender, oldPower - newPower);
}
emit LockExtended(msg.sender, newUnlockTime);
}

Since, both increase() and extend() could entail minting extra veTokens, not having the needed total supply limit check could violate the intended design. As a result, users can continue increasing locked RAAC tokens without constraints, inflating totalSupply() unchecked.

Now, totalSupply() is expected to be 0.25x to 1.0x of _lockState.totalLocked. However, given that MAX_TOTAL_LOCKED_AMOUNT is 10x MAX_TOTAL_SUPPLY, if unchecked, totalSupply() can exceed way beyond the intended ratio, causing severe dilution.

Impact

The unexpected behavior in tokenomics and governance, e.g. boost, calculations could lead to disproportionate voting power, i.e. users using multiple accounts could extend/consolidate their voting power unbounded whereas normal users are getting dwarfed correspondingly with their voting ability.

For example, multiple users can lock tokens up to their individual limit, thereby breaking tokenomics as these activities increasingly dilute the boost multiplier for a user's rewards long term due to totalVeSupply, the denominator that continues to grow larger than the capped MAX_TOTAL_SUPPLY. This will result in returning a smaller than intended votingPowerRatio in BoostCalculatorcalculateBoost():

BoostCalculator.sol#L86-L90

// Calculate voting power ratio with higher precision
uint256 votingPowerRatio = (veBalance * 1e18) / totalVeSupply;
// Calculate boost within min-max range
uint256 boostRange = params.maxBoost - params.minBoost;
uint256 boost = params.minBoost + ((votingPowerRatio * boostRange) / 1e18);

In extreme cases, precision issue could surface on voters with very small voting power (i.e., veBalance or point.bias) and assigned params.minBoost only.

Tools Used

Manual

Recommendations

Consider making the following change:

veRAACToken.sol#L270

// Mint veTokens
_mint(msg.sender, newPower - balanceOf(msg.sender));
+ if (totalSupply() > MAX_LOCK_AMOUNT) revert AmountExceedsLimit();

veRAACToken.sol#L297-L302

// Update veToken balance
if (newPower > oldPower) {
_mint(msg.sender, newPower - oldPower);
+ if (totalSupply() > MAX_LOCK_AMOUNT) revert AmountExceedsLimit();
} else if (newPower < oldPower) {
_burn(msg.sender, oldPower - newPower);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 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

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

Support

FAQs

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