Summary
Wrong max total supply check in lock contract will block users from locking tokens when the max supply is not reached
Vulnerability Details
The protocols lets users lock RAAC tokens in eachange for veRAAC tokens which grant voting power. This can be done via the `veRAACToken::lock()` function.
There is a MAX_TOTAL_SUPPLY of veRAAC tokens that can be minted. There is a check in the lock function that enforces the max supply invariant:
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();
The amount is calculated by combining the totalSupply of veRAAC tokens and the deposited amount of RAAC tokens. However this is wrong since the minted amount is not equal to the deposit amount.
The amount minted is calculated later in the function by
(int128 bias, ) = _votingState.calculateAndUpdatePower(
where bias is the minted amount. bias is less than amount since it is calculated by:
uint256 initialPower = (amount * duration) / MAX_LOCK_DURATION;
bias = int128(int256(initialPower));
Hence it would always be less than amount.
Having all this in mind, totalSupply() + amount > MAX_TOTAL_SUPPLY is calculated wrong since the bias (votingPower) should be added to the totalSupply and not the amount deposited
Impact
Low, DoS
Tools Used
Manual Review
Recommendations
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(); // @audit-issue L : should be + newPower
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, ) = _votingState.calculateAndUpdatePower(
msg.sender,
amount,
unlockTime
);
// Update checkpoints
uint256 newPower = uint256(uint128(bias));
+ if (totalSupply() + newPower > MAX_TOTAL_SUPPLY) revert TotalSupplyLimitExceeded(); // @audit-issue L : should be + newPower
_checkpointState.writeCheckpoint(msg.sender, newPower);
// Mint veTokens
_mint(msg.sender, newPower);
emit LockCreated(msg.sender, amount, unlockTime);
}