Core Contracts

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

Critical gauge functionalities will be blocked

Summary

RAACGauge and RWAGauge will revert on most functionalities because initial maxBoost is smaller than minBoost.

Vulnerability Details

All gauges implement BaseGauge contract. In the BaseGauge constructor maxBoost is smaller than minBoost.

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);
// here
boostState.maxBoost = 25000; // <-
boostState.minBoost = 1e18; // <-

Base gauge tracks user rewards through updateReward modifier

  • Reward calculation flow: earned → getUserWeight → _applyBoost → calculateBoost

  • Boost formula: (baseWeight * boost) / 1e18

In calculateBoost:

  • If no total voting power (totalVeSupply = 0), returns minimum boost

  • Otherwise calculates voting power ratio: (veBalance * 1e18) / totalVeSupply

  • Uses this ratio to determine boost between min-max range

  • The subtraction maxBoost - minBoost fails when values aren't properly set.

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; // <- here

POC

It can be easy reproduced in any gauge test file. Lets take for examp,e RAACGauge. We just have to remove the call that overrides the initial boost parameters:

import { time } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import hre from "hardhat";
const { ethers } = hre;
describe("RAACGauge", () => {
let raacGauge;
let gaugeController;
let veRAACToken;
let rewardToken;
let owner;
let user1;
let user2;
let emergencyAdmin;
let snapshotId;
const WEEK = 7 * 24 * 3600;
const WEIGHT_PRECISION = 10000;
beforeEach(async () => {
snapshotId = await network.provider.send('evm_snapshot');
[owner, user1, user2, emergencyAdmin] = await ethers.getSigners();
// Deploy mock tokens first
const MockToken = await ethers.getContractFactory("MockToken");
rewardToken = await MockToken.deploy("Reward Token", "RWD", 18);
veRAACToken = await MockToken.deploy("veRAAC Token", "veRAAC", 18);
// Mint tokens to users for staking and voting
await rewardToken.mint(user1.address, ethers.parseEther("1000"));
await rewardToken.mint(user2.address, ethers.parseEther("1000"));
await veRAACToken.mint(user1.address, ethers.parseEther("1000"));
await veRAACToken.mint(user2.address, ethers.parseEther("500"));
// Deploy controller
const GaugeController = await ethers.getContractFactory("GaugeController");
gaugeController = await GaugeController.deploy(await veRAACToken.getAddress());
// Get current time and align to next week boundary with proper buffer
const currentTime = BigInt(await time.latest());
const nextWeekStart = ((currentTime / BigInt(WEEK)) + 3n) * BigInt(WEEK); // Add 3 weeks buffer
// Move to next week start
await time.setNextBlockTimestamp(Number(nextWeekStart));
await network.provider.send("evm_mine");
// Deploy RAACGauge
const RAACGauge = await ethers.getContractFactory("RAACGauge");
raacGauge = await RAACGauge.deploy(
await rewardToken.getAddress(),
await veRAACToken.getAddress(),
await gaugeController.getAddress()
);
// Setup roles and initial state
await raacGauge.grantRole(await raacGauge.CONTROLLER_ROLE(), owner.address);
// Approve tokens for staking
await rewardToken.connect(user1).approve(raacGauge.getAddress(), ethers.MaxUint256);
await rewardToken.connect(user2).approve(raacGauge.getAddress(), ethers.MaxUint256);
// Add gauge to controller and set initial weights
await gaugeController.grantRole(await gaugeController.GAUGE_ADMIN(), owner.address);
await gaugeController.addGauge(await raacGauge.getAddress(), 0, WEIGHT_PRECISION);
// Move time forward to ensure period is ready
await time.increase(WEEK);
// Set initial gauge weight through voting
await gaugeController.connect(user1).vote(await raacGauge.getAddress(), WEIGHT_PRECISION);
// Set initial weekly emission rate
await raacGauge.setWeeklyEmission(ethers.parseEther("10000"));
// Transfer reward tokens to gauge for distribution
await rewardToken.mint(raacGauge.getAddress(), ethers.parseEther("100000"));
// Initialize boost parameters before any staking operations
- await raacGauge.setBoostParameters(
- 25000, // 2.5x max boost
- 10000, // 1x min boost
- WEEK // 7 days boost window
- );
+ // // Initialize boost parameters before any staking operations
+ // await raacGauge.setBoostParameters(
+ // 25000, // 2.5x max boost
+ // 10000, // 1x min boost
+ // WEEK // 7 days boost window
+ // );
// Set initial weight after time alignment
await raacGauge.setInitialWeight(5000); // 50% weight
// Mine another block to ensure time progression
await network.provider.send("evm_mine");
});

Then run the test. You will see that huge % of test fail with 0x11 error.

Impact

  • Major functionality on gauges will be blocked

Tools Used

Manual review, unit tests

Recommendations

Change initial min boost parameter to smaller value.

Updates

Lead Judging Commences

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