Core Contracts

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

Incorrect expression in `GaugeController` to calculate new gauge weight could lead to DoS

Description

Whenever a user votes on a gauge's weight through the vote function, the gauge's weight is updated in the _updateGaugeWeight function using the following expression:

uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
+ (newWeight * votingPower / WEIGHT_PRECISION);

This expression removes the old vote and adds the new one to the total gauge weight. However, it uses the current voting power rather than the old one when removing the old vote. If the user has gained more voting power, the amount removed will be higher than intended, potentially causing an underflow. As a result, this could lead to a denial of service, preventing users from voting indefinitely.

Context

Impact

Medium. This issue could prevent some users from voting.

Likelihood

Medium. This issue might affect a subset of users in some scenarios.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console, stdError} from "../../lib/forge-std/src/Test.sol";
import {GaugeController, IGaugeController} from "../../contracts/core/governance/gauges/GaugeController.sol";
import {RAACGauge} from "../../contracts/core/governance/gauges/RAACGauge.sol";
import {RWAGauge} from "../../contracts/core/governance/gauges/RWAGauge.sol";
import {veRAACToken} from "../../contracts/core/tokens/veRAACToken.sol";
import {RAACToken} from "../../contracts/core/tokens/RAACToken.sol";
import {ERC20Mock} from "./mocks/ERC20Mock.sol";
contract GaugesTest is Test {
// Contracts
GaugeController gaugeController;
veRAACToken veRaac;
RAACToken raac;
RAACGauge raacGauge;
RWAGauge rwaGauge;
ERC20Mock stakingToken;
ERC20Mock rewardToken;
// Actors
address OWNER = makeAddr("owner");
address ALICE = makeAddr("alice");
address BOB = makeAddr("bob");
function setUp() public {
vm.startPrank(OWNER);
// Deploy controller
raac = new RAACToken(OWNER, 0, 0);
veRaac = new veRAACToken(address(raac));
gaugeController = new GaugeController(address(veRaac));
// Deploy gauges
stakingToken = new ERC20Mock("Staking Token", "STK", 18);
rewardToken = new ERC20Mock("Reward Token", "RTK", 18);
raacGauge = new RAACGauge(address(rewardToken), address(stakingToken), address(gaugeController));
rwaGauge = new RWAGauge(address(rewardToken), address(stakingToken), address(gaugeController));
// Grant roles
raacGauge.grantRole(raacGauge.CONTROLLER_ROLE(), OWNER);
raacGauge.grantRole(raacGauge.EMERGENCY_ADMIN(), OWNER);
raacGauge.grantRole(raacGauge.FEE_ADMIN(), OWNER);
rwaGauge.grantRole(rwaGauge.CONTROLLER_ROLE(), OWNER);
rwaGauge.grantRole(rwaGauge.EMERGENCY_ADMIN(), OWNER);
rwaGauge.grantRole(rwaGauge.FEE_ADMIN(), OWNER);
// Config gauges
gaugeController.addGauge(
address(raacGauge), IGaugeController.GaugeType.RAAC, gaugeController.WEIGHT_PRECISION()
);
gaugeController.addGauge(address(rwaGauge), IGaugeController.GaugeType.RWA, gaugeController.WEIGHT_PRECISION());
vm.stopPrank();
}
function test_votingDoS() public {
// Give veTokens to Alice and Bob
deal(address(veRaac), ALICE, 100e18, true);
deal(address(veRaac), BOB, 200e18, true);
// Bob and Alice vote
vm.prank(ALICE);
gaugeController.vote(address(rwaGauge), 1000);
vm.prank(BOB);
gaugeController.vote(address(rwaGauge), 1000);
// Bob has more voting power
deal(address(veRaac), BOB, 1000e18, true);
// Assert that Bob can no longer vote because his transaction reverts due to underflow
vm.expectRevert(stdError.arithmeticError);
vm.prank(BOB);
gaugeController.vote(address(rwaGauge), 1000);
}
}

Instructions

First, integrate Foundry by running the following commands in your terminal, in the project's root directory:

# Create required directories
mkdir out lib
# Add `forge-std` module to `lib`
git submodule add https://github.com/foundry-rs/forge-std lib/forge-std
# Create foundry.toml
touch foundry.toml

Next, configure Foundry by adding the following settings to foundry.toml:

[profile.default]
src = "contracts"
out = "out"
lib = "lib"

After that, create a foundry/ directory inside the test/ directory. Inside foundry/, create the following files:

  • GaugesTest.t.sol

  • mocks/ERC20Mock.sol

Then, add the following code to mocks/ERC20Mock.sol:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract ERC20Mock is ERC20 {
uint8 private _decimals;
constructor(string memory _tokenName, string memory _tokenSymbol, uint8 _tokenDecimals)
ERC20(_tokenName, _tokenSymbol)
{
_decimals = _tokenDecimals;
}
function mint(address account, uint256 amount) external {
_mint(account, amount);
}
function burn(address account, uint256 amount) external {
_burn(account, amount);
}
function decimals() public view override returns (uint8) {
return _decimals;
}
}

Finally, paste the provided (PoC) into GaugesTest.t.sol and run:

forge test --mt test_votingDoS -vvv

Recommendation

Use the respective voting power of the old vote instead of the current voting power when removing from the gauge's weight to prevent underflows. This will ensure the correct calculation of the new gauge's weight and prevent unintended DoS scenarios.

Updates

Lead Judging Commences

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

GaugeController::_updateGaugeWeight uses current voting power for both old and new vote calculations, causing underflows when voting power increases and incorrect gauge weights

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

GaugeController::_updateGaugeWeight uses current voting power for both old and new vote calculations, causing underflows when voting power increases and incorrect gauge weights

Support

FAQs

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