Summary
When a user claims his reward by using getReward function defined in BaseGauge contract and used by RAACGauge and RWAGauge contracts, he gets rewards exceeding the distributionCap.
Vulnerability Details
If we look at the docs in 2025-02-raac/docs/core/governance/gauges/BaseGauge.md, we can see that:
Distribution cap limits reward amounts
This distributionCap is set using setDistributionCap function by FEE_ADMIN role
* @notice Sets cap on reward distribution
* @param newCap New distribution cap value
*/
function setDistributionCap(uint256 newCap) external {
if (!hasRole(FEE_ADMIN, msg.sender)) revert UnauthorizedCaller();
@> distributionCap = newCap;
emit DistributionCapUpdated(newCap);
}
But in getReward function there is no check for distributionCap
* @notice Claims accumulated rewards
* @dev Transfers earned rewards to caller
*/
function getReward() external virtual nonReentrant whenNotPaused updateReward(msg.sender) {
if (block.timestamp - lastClaimTime[msg.sender] < MIN_CLAIM_INTERVAL) {
revert ClaimTooFrequent();
}
lastClaimTime[msg.sender] = block.timestamp;
UserState storage state = userStates[msg.sender];
uint256 reward = state.rewards;
if (reward > 0) {
state.rewards = 0;
uint256 balance = rewardToken.balanceOf(address(this));
if (reward > balance) {
revert InsufficientBalance();
}
rewardToken.safeTransfer(msg.sender, reward);
emit RewardPaid(msg.sender, reward);
}
}
This leads to users getting rewards higher than the distributionCap.
Add this test to RAACGauge.test.js:
describe("Reward Cap", () => {
it("should distribute rewards exceeding distributionCap set by FEE_ADMIN", async () => {
await raacGauge.grantRole(await raacGauge.FEE_ADMIN(), user2.address);
await veRAACToken.connect(user1).approve(raacGauge.getAddress(), ethers.MaxUint256);
await raacGauge.connect(user1).stake(ethers.parseEther("100"));
await time.increase(WEEK / 2);
const rewardAmount = await raacGauge.earned(user1.address)
const newDistributionCap = rewardAmount / BigInt(2);
expect(await raacGauge.connect(user2).setDistributionCap(newDistributionCap)).to.emit(raacGauge, "DistributionCapUpdated");
expect(await raacGauge.connect(user1).getReward()).to.emit(raacGauge, "RewardPaid").withArgs(user1.address, rewardAmount);
})
});
Impact
Over distribution of reward tokens which can result in unexpected token inflation devaluing the reward token and potentially harming all stakeholders.
Tools Used
Manual review
Recommendations
Ensure that getReward function checks the distributionCap.