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
.