The claimRewards function in the FeeCollector contract is designed to allow users to claim their accumulated rewards based on their voting power. However, the function contains two critical oversights:
It fails to update the last claim timestamp via the helper function _updateLastClaimTime, which is necessary for enforcing a minimum claim delay.
It does not check that the minimum claim delay (e.g., MIN_CLAIM_INTERVAL) has passed since the last claim.
Without these checks, users can claim rewards too frequently. This not only violates the intended reward claim interval—potentially leading to abuse or misalignment in reward distribution—but also may cause a denial of service for users who have previously claimed rewards, as their recorded rewards are set to totalDistributed, preventing subsequent proper reward calculation.
Reward Calculation:
The function calculates the pending rewards using _calculatePendingRewards(user). If no rewards are pending, it reverts.
Faulty Reset of Reward State:
Instead of updating the user's reward record to their proportional share, the function resets userRewards[user] to totalDistributed. This incorrect update can cause subsequent reward calculations to yield zero, particularly if the user's rewards have already been claimed.
Missing Claim Delay Enforcement:
The contract maintains a state variable lastClaimTime and a helper function _updateLastClaimTime to record the timestamp of the last reward claim. However, claimRewards does not check whether the required minimum delay between claims has passed before allowing another claim. This oversight permits users to claim rewards repeatedly in rapid succession.
Faulty claimRewards Function:
Missing Claim Delay Check:
There is no check like:
nor is _updateLastClaimTime(user) called to update the claim timestamp after a reward claim.
Initial Claim:
User Alice claims her rewards for the first time. The function calculates her pending reward and successfully transfers tokens to her.
However, the function resets userRewards[ALICE] to totalDistributed rather than her proportional share, and it does not update lastClaimTime[ALICE].
Subsequent Claim Attempt:
Shortly after the initial claim, Alice attempts to claim rewards again.
Due to the improper update, her pending reward is calculated as zero (or less than expected), or the system may prevent further accumulation, leading to a denial of service where subsequent claims always return 0.
Test Suite Example:
Below is a Foundry test case demonstrating the vulnerability:
Create a Foundry Project:
Place Contract Files:
Ensure that FeeCollector.sol, RAACToken.sol, veRAACToken.sol, and dependencies are in the src directory.
Create Test Directory:
Create a test directory adjacent to src and add the above test file (e.g., FeeCollectorTest.t.sol).
Run the Test:
Expected Output:
The first claim should succeed, but after increasing voting power, the second claim should revert with an InsufficientBalance error, demonstrating that the resetting of userRewards is incorrect and that no update is made to the last claim timestamp.
Denial of Service in Reward Claims:
Users who claim rewards once will be locked out of future claims since userRewards is reset to totalDistributed, preventing accumulation of additional rewards.
Economic Distortion:
This error leads to inaccurate reward distribution, whereby users may never receive rewards proportional to their increased voting power, thus misaligning incentives.
Governance Implications:
Since rewards are tied to governance participation, an inability to claim rewards accurately undermines the incentive structure that ensures fair decision-making.
Long-Term Trust Erosion:
Persistent reward claim failures can diminish user confidence in the protocol, discouraging participation and destabilizing the overall ecosystem.
Manual Review
Foundry
To resolve this vulnerability, the claimRewards function must be modified to:
Correctly update userRewards[user] to reflect the user’s share of totalDistributed based on their voting power.
Update the last claim timestamp by invoking _updateLastClaimTime(user) to enforce the minimum claim delay.
Implement and Enforce a Minimum Claim Delay:
Ensure that a check is in place to verify that the minimum claim interval has elapsed before allowing a new claim, e.g.:
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.