Summary
Any users who has not staked anything can steal rewards from the gauges simply by calling getReward
Vulnerability Details
The RAAC gauge contracts are governance contracts used to incentivize users to participate in governance activities. The only requirement is that users should stake their tokens in the gauges. This can be done via the BaseGauge::stake function:
* @notice Stakes tokens in the gauge
* @param amount Amount to stake
*/
function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
if (amount == 0) revert InvalidAmount();
_totalSupply += amount;
_balances[msg.sender] += amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
Users who stake in gauges that perfom well receive rewards from the protocol. This is achieved via the getReward function:
* @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);
}
}
The problem is that anybody can get rewards from the gauges even when they have staked none, making if no use the staking mechanism.
For a POC:
-
Add a new user3 to the list of signers at https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/test/unit/core/governance/gauges/BaseGauge.test.js#L19:
[owner, user1, user2, user3] = await ethers.getSigners();
-
Add this test to
it("should send earned rewards to non-stakers", async () => {
const earned = await baseGauge.earned(user3.address);
expect(earned).to.be.gte(0);
await baseGauge.connect(user3).getReward();
const reward = await rewardToken.balanceOf(user3.address);
expect(reward).to.be.gte(0);
});
Impact
Non-stakers can steal rewards from the gauges by staking nothing and calling getReward
Tools Used
Manual review
Recommendations
Consider incorporating users staked amount when calculating earned rewards