Core Contracts

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

The getBoostMultiplier function in the BoostController contract always return the MAX_BOOST value

Summary

The getBoostMultiplier function in the BoostController contract contains a logical error that causes it to always return the MAX_BOOST value, regardless of the actual userBoost.amount. This issue arises due to incorrect mathematical operations in the calculation of the boost multiplier. As a result, the function fails to dynamically compute the boost multiplier based on the user's boost amount, rendering it ineffective for its intended purpose.

Vulnerability Details

The vulnerability lies in the following code segment within the getBoostMultiplier function:

function getBoostMultiplier(
address user,
address pool
) external view override returns (uint256) {
if (!supportedPools[pool]) revert PoolNotSupported();
UserBoost storage userBoost = userBoosts[user][pool];
if (userBoost.amount == 0) return MIN_BOOST;
// Calculate actual boost multiplier in basis points
uint256 baseAmount = userBoost.amount * 10000 / MAX_BOOST;
return userBoost.amount * 10000 / baseAmount;
}

Here, baseAmount is calculated as the ratio of userBoost.amount to MAX_BOOST, scaled by 10000. So the expression return userBoost.amount * 10000 / baseAmount; simplifies to:

return (userBoost.amount * 10000) / (userBoost.amount * 10000 / MAX_BOOST);

The userBoost.amount * 10000 terms cancel out, leaving the result as MAX_BOOST.

Poc

add the following test case to BoostController.test.js

it("test getBoostMultiplier", async () => {
await veToken.mint(user1.address, ethers.parseEther("19000"));
await veToken.mint(user2.address, ethers.parseEther("3000"));
await boostController.connect(user1).updateUserBoost(user1.address, mockPool.getAddress());
const user1Multiplier = await boostController.getBoostMultiplier(user1.address, mockPool.getAddress());
console.log("user1:",user1Multiplier);
await boostController.connect(user2).updateUserBoost(user2.address, mockPool.getAddress());
const user2Multiplier = await boostController.getBoostMultiplier(user2.address, mockPool.getAddress());
console.log("user2:",user2Multiplier);
});

run npx hardhat test --grep "test getBoostMultiplier"

BoostController
Delegation System
user1: 25000n
user2: 25000n

Although User1 and User2 have different amout of veToken, their boost multipier are the same, both are 25000.

Impact

  • Regardless of the value of userBoost.amount (as long as it is non-zero), the function always returns MAX_BOOST.

  • This behavior defeats the purpose of the function, which is expected to dynamically compute the boost multiplier based on the user's boost amount.

The impact is Medium, the likelihood is High, so the severity is Medium.

Tools Used

Manual Review

Recommendations

Consider following fix:

function getBoostMultiplier(
address user,
address pool
) external view override returns (uint256) {
if (!supportedPools[pool]) revert PoolNotSupported();
UserBoost storage userBoost = userBoosts[user][pool];
if (userBoost.amount == 0) return MIN_BOOST;
// Calculate actual boost multiplier in basis points
return userBoost.amount * 10000 / MAX_BOOST;
}
Updates

Lead Judging Commences

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

BoostController::getBoostMultiplier always returns MAX_BOOST for any non-zero boost due to mathematical calculation error, defeating the incentive mechanism

Support

FAQs

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