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