Core Contracts

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

Unbounded Boost Multiplier Breaks Protocol Economics

Summary

The boost multiplier in BoostController can exceed the MAX_BOOST limit of 2.5x, and this breaks core protocol economics by allowing users to receive higher rewards than intended. The BoostController calculates reward multipliers for users based on their veToken holdings. When a user's boost multiplier is calculated, the contract fails to properly enforce the MAX_BOOST limit of 25000 (2.5x). This creates a situation where users could receive amplified rewards beyond protocol design. his is like a loyalty program giving 500% cashback when the max should be 250% - it breaks core economic assumptions.

The assumption is the BoostCalculator library would naturally cap values, but the interaction between voting power calculations and boost multipliers created an edge case where values could exceed limits.

Vulnerability Details

The BoostController acts as the heart of RAAC's reward system, carefully balancing incentives for long-term holders. Think of it like a loyalty program where holding veRAACTokens lets you earn enhanced rewards - up to 2.5x more than base rates. /BoostController.sol#calculateBoost()

function calculateBoost(
address user,
address pool,
uint256 amount
) external view override returns (uint256 boostBasisPoints, uint256 boostedAmount) {
// ๐Ÿ” Validation check
if (!supportedPools[pool]) revert UnsupportedPool();
// โš–๏ธ Get current weights without modifying state
(uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower) = updateTotalWeight();
// ๐Ÿ“ˆ Get user's voting power at current timestamp
uint256 userVotingPower = veToken.getVotingPower(user, block.timestamp);
// ๐Ÿ”ง Setup calculation parameters
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost, // ๐ŸŽš๏ธ Should cap at 2.5x
minBoost: boostState.minBoost, // ๐Ÿ“‰ Minimum 1.0x
boostWindow: boostState.boostWindow,
totalWeight: totalWeight,
totalVotingPower: totalVotingPower,
votingPower: votingPower
});
// ๐Ÿ’ฅ Vulnerability: No explicit cap check after calculation
return BoostCalculator.calculateTimeWeightedBoost(
params,
userVotingPower,
totalVotingPower,
amount
);
}

The vulnerability emerges in the boost calculation logic. When a user with significant veToken holdings interacts with the system, the BoostCalculator can return multipliers exceeding the intended 2.5x cap (25000 basis points). This is similar to a credit card accidentally giving unlimited cashback instead of the advertised maximum.

Here's how it happens. A user deposits a large amount of tokens into veRAACToken. When they interact with a gauge or pool, the BoostController calculates their multiplier. The calculation considers their voting power relative to total supply, but crucially lacks a final boundary check against MAX_BOOST.

The impact ripples through the entire protocol. Gauges distribute excessive rewards, breaking carefully designed emission schedules. Pools apply multipliers beyond economic models. Most critically, this undermines RAAC's core mission of sustainable real estate tokenization by distorting reward mechanisms.

Impact

Allows users to receive higher rewards than intended by the protocol design. The economic model assumes a maximum 2.5x boost, but users could potentially get higher multipliers. Here's what makes this finding fascinating: /BoostController.sol#L39-L42

// Protocol Constants
uint256 public constant MAX_BOOST = 25000; // 2.5x maximum boost
uint256 public constant MIN_BOOST = 10000; // 1.0x minimum boost

Recommendations

The vulnerability exists in both calculateBoost() and _updateGaugeWeight(), but the core fix should be implemented in calculateBoost() since it's the primary function determining boost values.

function calculateBoost(
address user,
address pool,
uint256 amount
) external view override returns (uint256 boostBasisPoints, uint256 boostedAmount) {
// ๐Ÿ” Pool validation
if (!supportedPools[pool]) revert UnsupportedPool();
// โš–๏ธ Get current weights
(uint256 totalWeight, uint256 totalVotingPower, uint256 votingPower) = updateTotalWeight();
// ๐Ÿ“ˆ Get user's voting power
uint256 userVotingPower = veToken.getVotingPower(user, block.timestamp);
// ๐Ÿ”ง Setup boost parameters
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost, // ๐ŸŽš๏ธ 2.5x cap
minBoost: boostState.minBoost, // ๐Ÿ“‰ 1.0x floor
boostWindow: boostState.boostWindow,
totalWeight: totalWeight,
totalVotingPower: totalVotingPower,
votingPower: votingPower
});
// ๐ŸŽฏ Calculate boost
(boostBasisPoints, boostedAmount) = BoostCalculator.calculateTimeWeightedBoost(
params,
userVotingPower,
totalVotingPower,
amount
);
โ€‹
// โœ… Add explicit bounds checking
boostBasisPoints = boostBasisPoints > MAX_BOOST ? MAX_BOOST : boostBasisPoints;
boostedAmount = boostedAmount > (amount * MAX_BOOST / BASIS_POINTS) ?
(amount * MAX_BOOST / BASIS_POINTS) : boostedAmount;
return (boostBasisPoints, boostedAmount);
}

This implementation ensures the boost values never exceed MAX_BOOST while preserving all the sophisticated weight calculations that make RAAC's tokenomics work.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!