Missing Total Locked Amount Validation in veRAACToken
Description
The veRAACToken contract has a critical oversight in its lock()
function where it fails to validate that the new lock amount won't exceed MAX_TOTAL_LOCKED_AMOUNT
. While the contract correctly initializes this limit in _initializeLockParameters()
as 1B tokens, the lock()
function only checks the individual lock amount limit (MAX_LOCK_AMOUNT
) but not the total locked amount limit.
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);
Impact
Protocol's total lock limit can be exceeded
Economic model assumptions about maximum locked tokens could be violated
Could lead to unexpected behavior in boost calculations if total exceeds expected maximum
Protocol governance could be affected if voting power calculations assume a maximum total locked amount
Proof of Concept
The following test demonstrates how the total locked amount limit can be exceeded:
function testExceedTotalLockedLimit() public {
uint256 MAX_TOTAL_LOCKED = 1_000_000_000e18;
address[] memory users = new address[](3);
for(uint i = 0; i < 3; i++) {
users[i] = address(uint160(i + 1));
deal(address(raacToken), users[i], 400_000_000e18);
vm.startPrank(users[i]);
raacToken.approve(address(veRAACToken), 400_000_000e18);
veRAACToken.lock(400_000_000e18, 365 days);
vm.stopPrank();
}
assertEq(_lockState.totalLocked, 1_200_000_000e18);
}
function testBoostCalculationWithExceededTotal() public {
testExceedTotalLockedLimit();
address newUser = address(0x999);
deal(address(raacToken), newUser, 1000e18);
vm.startPrank(newUser);
raacToken.approve(address(veRAACToken), 1000e18);
veRAACToken.lock(1000e18, 365 days);
(uint256 boost, uint256 boostedAmount) = veRAACToken.calculateBoost(newUser, 1000e18);
vm.stopPrank();
}
Fix Recommendation
Add total locked amount validation in the lock function:
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();
if (_lockState.totalLocked + amount > MAX_TOTAL_LOCKED_AMOUNT)
revert TotalLockedExceedsLimit();
raacToken.safeTransferFrom(msg.sender, address(this), amount);
}
error TotalLockedExceedsLimit();
The fix should also be applied to the increase()
function since it also increases the total locked amount:
function increase(uint256 amount) external nonReentrant whenNotPaused {
if (_lockState.totalLocked + amount > MAX_TOTAL_LOCKED_AMOUNT)
revert TotalLockedExceedsLimit();
}
Tools Used