Summary
Each users have a Max locked amount of RAAC tokens But also the entire contract has a MAX locked amount for all users the individual max locked are enforced but the maxlocked amount is not checked hence users can locked more RAAC tokens above the max lock
Vulnerability Details
This Bypass is possible in the Lock function and the Increase Function
* @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();
@audit>> only users max >> if (amount > MAX_LOCK_AMOUNT) revert AmountExceedsLimit();
@audit >> we are check max vetooken HERE NOT MAXLOCKED>> 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);
}
* @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 {
_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);
}
Even though a user doesn't bypass the max locked per user the Max locked for everyone can still be bypass as there are no checks to prevent this
* @notice Initializes lock parameters
* @dev Sets up initial parameters for lock calculations
*/
function _initializeLockParameters() internal {
_lockState.minLockDuration = MIN_LOCK_DURATION;
_lockState.maxLockDuration = MAX_LOCK_DURATION;
@audit >> max for user
>> _lockState.maxLockAmount = MAX_LOCK_AMOUNT;
@audit >> max for contract >> _lockState.maxTotalLocked = MAX_TOTAL_LOCKED_AMOUNT;
}
* @notice Maximum amount that can be locked in a single position
*/
uint256 private constant MAX_LOCK_AMOUNT = 10_000_000e18;
* @notice Maximum total amount that can be locked globally
*/
uint256 public constant MAX_TOTAL_LOCKED_AMOUNT = 1_000_000_000e18;
From the above if we have 1B locked already and user B calls to lock
The contract will accept his deposits instead of reverting.
Checking the locked manager also this is not enforced
* @notice Creates a new lock position
* @dev Validates duration and amount, creates lock entry
* @param state The lock state storage
* @param user Address creating the lock
* @param amount Amount of tokens to lock
* @param duration Duration of the lock in seconds
* @return end The timestamp when the lock expires
*/
function createLock(
LockState storage state,
address user,
uint256 amount,
uint256 duration
) internal returns (uint256 end) {
if (state.minLockDuration != 0 && state.maxLockDuration != 0) {
if (duration < state.minLockDuration || duration > state.maxLockDuration)
revert InvalidLockDuration();
}
if (amount == 0) revert InvalidLockAmount();
end = block.timestamp + duration;
state.locks[user] = Lock({
amount: amount,
end: end,
exists: true
});
state.totalLocked += amount;
emit LockCreated(user, amount, end);
return end;
}
* @notice Increases the amount in an existing lock
* @dev Adds tokens to existing lock without changing duration
* @param state The lock state storage
* @param user Address increasing their lock
* @param additionalAmount Additional amount to lock
*/
function increaseLock(
LockState storage state,
address user,
uint256 additionalAmount
) internal {
Lock storage lock = state.locks[user];
if (!lock.exists) revert LockNotFound();
if (lock.end <= block.timestamp) revert LockExpired();
@audit>> only users max>> if (lock.amount + additionalAmount > state.maxLockAmount) revert AmountExceedsLimit();
@audit>> commented out>>
lock.amount += additionalAmount;
state.totalLocked += additionalAmount;
emit LockIncreased(user, additionalAmount);
}
Impact
Bypass of the max locked amount for the contract
Tools Used
Manual Review
Recommendations
Nest a check in the Lock and increase lock to prevent this.