Summary
Using assignment instead of adding value when updating pool boost working supply in the function BoostController::updateUserBoost() causes pool boost working supply to be incorrectly updated.
Vulnerability Details
According to natspec, PoolBoost.workingSupply is The total working supply including boosts. However, in the function BoostController::updateUserBoost() the value poolBoost.workingSupply is set directly to the value newBoost, which is the boost amount of the user account, not the new boost amount of the pool boost. This causes the poolBoost.workingSupply to be updated incorrectly.
* @notice Struct to track pool-wide boost metrics
* @param totalBoost The total boost amount for the pool
* @param workingSupply The total working supply including boosts
* @param baseSupply The base supply without boosts
* @param lastUpdateTime The last time pool boosts were updated
*/
struct PoolBoost {
uint256 totalBoost;
uint256 workingSupply;
uint256 baseSupply;
uint256 lastUpdateTime;
}
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);
}
PoC
Add the test to test/unit/core/governance/boost/BoostController.test.js
describe("Boost Calculations", () => {
it.only("pool boost working supply incorrectly updated", async () => {
const poolAddr = mockPool.target
await boostController.connect(user2).updateUserBoost(user1.address, poolAddr);
const initialWorkingSupply = (await boostController.getPoolBoost(poolAddr))[1];
await veToken.setBalance(user1.address, ethers.parseEther("50"))
await boostController.connect(user1).updateUserBoost(user1.address, poolAddr);
const finalWorkingSupply = (await boostController.getPoolBoost(poolAddr))[1];
expect(finalWorkingSupply).to.gt(initialWorkingSupply)
});
Run the test and console shows:
BoostController
Boost Calculations
1) pool boost working supply incorrectly updated
0 passing (2s)
1 failing
1) BoostController
Boost Calculations
pool boost working supply incorrectly updated:
AssertionError: expected 10365 to be above 14999.
+ expected - actual
-10365
+14999
Impact
Tools Used
Manual
Recommendations
N/A