Vulnerability Details
The BoostController
contract enforces a design where only the recipient of a boost delegation (the pool) can remove the delegation after it expires. The original delegator (the user) has no ability to remove their expired delegation, creating an unnecessary dependency on the pool's action and potentially trapping user's delegation state.
The issue is in the 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];
}
Here we have a case:
Delegation expired(user chooses the time)
User boost is allocated to this pool
User cannot remove the boost delegation due to the strict check:
if (delegation.delegatedTo != msg.sender) revert DelegationNotFound();
Sponsor confirmed:
Alex Werner
"It is a bug, we indeed need the user to be able to remove the delegation."
Impact
Tools Used
Manual Review
Recommendations
Modify the removeBoostDelegation
function to allow both the original delegator and the delegation recipient to remove expired delegations.
function removeBoostDelegation(address from) external override nonReentrant {
UserBoost storage delegation = userBoosts[from][msg.sender];
- if (delegation.delegatedTo != msg.sender) revert DelegationNotFound();
+ // Allow either the delegator (from) or the delegatee (delegation.delegatedTo) to remove
+ bool isAuthorizedCaller = (msg.sender == from || msg.sender == delegation.delegatedTo);
+ if (!isAuthorizedCaller) revert NotAuthorized();
if (delegation.expiry > block.timestamp) revert InvalidDelegationDuration();
// Update pool boost totals before removing delegation
- PoolBoost storage poolBoost = poolBoosts[msg.sender];
+ PoolBoost storage poolBoost = poolBoosts[delegation.delegatedTo];
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];
+ emit DelegationRemoved(from, delegation.delegatedTo, delegation.amount);
+ delete userBoosts[from][delegation.delegatedTo];
}