Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

Pool boost can be manipulated

Summary

The function BoostController::updateUserBoost() updates the boost value for a user in a specific pool. However, by using an fixed base amount for calculating boost amount, the pool boost can be manipulated

Vulnerability Details

Function BoostController::updateUserBoost() calls internal function _calculateBoost() with hardcode value amount = 10000. The function _calculateBoost() calculates user boost amount based on current voting states. However, when the user account does not have voting power, the function returns amount, which is the hardcode value 10000. This allows an user with no voting power can update pool boost, hence the pool boost can be manipulated.

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;
// Calculate new boost based on current veToken balance
@> uint256 newBoost = _calculateBoost(user, pool, 10000); // Base amount // <<<< hardcode amount = 10000
userBoost.amount = newBoost;
userBoost.lastUpdateTime = block.timestamp;
// Update pool totals safely
if (newBoost >= oldBoost) {
poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost);
} else {
poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
}
poolBoost.workingSupply = newBoost; // Set working supply directly to new boost
poolBoost.lastUpdateTime = block.timestamp;
emit BoostUpdated(user, pool, newBoost);
emit PoolBoostUpdated(pool, poolBoost.totalBoost, poolBoost.workingSupply);
}
function _calculateBoost(
address user,
address pool,
uint256 amount
) internal view returns (uint256) {
if (amount == 0) revert InvalidBoostAmount();
if (!supportedPools[pool]) revert PoolNotSupported();
// Get current weights without modifying state
(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; // <<< return 10000
}
// Create parameters struct for calculation
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;
}

PoC

Add the test to file test/unit/core/governance/boost/BoostController.test.js

describe("Boost Calculations", () => {
// ..
it.only("pool boost updates incorrectly", async () => {
// @audit PoC inflate pool total boost
// Initial boost setup
const poolAddr = mockPool.target
// valid update
await boostController.connect(user1).updateUserBoost(user1.address, poolAddr);
const initialBoost = await boostController.getPoolBoost(
poolAddr
);
let newUsers
[,,, ...newUsers] = await ethers.getSigners();
for(let u of newUsers){
expect(await veToken.balanceOf(u.address)).to.eq(0n) // no voting power
expect(await boostController.getWorkingBalance(u.address, poolAddr)).to.eq(0n) // no boost in the pool
await boostController.connect(u).updateUserBoost(u.address, poolAddr);
}
const finalBoost = await boostController.getPoolBoost(
poolAddr
);
// initial pool boost amount compare with final pool boost amount
expect(initialBoost[0]).to.eq(finalBoost[0])
});

Run the test and console shows

BoostController
Boost Calculations
1) pool boost updates incorrectly
0 passing (2s)
1 failing
1) BoostController
Boost Calculations
pool boost updates incorrectly:
AssertionError: expected 14999 to equal 184999.
+ expected - actual
-14999
+184999

It shows that the pool boost amount is manipulated to be much more higher than the initial state by which the initial state is result of a valid update

Impact

  • Pool boost amount is manipulated by accounts with no voting power

Tools Used

Manual

Recommendations

if (userBalance == 0 || totalSupply == 0) {
- return amount;
+ return 0;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

BoostController::updateUserBoost uses hardcoded 10000 base amount, storing basis points instead of actual boosted amount

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

BoostController::updateUserBoost uses hardcoded 10000 base amount, storing basis points instead of actual boosted amount

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.