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.
Medium. This issue could prevent some users from voting.
Medium. This issue might affect a subset of users in some scenarios.
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 {
GaugeController gaugeController;
veRAACToken veRaac;
RAACToken raac;
RAACGauge raacGauge;
RWAGauge rwaGauge;
ERC20Mock stakingToken;
ERC20Mock rewardToken;
address OWNER = makeAddr("owner");
address ALICE = makeAddr("alice");
address BOB = makeAddr("bob");
function setUp() public {
vm.startPrank(OWNER);
raac = new RAACToken(OWNER, 0, 0);
veRaac = new veRAACToken(address(raac));
gaugeController = new GaugeController(address(veRaac));
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));
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);
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 {
deal(address(veRaac), ALICE, 100e18, true);
deal(address(veRaac), BOB, 200e18, true);
vm.prank(ALICE);
gaugeController.vote(address(rwaGauge), 1000);
vm.prank(BOB);
gaugeController.vote(address(rwaGauge), 1000);
deal(address(veRaac), BOB, 1000e18, true);
vm.expectRevert(stdError.arithmeticError);
vm.prank(BOB);
gaugeController.vote(address(rwaGauge), 1000);
}
}
First, integrate Foundry by running the following commands in your terminal, in the project's root directory:
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;
}
}
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.