Core Contracts

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

Missing Valid GaugeType Check in setTypeWeight: Inconsistent State and Unwanted Storage Population

Summary

The setTypeWeight function in the GaugeController contract allows a gauge admin to set the weight for a specified gauge type. It accepts two parameters—the gauge type and the new weight—and updates the typeWeights mapping accordingly after verifying that the weight does not exceed MAX_TYPE_WEIGHT. However, the function lacks a validation check to ensure that the provided gauge type is supported (i.e., either GaugeType.RWA or GaugeType.RAAC as enforced in the addGauge function). Without this check, an admin (or a malicious actor with admin privileges) can assign a weight to an invalid or unsupported gauge type, which can lead to inconsistent internal state, unwanted storage population, and potentially erroneous behavior in gauge-related calculations such as reward distribution or governance.

Vulnerability Details

Function Overview

The setTypeWeight function is designed to update the weight associated with a gauge type. Its code is as follows:

function setTypeWeight(GaugeType gaugeType, uint256 weight) external onlyRole(GAUGE_ADMIN) {
if (weight > MAX_TYPE_WEIGHT) revert InvalidWeight();
// @info: doesn't check for a valid gaugeType
uint256 oldWeight = typeWeights[gaugeType];
typeWeights[gaugeType] = weight;
emit TypeWeightUpdated(gaugeType, oldWeight, weight);
}

Root Cause

  • Lack of GaugeType Validation:
    Unlike the addGauge function—which validates the gauge type by ensuring it is either GaugeType.RWA or GaugeType.RAAC—the setTypeWeight function does not perform such a check. As a result, any arbitrary value passed as gaugeType will be accepted, even if it is not one of the supported types.

  • Consequences:
    This oversight can lead to:

    • Inconsistent State: Unsupported gauge types may be recorded in the typeWeights mapping, causing mismatches with the rest of the system that expects only valid gauge types.

    • Unwanted Storage Population: Storing weights for gauge types that should not exist can lead to unpredictable behavior during gauge-related computations (e.g., reward allocations, governance weight calculations).

    • Potential Exploitation: Although only the gauge admin can call this function, a compromised admin key or an error by an admin can introduce invalid entries, disrupting the protocol's functionality.

Proof of Concept

Scenario Walkthrough

  1. Setup:
    An admin calls setTypeWeight with an invalid gauge type value (for example, an enum value that is not RWA or RAAC).

  2. Execution:
    The function accepts the invalid gauge type and sets a weight in the typeWeights mapping without any validation. Consequently, the internal state is updated with an unsupported gauge type, which might then be referenced by other parts of the system, leading to inconsistencies.

  3. Test Suite:
    The following Foundry test suite demonstrates how an invalid gauge type can be recorded in the typeWeights mapping.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {BaseGauge} from "../src/core/governance/gauges/BaseGauge.sol";
import {GaugeController} from "../src/core/governance/gauges/GaugeController.sol";
import {RAACGauge} from "../src/core/governance/gauges/RAACGauge.sol";
import {RWAGauge} from "../src/core/governance/gauges/RWAGauge.sol";
import {veRAACToken} from "../src/core/tokens/veRAACToken.sol";
import {RAACToken} from "../src/core/tokens/RAACToken.sol";
import {RewardTokenMock} from "./mocks/RewardTokenMock.m.sol";
import {StakingTokenMock} from "./mocks/StakingTokenMock.m.sol";
import {IGaugeController} from "../src/interfaces/core/governance/gauges/IGaugeController.sol";
import {IGauge} from "../src/interfaces/core/governance/gauges/IGauge.sol";
contract GaugeTest is Test {
GaugeController gaugeController;
BaseGauge baseGauge;
RAACGauge raacGauge;
RWAGauge rwaGauge;
veRAACToken veToken;
RAACToken raacToken;
RewardTokenMock rewardToken;
StakingTokenMock stakingToken;
address GAUGE_CONTROLLER_OWNER = makeAddr("GAUGE_CONTROLLER_OWNER");
address RAAC_GAUGE_OWNER = makeAddr("RAAC_GAUGE_OWNER");
address RWA_GAUGE_OWNER = makeAddr("RWA_GAUGE_OWNER");
address RAAC_OWNER = makeAddr("RAAC_OWNER");
address RAAC_MINTER = makeAddr("RAAC_MINTER");
uint256 initialRaacSwapTaxRateInBps = 200; // 2%, 10000 - 100%
uint256 initialRaacBurnTaxRateInBps = 150; // 1.5%, 10000 - 100%
uint256 MAX_LOCK_AMOUNT = 10_000_000e18;
uint256 MAX_LOCK_DURATION = 1460 days;
address VE_RAAC_OWNER = makeAddr("VE_RAAC_OWNER");
address ALICE = makeAddr("ALICE");
address BOB = makeAddr("BOB");
address CHARLIE = makeAddr("CHARLIE");
address DEVIL = makeAddr("DEVIL");
function setUp() public {
vm.startPrank(RAAC_OWNER);
raacToken = new RAACToken(RAAC_OWNER, initialRaacSwapTaxRateInBps, initialRaacBurnTaxRateInBps);
vm.stopPrank();
vm.startPrank(VE_RAAC_OWNER);
veToken = new veRAACToken(address(raacToken));
vm.stopPrank();
rewardToken = new RewardTokenMock();
stakingToken = new StakingTokenMock();
vm.startPrank(GAUGE_CONTROLLER_OWNER);
gaugeController = new GaugeController(address(veToken));
vm.stopPrank();
vm.startPrank(RAAC_GAUGE_OWNER);
raacGauge = new RAACGauge(address(rewardToken), address(stakingToken), address(gaugeController));
vm.stopPrank();
vm.startPrank(RWA_GAUGE_OWNER);
rwaGauge = new RWAGauge(address(rewardToken), address(stakingToken), address(gaugeController));
vm.stopPrank();
}
function raacTokenAllotmentAndAcquireVeRaac() private {
vm.startPrank(RAAC_OWNER);
raacToken.setMinter(RAAC_MINTER);
vm.stopPrank();
vm.startPrank(RAAC_MINTER);
raacToken.mint(ALICE, MAX_LOCK_AMOUNT);
raacToken.mint(BOB, MAX_LOCK_AMOUNT);
raacToken.mint(CHARLIE, MAX_LOCK_AMOUNT);
raacToken.mint(DEVIL, MAX_LOCK_AMOUNT);
raacToken.mint(ALICE, MAX_LOCK_AMOUNT);
vm.stopPrank();
}
function raacTokenLock() private {
vm.startPrank(ALICE);
raacToken.approve(address(veToken), MAX_LOCK_AMOUNT);
veToken.lock(MAX_LOCK_AMOUNT, MAX_LOCK_DURATION);
vm.stopPrank();
vm.startPrank(BOB);
raacToken.approve(address(veToken), MAX_LOCK_AMOUNT);
veToken.lock(MAX_LOCK_AMOUNT, MAX_LOCK_DURATION);
vm.stopPrank();
vm.startPrank(CHARLIE);
raacToken.approve(address(veToken), MAX_LOCK_AMOUNT);
veToken.lock(MAX_LOCK_AMOUNT, MAX_LOCK_DURATION);
vm.stopPrank();
vm.startPrank(DEVIL);
raacToken.approve(address(veToken), MAX_LOCK_AMOUNT);
veToken.lock(MAX_LOCK_AMOUNT, MAX_LOCK_DURATION);
vm.stopPrank();
}
function testInvalidGaugeTypeInSetTypeWeight() public {
// modified GaugeType in IGaugeController
// enum GaugeType {
// RWA,
// RAAC,
// WAAC
// }
// Call setTypeWeight with an invalid gauge type (e.g., 2) and a valid weight
uint256 newWeight = 500;
// No check exists in the function so this should succeed unexpectedly.
vm.startPrank(GAUGE_CONTROLLER_OWNER);
gaugeController.setTypeWeight(IGaugeController.GaugeType.WAAC, newWeight);
vm.stopPrank();
// Retrieve the stored type weight for the invalid gauge type
uint256 storedWeight = gaugeController.typeWeights(IGaugeController.GaugeType.WAAC);
console.log("Stored weight for invalid gauge type:", storedWeight);
// The stored weight should equal newWeight even though the gauge type is invalid
assertEq(storedWeight, newWeight);
}
}

How to Run the Test

  1. Step 1: Create a new Foundry project:

    forge init my-foundry-project
  2. Step 2: Remove unnecessary files.

  3. Step 3: Place your contract files (including GaugeController and its interface) in the src directory.

  4. Step 4: Create a test directory adjacent to src and add the above test file (e.g., GaugeControllerTypeWeightTest.t.sol).

  5. Step 5: Run the test using:

    forge test --mt testInvalidGaugeTypeInSetTypeWeight -vv
  6. Expected Output:

    [⠒] Compiling...
    No files changed, compilation skipped
    Ran 1 test for test/GaugeTest.t.sol:GaugeTest
    [PASS] testInvalidGaugeTypeInSetTypeWeight() (gas: 42598)
    Logs:
    Stored weight for invalid gauge type: 500
    Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.32ms (126.70µs CPU time)
    Ran 1 test suite in 12.08ms (3.32ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

This confirms that the function accepts and stores an invalid gauge type, resulting in inconsistent state.

Impact

  • Inconsistent Internal State:
    Recording an invalid gauge type in the typeWeights mapping can lead to misalignment between expected gauge types and the stored configuration. Subsequent functions that rely on gauge type values may produce erroneous calculations or behaviors.

  • Reward Distribution and Governance Distortion:
    If an invalid gauge type is referenced during reward distribution or governance calculations, it may result in incorrect reward allocations or improperly weighted votes.

  • Unwanted Storage Population:
    The absence of gauge type validation allows for the injection of arbitrary keys into the typeWeights mapping, which could potentially be exploited to interfere with system operations.

  • System Integrity Risk:
    Over time, if multiple invalid gauge types accumulate, the overall integrity of the gauge management system may be compromised, affecting both reward mechanics and governance stability.

Tools Used

  • Manual Review

  • Foundry

Recommendations

To mitigate this vulnerability, the setTypeWeight function should be updated to include a validation check for the gauge type. The valid gauge types should be restricted to those defined by the protocol (i.e., GaugeType.RWA and GaugeType.RAAC). If an invalid gauge type is provided, the function should revert with an InvalidGaugeType error.

Proposed Diff for GaugeController::setTypeWeight

function setTypeWeight(GaugeType gaugeType, uint256 weight) external onlyRole(GAUGE_ADMIN) {
- if (weight > MAX_TYPE_WEIGHT) revert InvalidWeight();
- // @info: doesn't check for a valid gaugeType
+ if (gaugeType != GaugeType.RWA && gaugeType != GaugeType.RAAC) revert InvalidGaugeType();
+ if (weight > MAX_TYPE_WEIGHT) revert InvalidWeight();
uint256 oldWeight = typeWeights[gaugeType];
typeWeights[gaugeType] = weight;
emit TypeWeightUpdated(gaugeType, oldWeight, weight);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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

Give us feedback!