Summary
The delegateBoost
function in BoostController.sol
allows a user to delegate their entire veToken
balance multiple times to different pools. This results in an inflated total boost across pools, allowing users to gain excessive benefits beyond their actual voting power.
Vulnerability Details
The issue arises because there is no tracking mechanism to ensure that a user’s delegated boost does not exceed their available veToken
balance. As a result, a user can delegate their full balance multiple times, effectively creating an inflated boost across multiple pools.
Relevant Code
function delegateBoost(
address to,
uint256 amount,
uint256 duration
) external override nonReentrant {
if (paused()) revert EmergencyPaused();
if (to == address(0)) revert InvalidPool();
if (amount == 0) revert InvalidBoostAmount();
if (duration < MIN_DELEGATION_DURATION || duration > MAX_DELEGATION_DURATION)
revert InvalidDelegationDuration();
uint256 userBalance = IERC20(address(veToken)).balanceOf(msg.sender);
if (userBalance < amount) revert InsufficientVeBalance();
UserBoost storage delegation = userBoosts[msg.sender][to];
if (delegation.amount > 0) revert BoostAlreadyDelegated();
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
emit BoostDelegated(msg.sender, to, amount, duration);
}
The contract verifies that the user has a sufficient balance before delegating, but it does not check if the same balance has already been delegated elsewhere. This enables multiple delegations that exceed the actual veToken
holdings of the user.
Impact
Boost inflation allows users to unfairly gain higher rewards or governance influence.
Pools may be misled into overestimating their boost, leading to unfair advantage distribution.
Tools Used
Manual code review
Recommended Mitigation
To prevent multiple delegations exceeding a user’s balance:
Introduce a mapping to track the total delegated amount per user.
Before delegating, check that the sum of all delegated amounts does not exceed the user’s veToken
balance.
Modify delegateBoost
to update and enforce this tracking mechanism.
Suggested Fix
mapping(address => uint256) public totalDelegatedBoost;
function delegateBoost(
address to,
uint256 amount,
uint256 duration
) external override nonReentrant {
if (paused()) revert EmergencyPaused();
if (to == address(0)) revert InvalidPool();
if (amount == 0) revert InvalidBoostAmount();
if (duration < MIN_DELEGATION_DURATION || duration > MAX_DELEGATION_DURATION)
revert InvalidDelegationDuration();
uint256 userBalance = IERC20(address(veToken)).balanceOf(msg.sender);
if (totalDelegatedBoost[msg.sender] + amount > userBalance) revert InsufficientVeBalance();
UserBoost storage delegation = userBoosts[msg.sender][to];
if (delegation.amount > 0) revert BoostAlreadyDelegated();
totalDelegatedBoost[msg.sender] += amount;
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
emit BoostDelegated(msg.sender, to, amount, duration);
}
This ensures that a user cannot delegate more than their available balance.