Core Contracts

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

Incorrect initialization of minBoost in the BaseGauge contract

Summary

The BaseGauge contract and BoostCalculator library implement a reward distribution system with boost multipliers based on user staking and voting power. However, there is a critical issue in the initialization of minBoost and maxBoost parameters in the BaseGauge contract. Specifically, minBoost is set to 1e18 (1,000,000,000,000,000,000), while maxBoost is set to 25000. This causes the calculateBoost function in the BoostCalculator library to behave incorrectly, leading to unintended reward distribution logic.

Vulnerability Details

In the BaseGauge constructor, the boostState is initialized as follows:

// Initialize boost parameters
boostState.maxBoost = 25000; // 2.5x
boostState.minBoost = 1e18;
boostState.boostWindow = 7 days;

Here, minBoost is set to 1e18, while maxBoost is set to 25000. This means minBoost is significantly larger than maxBoost, which violates the intended logic of the boost calculation.

The _applyBoost function will use these params to call calculateBoost function:

// Create BoostParameters struct from boostState
BoostCalculator.BoostParameters memory params = BoostCalculator.BoostParameters({
maxBoost: boostState.maxBoost,
minBoost: boostState.minBoost,
boostWindow: boostState.boostWindow,
totalWeight: boostState.totalWeight,
totalVotingPower: boostState.totalVotingPower,
votingPower: boostState.votingPower
});
uint256 boost = BoostCalculator.calculateBoost(
veBalance,
totalVeSupply,
params
);

The calculateBoost function in the BoostCalculator library is designed to calculate a user's boost multiplier based on their veToken balance and total supply. The function uses minBoost and maxBoost to determine the boost range. Here is the relevant code:

function calculateBoost(
uint256 veBalance,
uint256 totalVeSupply,
BoostParameters memory params
) internal pure returns (uint256) {
if (totalVeSupply == 0) {
return params.minBoost;
}
uint256 votingPowerRatio = (veBalance * 1e18) / totalVeSupply;
uint256 boostRange = params.maxBoost - params.minBoost;
uint256 boost = params.minBoost + ((votingPowerRatio * boostRange) / 1e18);
if (boost < params.minBoost) {
return params.minBoost;
}
if (boost > params.maxBoost) {
return params.maxBoost;
}
return boost;
}

In calculateBoost function, boostRange is calculated as params.maxBoost - params.minBoost. Since minBoost (1e18) is much larger than maxBoost (25000), this results in an underflow in Solidity's uint256 type, as a result, this function will revert.

The _applyBoost function is used in getUserWeight :

function getUserWeight(address account) public view virtual returns (uint256) {
uint256 baseWeight = _getBaseWeight(account);
return _applyBoost(account, baseWeight);
}

The getUserWeight function is used in earned :

function earned(address account) public view returns (uint256) {
return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}

The earned is used in _updateReward :

function _updateReward(address account) internal {
rewardPerTokenStored = getRewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
UserState storage state = userStates[account];
state.rewards = earned(account);
state.rewardPerTokenPaid = rewardPerTokenStored;
state.lastUpdateTime = block.timestamp;
emit RewardUpdated(account, state.rewards);
}
}

As a result, the _updateReward function will always revert due to underflow issues.

POC

add following test case into RAACGauge.test.js

it("getUserWeight should not revert", async () => {
const weight = await raacGauge.getUserWeight(user1.address);
expect(weight).to.be.gte(0);
});

comment the following code in beforeEach hook so that the initialization of minBoost and maxBoost parameters will not be changed:

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

run npx hardhat test --grep "getUserWeight should not revert"

1) RAACGauge
Initialization
getUserWeight should not revert:
Error: VM Exception while processing transaction: reverted with panic code 0x11 (Arithmetic operation overflowed outside of an unchecked block)
at RAACGauge.calculateBoost (contracts/libraries/governance/BoostCalculator.sol:89)
at RAACGauge._applyBoost (contracts/core/governance/gauges/BaseGauge.sol:246)
at RAACGauge.getUserWeight (contracts/core/governance/gauges/BaseGauge.sol:596)
at EdrProviderWrapper.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:444:41)
at staticCallResult (node_modules/ethers/src.ts/contract/contract.ts:337:22)
at staticCall (node_modules/ethers/src.ts/contract/contract.ts:303:24)

The transaction reverts due to Arithmetic operation overflowed.

Impact

The getUserWeight function will always revert if the minBoost is not reset.

The impact is Medium because the minBoost can be reset by calling setBoostParameters. The likelihood is High, so the severity is Medium.

Tools Used

Maunal Review

Recommendations

Consider following fix in BaseGauge

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 = 10000;
boostState.boostWindow = 7 days;
...
...
Updates

Lead Judging Commences

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