Core Contracts

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

Incorrect boost initialization can lead to overflow

Author Revealed upon completion

Summary

The BaseGauge.sol contract contains a potential arithmetic overflow vulnerability in the boost calculation logic. This occurs because the initial values for maxBoost and minBoost are set incorrectly in the constructor, leading to an underflow when calculating the boost range. While a function (setBoostParameters) exists to fix this issue, the contract will revert on most operations if this function is not called before any staking actions occur. This vulnerability is classified as medium severity due to the existence of a mitigation function, but it can still cause significant disruption if not addressed promptly.

Vulnerability Details

The constructor for BaseGauge.sol contains the following code:

/**
* @notice Initializes the gauge contract
* @param _rewardToken Address of reward token
* @param _stakingToken Address of staking token
* @param _controller Address of controller contract
* @param _maxEmission Maximum emission amount
* @param _periodDuration Duration of the period
*/
constructor(
address _rewardToken,
address _stakingToken,
address _controller,
uint256 _maxEmission,
uint256 _periodDuration
) {
rewardToken = IERC20(_rewardToken);
stakingToken = IERC20(_stakingToken);
controller = _controller;
// Initialize roles
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(CONTROLLER_ROLE, _controller);
// Initialize boost parameters
boostState.maxBoost = 25000; // 2.5x
boostState.minBoost = 1e18;
boostState.boostWindow = 7 days;
uint256 currentTime = block.timestamp;
uint256 nextPeriod = ((currentTime / _periodDuration) *
_periodDuration) + _periodDuration;
// Initialize period state
periodState.periodStartTime = nextPeriod;
periodState.emission = _maxEmission;
TimeWeightedAverage.createPeriod(
periodState.votingPeriod,
nextPeriod,
_periodDuration,
0,
10000 // VOTE_PRECISION
);
}

The potential overflow can occur in the following lines:

boostState.maxBoost = 25000; // 2.5x
boostState.minBoost = 1e18;

This is because when BaseGauge::_updateReward is called, it calls BaseGauge::earned which in turn calls BaseGauge::getUserWeight which then calls BaseGauge::_applyBoost. BaseGauge::_applyBoost calls BoostCalculator.calculateBoost which contains the following:

function calculateBoost(
uint256 veBalance,
uint256 totalVeSupply,
BoostParameters memory params
) internal pure returns (uint256) {
// Return base boost (1x = 10000 basis points) if no voting power
if (totalVeSupply == 0) {
return params.minBoost;
}
// Calculate voting power ratio with higher precision
uint256 votingPowerRatio = (veBalance * 1e18) / totalVeSupply;
// Calculate boost within min-max range
uint256 boostRange = params.maxBoost - params.minBoost;
uint256 boost = params.minBoost + ((votingPowerRatio * boostRange) / 1e18);
// Ensure boost is within bounds
if (boost < params.minBoost) {
return params.minBoost;
}
if (boost > params.maxBoost) {
return params.maxBoost;
}
return boost;
}

As seen above, if:

uint256 boostRange = params.maxBoost - params.minBoost;

and minBoost > maxBoost which it is in the constructor, then there is an overflow which stops most operations in the contract from running.

The following function does exist in the contract:

/**
* @notice Updates boost calculation parameters
* @param _maxBoost Maximum boost multiplier
* @param _minBoost Minimum boost multiplier
* @param _boostWindow Time window for boost
* @dev Only callable by controller
*/
function setBoostParameters(
uint256 _maxBoost,
uint256 _minBoost,
uint256 _boostWindow
) external onlyController {
boostState.maxBoost = _maxBoost;
boostState.minBoost = _minBoost;
boostState.boostWindow = _boostWindow;
}

This allows any address with the controller role to reset the boost parameters and prevent the overflow but if this function is not called by anyone before any staking operations begin, all operations will revert due to overflow. The existence of this function reduces the impact of this to a medium.

Proof Of Code (POC)

This test was run in the RAACGauge.test.js file. For this test to display the desired results, go into the initial beforeEach in the "RAACGauge" describe block and comment out the following block of code

// Initialize boost parameters before any staking operations
await raacGauge.setBoostParameters(
25000, // 2.5x max boost
10000, // 1x min boost
WEEK // 7 days boost window
);

This will assume that no controller has called raacGauge.setBoostParameters yet and the initial boost parameters in the constructor are still used. After this, any test you run in the file will overflow as expected.

Impact

Contract Functionality Broken: Most operations in the contract (e.g., staking, reward distribution, reward claims) will revert due to the arithmetic underflow, rendering the contract unusable.

Medium Severity:The existence of the setBoostParameters function reduces the severity of this issue, as the contract can be fixed by calling this function. However, the impact is still significant if the function is not called promptly.

Tools Used

Manual Review, Hardhat

Recommendations

To fix this issue, the following steps should be taken:

  1. Fix Initial Boost Parameters in Constructor:
    Ensure that minBoost is less than or equal to maxBoost in the constructor. For example:

boostState.maxBoost = 25000; // 2.5x
boostState.minBoost = 10000; // 1x

This prevents the arithmetic underflow from occurring during boost calculations.

Add Validation in setBoostParameters:
Add a validation check in the setBoostParameters function to ensure that minBoost is always less than or equal to maxBoost:

function setBoostParameters(
uint256 _maxBoost,
uint256 _minBoost,
uint256 _boostWindow
) external onlyController {
require(_minBoost <= _maxBoost, "minBoost must be <= maxBoost");
boostState.maxBoost = _maxBoost;
boostState.minBoost = _minBoost;
boostState.boostWindow = _boostWindow;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 12 days ago
Submission Judgement Published
Validated
Assigned finding tags:

boostState.minBoost is set to 1e18

Support

FAQs

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