Summary
Contract - BoostController.sol
Code snippet of delegateBoost()
function.
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);
}
Code snippet of removeBoostDelegation()
function.
function removeBoostDelegation(address from) external override nonReentrant {
UserBoost storage delegation = userBoosts[from][msg.sender];
if (delegation.delegatedTo != msg.sender) revert DelegationNotFound();
if (delegation.expiry > block.timestamp) revert InvalidDelegationDuration();
PoolBoost storage poolBoost = poolBoosts[msg.sender];
if (poolBoost.totalBoost >= delegation.amount) {
poolBoost.totalBoost -= delegation.amount;
}
if (poolBoost.workingSupply >= delegation.amount) {
poolBoost.workingSupply -= delegation.amount;
}
poolBoost.lastUpdateTime = block.timestamp;
emit DelegationRemoved(from, msg.sender, delegation.amount);
delete userBoosts[from][msg.sender];
}
Vulnerability Details
When removeBoostDelegation()
is called, it's expected to update poolBoost.totalBoost
and poolBoost.workingSupply
.
If poolBoost.totalBoost >= delegation.amount
and poolBoost.workingSupply >= delegation.amount
, then poolBoost state
will update.
But problem here is that poolBoost
will be empty object all the time.
Why? Because in delegateBoost()
function, poolBoost is not set for to
address. something like -
PoolBoost storage poolBoost = poolBoosts[to];
as to
in delegateBoost()
is equivalent to msg.sender
in removeBoostDelegation()
.
In PoolBoost storage poolBoost = poolBoosts[msg.sender];
; poolBoost will always be an empty object.
so the operation (below) will never execute.
if (poolBoost.totalBoost >= delegation.amount) {
poolBoost.totalBoost -= delegation.amount;
}
if (poolBoost.workingSupply >= delegation.amount) {
poolBoost.workingSupply -= delegation.amount;
}
Impact
poolBoost
state not being udpated can lead to multiple issue, as it's used many places, throughout the proctocol.
Also it's causing the function to not work properly.
Tools Used
Manual
Recommendations
In delegateBoost()
function update poolBoost for to
address. something like -
function delegateBoost(
address to,
uint256 amount,
uint256 duration
) external override nonReentrant {
// Code....
+ PoolBoost storage poolBoost = poolBoosts[to];
+ poolBoost.totalBoost += delegation.amount;
+ poolBoost.workingSupply += delegation.amount;
// Code....
}