All users in the gauge get rewards based on the gauge's total weight rather than their stake. In this way, a user with a tiny stake can receive the same rewards as a large staker (assuming the same boost). Users with small stakes get drastically overcompensated and large stakers get undercompensated. This breaks the principle of stake-based reward distribution. Additionally, this can drain reward tokens faster than intended.
pragma solidity ^0.8.0;
import {Test, console2} from "forge-std/Test.sol";
import {RAACGauge} from "../contracts/core/governance/gauges/RAACGauge.sol";
import {GaugeController} from "../contracts/core/governance/gauges/GaugeController.sol";
import {IGaugeController} from "../contracts/interfaces/core/governance/gauges/IGaugeController.sol";
import {IGauge} from "../contracts/interfaces/core/governance/gauges/IGauge.sol";
import {MockToken} from "../contracts/mocks/core/tokens/MockToken.sol";
contract RaacGaugeTest is Test {
MockToken public rewardToken;
MockToken public veRAACToken;
GaugeController public gaugeController;
RAACGauge public raacGauge;
bytes32 public constant CONTROLLER_ROLE = keccak256("CONTROLLER_ROLE");
bytes32 public constant EMERGENCY_ADMIN = keccak256("EMERGENCY_ADMIN");
bytes32 public constant FEE_ADMIN = keccak256("FEE_ADMIN");
address owner = makeAddr("owner");
address alice = makeAddr("alice");
address bob = makeAddr("bob");
uint256 public WEEK = 7 * 24 * 3600;
uint256 public WEIGHT_PRECISION = 10000;
function setUp() public {
rewardToken = new MockToken("Reward Token", "RWD", 18);
veRAACToken = new MockToken("veRAAC Token", "veRAAC", 18);
veRAACToken.mint(alice, 200 ether);
veRAACToken.mint(bob, 1000 ether);
rewardToken.mint(alice, 1000 ether);
rewardToken.mint(bob, 1000 ether);
gaugeController = new GaugeController(address(veRAACToken));
vm.warp(block.timestamp + 3 weeks);
raacGauge = new RAACGauge(address(rewardToken), address(veRAACToken), address(gaugeController));
raacGauge.grantRole(raacGauge.CONTROLLER_ROLE(), owner);
vm.startPrank(alice);
rewardToken.approve(address(raacGauge), type(uint256).max);
veRAACToken.approve(address(raacGauge), type(uint256).max);
vm.stopPrank();
vm.startPrank(bob);
rewardToken.approve(address(raacGauge), type(uint256).max);
veRAACToken.approve(address(raacGauge), type(uint256).max);
vm.stopPrank();
gaugeController.grantRole(gaugeController.GAUGE_ADMIN(), owner);
gaugeController.addGauge(address(raacGauge), IGaugeController.GaugeType.RAAC, WEIGHT_PRECISION);
vm.warp(block.timestamp + 1 weeks);
vm.prank(alice);
gaugeController.vote(address(raacGauge), WEIGHT_PRECISION);
vm.prank(owner);
raacGauge.setWeeklyEmission(10000 ether);
rewardToken.mint(address(raacGauge), 100000 ether);
vm.prank(owner);
raacGauge.setBoostParameters(25000, 10000, WEEK);
vm.prank(address(gaugeController));
raacGauge.setInitialWeight(5000);
vm.warp(block.timestamp + 1);
console2.log("\nContracts:");
console2.log("rewardToken: ", address(rewardToken));
console2.log("veRAACToken: ", address(veRAACToken));
console2.log("raacGauge: ", address(raacGauge));
console2.log("");
}
function test_wrongEarnedRewardsCalculation() public {
vm.prank(alice);
raacGauge.stake(100 ether);
vm.prank(bob);
raacGauge.stake(900 ether);
uint256 aliceBalanceAfter = veRAACToken.balanceOf(alice);
uint256 bobBalanceAfter = veRAACToken.balanceOf(bob);
assertEq(aliceBalanceAfter, 100 ether);
assertEq(bobBalanceAfter, 100 ether);
vm.prank(address(gaugeController));
raacGauge.notifyRewardAmount(1000 ether);
vm.prank(alice);
raacGauge.voteEmissionDirection(5000);
vm.warp(block.timestamp + 1 weeks / 2);
assertEq(raacGauge.getUserWeight(alice), raacGauge.getUserWeight(bob));
assertEq(raacGauge.earned(alice), raacGauge.earned(bob));
console2.log("AliceWeight: ", raacGauge.getUserWeight(alice));
console2.log("BobWeight: ", raacGauge.getUserWeight(bob));
console2.log("Alice earned: ", raacGauge.earned(alice));
console2.log("Bob earned: ", raacGauge.earned(bob));
console2.log("----------------");
}
}
The test shows that Alice and Bob earn the same rewards due same weight despite Bob stake being 9x higher than Alice.
Note: In the test, Alice and Bob have the same veRAACToken balance (100 ether each), therefore they get the same boost multiplier.
function test_wrongEarnedRewardsCalculationAfterRecommandation() public {
vm.prank(alice);
raacGauge.stake(100 ether);
vm.prank(bob);
raacGauge.stake(900 ether);
uint256 aliceBalanceAfter = veRAACToken.balanceOf(alice);
uint256 bobBalanceAfter = veRAACToken.balanceOf(bob);
assertEq(aliceBalanceAfter, 100 ether);
assertEq(bobBalanceAfter, 100 ether);
vm.prank(address(gaugeController));
raacGauge.notifyRewardAmount(1000 ether);
vm.prank(alice);
raacGauge.voteEmissionDirection(5000);
vm.warp(block.timestamp + 1 weeks / 2);
assertLt(raacGauge.getUserWeight(alice), raacGauge.getUserWeight(bob));
assertLt(raacGauge.earned(alice), raacGauge.earned(bob));
console2.log("AliceWeight: ", raacGauge.getUserWeight(alice));
console2.log("BobWeight: ", raacGauge.getUserWeight(bob));
console2.log("Alice earned: ", raacGauge.earned(alice));
console2.log("Bob earned: ", raacGauge.earned(bob));
console2.log("----------------");
}
and run forge test --match-test test_wrongEarnedRewardsCalculationAfterRecommandation -vv
The test shows that Bob earns correctly 9x then Alice.