Core Contracts

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

Boost Multiplier Calculation Always Returns Max Value

Summary

After analyzing the getBoostMultiplier function in BoostController.sol, I can confirm a critical mathematical flaw that causes incorrect boost multiplier calculations.

Vulnerable Code

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;
// VULNERABILITY: Mathematical cancellation leads to constant value
uint256 baseAmount = userBoost.amount * 10000 / MAX_BOOST;
return userBoost.amount * 10000 / baseAmount;
// Simplifies to: (amount * 10000) / (amount * 10000 / 25000) = 25000 (MAX_BOOST)
}

Mathematical Proof

Let's substitute:
amount = userBoost.amount
MAX_BOOST = 25000
Step 1: baseAmount = amount * 10000 / MAX_BOOST
Step 2: return (amount * 10000) / baseAmount
Substituting Step 1 into Step 2:
return (amount * 10000) / (amount * 10000 / 25000)
Simplifying:
= (amount * 10000 * 25000) / (amount * 10000)
= 25000 (MAX_BOOST)

Impact

  1. Economic Implications

// Every user gets maximum boost
function testBoostMultiplier() {
address user1 = address(0x1); // 1000 veTokens
address user2 = address(0x2); // 1 veToken
assert(controller.getBoostMultiplier(user1, pool) ==
controller.getBoostMultiplier(user2, pool)); // Both return 25000
}
  1. Reward Distribution Distortion

  • Small stakers receive same boost as large stakers

  • Negates veToken staking incentives

  • Undermines protocol tokenomics

  1. System Manipulation

// Example exploit
function exploitMaxBoost() {
// 1. Stake minimum veTokens
veToken.stake(1);
// 2. Receive maximum boost
uint256 boost = controller.getBoostMultiplier(address(this), pool);
assert(boost == MAX_BOOST); // Always true
// 3. Earn inflated rewards
pool.claim(); // Receives 2.5x intended rewards
}

Proof of Concept

contract BoostMultiplierTest {
BoostController controller;
function testAlwaysMaxBoost() public {
// Setup
address user = address(1);
address pool = address(2);
uint256 smallStake = 1e18;
uint256 largeStake = 1000e18;
// Mock veToken balances
vm.mockCall(
address(veToken),
abi.encodeWithSelector(IERC20.balanceOf.selector, user),
abi.encode(smallStake)
);
// Test multiplier
uint256 multiplier = controller.getBoostMultiplier(user, pool);
assertEq(multiplier, MAX_BOOST); // Always 25000
}
}

Recommended Mitigation

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 based on veToken holdings
uint256 userVotingPower = veToken.getVotingPower(user, block.timestamp);
uint256 totalVotingPower = veToken.getTotalVotingPower();
if (totalVotingPower == 0) return MIN_BOOST;
uint256 boostRatio = (userVotingPower * BASIS_POINTS) / totalVotingPower;
uint256 multiplier = MIN_BOOST +
((MAX_BOOST - MIN_BOOST) * boostRatio) / BASIS_POINTS;
return Math.min(multiplier, MAX_BOOST);
}

Additional Recommendations

  1. Add input validation:

require(userVotingPower <= totalVotingPower, "Invalid voting power");
  1. Implement boost decay:

uint256 timeSinceLastUpdate = block.timestamp - userBoost.lastUpdateTime;
multiplier = multiplier * (MAX_DURATION - timeSinceLastUpdate) / MAX_DURATION;
  1. Add events for monitoring:

event BoostMultiplierCalculated(
address indexed user,
address indexed pool,
uint256 multiplier,
uint256 votingPower
);
Updates

Lead Judging Commences

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

Give us feedback!