When users try to vote to reduce a gauge's weight, the voted weight is added to the initial weight instead, effectively creating a permanent weight floor. This could cause a revert in BaseGauge::notifyRewardAmount, when called by GaugeController::distributeRewards as at a point, the total calculated rewards would be greater than the actual rewards. i.e Two gauges with initial weight set at 5000 bips with total reward at 10000, since the weights can only increase when voted for, any vote will make the total greater than 10000 bips. This would cause the sum of the calculated rewards, greater than the total reward.
pragma solidity ^0.8.19;
import {ERC20Mock} from "../../../../contracts/mocks/core/tokens/ERC20Mock.sol";
import {FeeCollector} from "../../../../contracts/core/collectors/FeeCollector.sol";
import {Treasury} from "../../../../contracts/core/collectors/Treasury.sol";
import {RAACToken} from "../../../../contracts/core/tokens/RAACToken.sol";
import {veRAACToken} from "../../../../contracts/core/tokens/veRAACToken.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 {Test, console} from "forge-std/Test.sol";
contract TestSuite is Test {
FeeCollector feeCollector;
Treasury treasury;
RAACToken raacToken;
veRAACToken veRAACTok;
GaugeController gaugeController;
RAACGauge raacGauge;
RAACGauge raacGauge2;
RWAGauge rwaGauge;
ERC20Mock rewardToken;
address repairFund;
address admin;
uint256 initialSwapTaxRate = 100;
uint256 initialBurnTaxRate = 50;
uint256 initialWeight = 5000;
function setUp() public {
repairFund = makeAddr("repairFund");
admin = makeAddr("admin");
rewardToken = new ERC20Mock("Reward Token", "RWT");
treasury = new Treasury(admin);
raacToken = new RAACToken(admin, initialSwapTaxRate, initialBurnTaxRate);
veRAACTok = new veRAACToken(address(raacToken));
feeCollector = new FeeCollector(address(raacToken), address(veRAACTok), address(treasury), repairFund, admin);
vm.startPrank(admin);
raacToken.setFeeCollector(address(feeCollector));
raacToken.setMinter(admin);
gaugeController = new GaugeController(address(veRAACTok));
raacGauge = new RAACGauge(address(rewardToken), address(raacToken), address(gaugeController));
raacGauge2 = new RAACGauge(address(rewardToken), address(raacToken), address(gaugeController));
rwaGauge = new RWAGauge(address(rewardToken), address(raacToken), address(gaugeController));
gaugeController.addGauge(address(raacGauge), IGaugeController.GaugeType.RAAC, initialWeight);
gaugeController.addGauge(address(raacGauge2), IGaugeController.GaugeType.RAAC, initialWeight);
gaugeController.addGauge(address(rwaGauge), IGaugeController.GaugeType.RWA, initialWeight);
vm.stopPrank();
}
function testGaugeWeightFloorPreventsGaugeWeightReduction() public {
address user1 = makeAddr("user1");
address user2 = makeAddr("user2");
uint256 userTokens = 100e18;
vm.startPrank(admin);
raacToken.mint(user1, userTokens);
raacToken.mint(user2, userTokens);
vm.stopPrank();
vm.startPrank(user1);
raacToken.approve(address(veRAACTok), userTokens);
veRAACTok.lock(userTokens, veRAACTok.MAX_LOCK_DURATION());
vm.stopPrank();
vm.startPrank(user2);
raacToken.approve(address(veRAACTok), userTokens);
veRAACTok.lock(userTokens, veRAACTok.MAX_LOCK_DURATION());
vm.stopPrank();
vm.prank(user1);
gaugeController.vote(address(raacGauge), 5000);
vm.prank(user2);
gaugeController.vote(address(raacGauge2), 5000);
uint256 gauge1InitialReward = gaugeController._calculateReward(address(raacGauge));
uint256 gauge2InitialReward = gaugeController._calculateReward(address(raacGauge2));
uint256 gauge1InitialWeight = gaugeController.getGaugeWeight(address(raacGauge));
uint256 gauge2InitialWeight = gaugeController.getGaugeWeight(address(raacGauge2));
console.log("Gauge 1 initial reward:", gauge1InitialReward);
console.log("Gauge 2 initial reward:", gauge2InitialReward);
console.log("Gauge 1 initial weight:", gauge1InitialWeight);
console.log("Gauge 2 initial weight:", gauge2InitialWeight);
vm.startPrank(user1);
gaugeController.vote(address(raacGauge), 0);
gaugeController.vote(address(raacGauge2), 10000);
vm.stopPrank();
vm.startPrank(user2);
gaugeController.vote(address(raacGauge), 0);
gaugeController.vote(address(raacGauge2), 10000);
vm.stopPrank();
uint256 gauge1FinalReward = gaugeController._calculateReward(address(raacGauge));
uint256 gauge2FinalReward = gaugeController._calculateReward(address(raacGauge2));
uint256 gauge1FinalWeight = gaugeController.getGaugeWeight(address(raacGauge));
uint256 gauge2FinalWeight = gaugeController.getGaugeWeight(address(raacGauge2));
console.log("Gauge 1 final reward:", gauge1FinalReward);
console.log("Gauge 2 final reward:", gauge2FinalReward);
console.log("Gauge 1 final weight:", gauge1FinalWeight);
console.log("Gauge 2 final weight:", gauge2FinalWeight);
assertGt(
gauge1FinalReward, gauge1InitialReward / 2, "Gauge 1 still gets significant rewards due to weight floor"
);
uint256 totalRewards = gauge1FinalReward + gauge2FinalReward;
uint256 gauge1Share = (gauge1FinalReward * 100) / totalRewards;
console.log("Gauge 1 share despite zero votes:", gauge1Share, "%");
console.log("Initial weight acting as floor:", initialWeight);
assertGt(gauge1Share, 30, "Deprecated gauge still receives significant share due to weight floor");
}
}
The inability to reduce gauge weights below their initial values will make capital allocation inefficient and potentially cause reward distribution failures. It will also undermine the governance of the community in controlling the gauge weight value.