The GaugeController contract lacks proper tracking and validation of total voting power usage per user across multiple gauges. Users can allocate more than 100% of their voting power across different gauges, which violates the intended voting mechanism and could lead to unfair influence over gauge weights and reward distributions.
The current implementation only validates individual vote weights but fails to track the total voting power used across all gauges:
This means a user can vote on multiple gauges with 100% of his voting power.
pragma solidity ^0.8.19;
import {Test} from "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20Mock} from "../../contracts/mocks/core/tokens/ERC20Mock.sol";
import {GaugeController} 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 {IGaugeController} from "../../contracts/interfaces/core/governance/gauges/IGaugeController.sol";
contract FoundryTest is Test {
ERC20Mock public raacToken;
ERC20Mock public stakingToken;
ERC20Mock public rewardToken;
veRAACToken public veToken;
GaugeController public controller;
RAACGauge public raacGauge;
RWAGauge public rwaGauge;
address public admin = address(this);
address public alice = address(0x1);
address public bob = address(0x2);
address public carol = address(0x3);
uint256 public constant INITIAL_SUPPLY = 1_000_000e18;
uint256 public constant LOCK_AMOUNT = 100_000e18;
uint256 public constant YEAR = 365 days;
function setUp() public {
raacToken = new ERC20Mock("RAAC Token", "RAAC");
rewardToken = new ERC20Mock("Reward Token", "RWD");
veToken = new veRAACToken(address(raacToken));
controller = new GaugeController(address(veToken));
raacGauge = new RAACGauge(address(rewardToken), address(veToken), address(controller));
rwaGauge = new RWAGauge(address(rewardToken), address(veToken), address(controller));
raacToken.mint(alice, INITIAL_SUPPLY);
raacToken.mint(bob, INITIAL_SUPPLY);
rewardToken.mint(address(controller), INITIAL_SUPPLY * 10);
vm.startPrank(admin);
controller.addGauge(address(raacGauge), IGaugeController.GaugeType.RAAC, 0);
controller.addGauge(address(rwaGauge), IGaugeController.GaugeType.RWA, 0);
vm.stopPrank();
vm.startPrank(alice);
raacToken.approve(address(veToken), type(uint256).max);
veToken.approve(address(raacGauge), type(uint256).max);
vm.stopPrank();
vm.startPrank(bob);
raacToken.approve(address(veToken), type(uint256).max);
veToken.approve(address(raacGauge), type(uint256).max);
vm.stopPrank();
}
function test_UnlimitedVotingPower() public {
vm.startPrank(alice);
veToken.lock(LOCK_AMOUNT, YEAR);
controller.vote(address(raacGauge), 10000);
controller.vote(address(rwaGauge), 10000);
vm.stopPrank();
uint256 gaugeWeightRAAC = controller.getGaugeWeight(address(raacGauge));
console2.log("Gauge Weight RAAC:", gaugeWeightRAAC);
uint256 gaugeWeightRWA = controller.getGaugeWeight(address(rwaGauge));
console2.log("Gauge Weight RWA:", gaugeWeightRWA);
}
}
mapping(address => uint256) public userTotalVotingPower;
function vote(address gauge, uint256 weight) external override whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
if (weight > WEIGHT_PRECISION) revert InvalidWeight();
uint256 votingPower = veRAACToken.balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
uint256 oldWeight = userGaugeVotes[msg.sender][gauge];
uint256 newTotalPower = userTotalVotingPower[msg.sender] - oldWeight + weight;
if (newTotalPower > WEIGHT_PRECISION) revert ExceedsVotingPower();
userTotalVotingPower[msg.sender] = newTotalPower;
userGaugeVotes[msg.sender][gauge] = weight;
_updateGaugeWeight(gauge, oldWeight, weight, votingPower);
emit WeightUpdated(gauge, oldWeight, weight);
}