Core Contracts

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

totalBoost of pool can be increased artificially

Summary

Anyone can call the updateUserBoost function to update a user's boost in the pool. However, this function does not validate the user's veRaacToken balance, allowing an attacker to manipulate the totalBoost of the pool and incorrectly increase its value.

Vulnerability Details

The userBoost in pool can be update via updateUserBoost.

/contracts/core/governance/boost/BoostController.sol:178
178: function updateUserBoost(address user, address pool) external override nonReentrant whenNotPaused {
179: if (paused()) revert EmergencyPaused(); // @note no need for this one
180: if (user == address(0)) revert InvalidPool();
181: if (!supportedPools[pool]) revert PoolNotSupported();
182:
183: UserBoost storage userBoost = userBoosts[user][pool];
184: PoolBoost storage poolBoost = poolBoosts[pool];
185:
186: uint256 oldBoost = userBoost.amount;
187: // Calculate new boost based on current veToken balance
188: uint256 newBoost = _calculateBoost(user, pool, 10000); // Base amount
191: userBoost.amount = newBoost;
192: userBoost.lastUpdateTime = block.timestamp;
193: if (newBoost >= oldBoost) {
194: poolBoost.totalBoost = poolBoost.totalBoost + (newBoost - oldBoost); // @audit-attack : increase this value to max limit
195: } else {
196: poolBoost.totalBoost = poolBoost.totalBoost - (oldBoost - newBoost);
197: }
198: poolBoost.workingSupply = newBoost; // Set working supply directly to new boost @audit need to understand this
199: poolBoost.lastUpdateTime = block.timestamp;
..
202: emit PoolBoostUpdated(pool, poolBoost.totalBoost, poolBoost.workingSupply);
203: }

From the above code it can be seen that we pass the user address, pool address and bps 10000 to _calculateBoost find the new Boost for a user in a pool.

/contracts/core/governance/boost/BoostController.sol:89
89: function _calculateBoost(
90: address user,
91: address pool,
92: uint256 amount
93: ) internal view returns (uint256) {
94: if (amount == 0) revert InvalidBoostAmount();
95: if (!supportedPools[pool]) revert PoolNotSupported();
96:
97: // Get current weights without modifying state
98: (uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower) = updateTotalWeight();
99:
100: uint256 userBalance = IERC20(address(veToken)).balanceOf(user);
101: uint256 totalSupply = IERC20(address(veToken)).totalSupply();
102:
103: if (userBalance == 0 || totalSupply == 0) {
104: return amount; // @audit why not returning zero ? return 10000
105: }
106:

Inside the _calculateBoost function, we return 10000 when the user's veToken balance is 0 or when totalSupply is 0. This can be problematic, as an attacker could call this function with an arbitrary user address, artificially increasing the totalBoost of the pool by 10000.

POC

Add the following test case to BoostController.test.js and run with command with npx hardhat test

it.only("totalBoost will be manipulated by any attacker", async () => {
const amount = ethers.parseEther("100");
let poolAddress = await mockPool.getAddress();
let [beforeHackBoost ,,,] = await boostController.getPoolBoost(poolAddress)
for(let i=0 ; i <10 ; i++){
const userWallet = ethers.Wallet.createRandom();
await boostController.connect(user1).updateUserBoost(userWallet.address,poolAddress);
beforeHackBoost +=10000n; // it is being increased 10000 per calls
}
let [afterHackBoost,,,] = await boostController.getPoolBoost(poolAddress)
expect(beforeHackBoost).to.eq(afterHackBoost);
});

Impact

The totalBoost value can be manipulated by an attacker, leading to incorrect value reporting. Additionally, in cases where the totalBoost is decreased—such as during delegate removal calls the totalBoost of the pool will not be updated correctly.

Tools Used

Manual Review

Recommendations

return 0 in case when user balance is 0.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 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 7 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.

Give us feedback!