Relevant GitHub Links
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/BaseGauge.sol#L327-L345
Summary
BaseGauge.getReward() checks lastClaimTime to enforce minimum interval between claims but fails to update it after a successful claim, allowing users to bypass the interval restriction.
Vulnerability Details
The getReward() function checks lastClaimTime but never updates it:
function getReward() external virtual nonReentrant whenNotPaused updateReward(msg.sender) {
if (block.timestamp - lastClaimTime[msg.sender] < MIN_CLAIM_INTERVAL) {
revert ClaimTooFrequent();
}
}
While the contract has _updateLastClaimTime(address user) function, it's never called after claims.
Impact
Users can claim rewards more frequently than the intended MIN_CLAIM_INTERVAL (1 day), disrupting the protocol's reward distribution schedule.
Tools Used
Manual Review
Recommendations
Add _updateLastClaimTime(msg.sender) at the end of getReward():
function getReward() external virtual nonReentrant whenNotPaused updateReward(msg.sender) {
if (block.timestamp - lastClaimTime[msg.sender] < MIN_CLAIM_INTERVAL) {
revert ClaimTooFrequent();
}
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);
}
_updateLastClaimTime(msg.sender);
}