Vulnerability Details
When calling updateUserBoost()
function, poolBoost.workingSupply
will be updated based on newBoost
amount:
function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
if (paused()) revert EmergencyPaused();
if (user == address(0)) revert InvalidPool();
if (!supportedPools[pool]) revert PoolNotSupported();
UserBoost storage userBoost = userBoosts[user][pool];
@> PoolBoost storage poolBoost = poolBoosts[pool];
uint256 oldBoost = userBoost.amount;
@> uint256 newBoost = _calculateBoost(user, pool, 10000);
userBoost.amount = newBoost;
userBoost.lastUpdateTime = block.timestamp;
if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
}
@> poolBoost.workingSupply = newBoost;
poolBoost.lastUpdateTime = block.timestamp;
emit BoostUpdated(user, pool, newBoost);
emit PoolBoostUpdated(pool, poolBoost.totalBoost, poolBoost.workingSupply);
}
In _calculateBoost()
function, it calculate boost cased on user's veRAAC token balance only, not entire pool (with boost amount in range [10000, 25000] based on user's balance)
function _calculateBoost(
address user,
address pool,
uint256 amount
) internal view returns (uint256) {
if (amount == 0) revert InvalidBoostAmount();
if (!supportedPools[pool]) revert PoolNotSupported();
(uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower) = updateTotalWeight();
@> uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
uint256 totalSupply = IERC20(address(veToken)).totalSupply();
if (userBalance == 0 || totalSupply == 0) {
return amount;
}
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost,
minBoost: boostState.minBoost,
boostWindow: boostState.boostWindow,
totalWeight: totalWeight,
totalVotingPower: totalVotingPower,
votingPower: votingPower
});
@> (uint256 boostBasisPoints, uint256 boostedAmount) = BoostCalculator.calculateTimeWeightedBoost(
params,
userBalance,
totalSupply,
amount
);
if (boostedAmount < amount) {
return amount;
}
uint256 maxBoostAmount = amount * MAX_BOOST / 10000;
if (boostedAmount > maxBoostAmount) {
return maxBoostAmount;
}
return boostedAmount;
}
Which lead to workingSupply
amount of poolBoosts
is incorrectly updated
Impact
workingSupply
amount of poolBoosts
is incorrectly updated. Currently, this variable is only used in getPoolBoost()
function to get data of the pool, and it is not used anywhere
function getPoolBoost(
address pool
) external view returns (
uint256 totalBoost,
uint256 workingSupply,
uint256 baseSupply,
uint256 lastUpdateTime
) {
if (!supportedPools[pool]) revert PoolNotSupported();
PoolBoost storage boost = poolBoosts[pool];
return (
boost.totalBoost,
@> boost.workingSupply,
boost.baseSupply,
boost.lastUpdateTime
);
}
Recommendations
Update mechanism for updating workingSupply
variable