Core Contracts

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

BaseGauge.sol, wrong value of minBoost makes the contract unusable.

Summary

In BaseGauge.sol a max and min boost values are set to calculate boost of a user. However, minBoost is set to the wrong value, making it larger than maxBoost, which makes the whole contract unusable because of how it is designed.

Vulnerability Details

In the constructor of BaseGauge.sol, minBoost is set to 1e18, while maxBoost is only 25000.

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;

These values are used in _applyBoost(), passed into calculateBoost() of BoostCalculater library

function _applyBoost(address account, uint256 baseWeight) internal view virtual returns (uint256) {
if (baseWeight == 0) return 0;
IERC20 veToken = IERC20(IGaugeController(controller).veRAACToken());
uint256 veBalance = veToken.balanceOf(account);
uint256 totalVeSupply = veToken.totalSupply();
// 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
);
return (baseWeight * boost) / 1e18;
}

which will cause an underflow revert in BoostCalculator library

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;
}

Since _applyBoost() is called in getUserWeight(), which is called in earned(), which is called in _updateReward(), which is called in the modifier updateReward(), which is used in all major functions, stake(), withdraw(), getReward(), notifiyRewardAmount(), voteDirection(), making the contract unusable.

function getUserWeight(address account) public view virtual returns (uint256) {
uint256 baseWeight = _getBaseWeight(account);
@> return _applyBoost(account, baseWeight);
}
function earned(address account) public view returns (uint256) {
@> return (getUserWeight(account) *
(getRewardPerToken() - userStates[account].rewardPerTokenPaid) / 1e18
) + userStates[account].rewards;
}
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);
}
}
modifier updateReward(address account) {
@> _updateReward(account);
_;
}
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
function getReward() external virtual nonReentrant whenNotPaused updateReward(msg.sender) {
function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0))
function voteDirection(uint256 direction) public whenNotPaused updateReward(msg.sender) {

Impact

The contract is unusable, functions will revert with underflow error.

Tools Used

Manual Review

Recommendations

Set the min amount to 10000, instead of 1e18

constructor(
....
// Initialize boost parameters
boostState.maxBoost = 25000; // 2.5x
-- boostState.minBoost = 1e18;
++ boostState.minBoost = 10000;
Updates

Lead Judging Commences

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