Summary
Users can lock their RAAC tokens and receive veRAAC tokens by invoking the veRAACToken.lock()
function. However, there is an incorrect check against MAX_TOTAL_SUPPLY
in this function, which can lead to issues for users attempting to lock their tokens.
Vulnerability Details
There is check totalSupply() + amount > MAX_TOTAL_SUPPLY
in veRAACToken.lock()
function. Here, totalSupply()
represents the total amount of minted veRAAC tokens, and amount
is the amount of RAAC tokens the user wants to lock.
The issue arises because the amount
specified by the user does not directly correspond to the amount of veRAAC tokens that will be minted. The actual amount of veRAAC tokens minted is calculated as amount * duration / MAX_LOCK_DURATION
.
This means that the amount
being checked against MAX_TOTAL_SUPPLY
is less than the actual number of veRAAC tokens that will be minted to the user. As a result, if totalSupply()
is close to MAX_TOTAL_SUPPLY
, legitimate users may be denied the ability to lock their RAAC tokens, leading to DOS scenario for those users.
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();
raacToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 unlockTime = block.timestamp + duration;
_lockState.createLock(msg.sender, amount, duration);
_updateBoostState(msg.sender, amount);
(int128 bias, int128 slope) = _votingState.calculateAndUpdatePower(
msg.sender,
amount,
unlockTime
);
uint256 newPower = uint256(uint128(bias));
_checkpointState.writeCheckpoint(msg.sender, newPower);
_mint(msg.sender, newPower);
emit LockCreated(msg.sender, amount, unlockTime);
}
Impact
When totalSupply()
approaches MAX_TOTAL_SUPPLY
, users may be unable to lock their RAAC tokens due to this inconsistency in the check, leading DOS for those users.
Tools Used
Manual Review
Recommendations
Check with the actual amount of veRAAC tokens that will be minted to the user.
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);
}