The FeeCollector::claimRewards() is responsible for distributing pending rewards to users who have locked their RAAC tokens in the veRAACToken contract based on their voting power. However, due to incorrect updates in the userRewards mapping, a user who has already claimed rewards once may be unable to claim them again, even if they are eligible.
The claimRewards() function calculates rewards using _calculatePendingRewards():
The _calculatePendingRewards() function determines a user's share of rewards based on their voting power and the total supply of veRAACToken:
Before transferring rewards, the contract updates the userRewards mapping for the user:
https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/collectors/FeeCollector.sol#L206
This issue does not affect the first reward claim. However, after more rewards are distributed, a user may not be able to claim future rewards due to several issues:
The totalDistributed value increases whenever rewards are distributed but is never reset or decreased.
The reward share calculation is based on the updated totalDistributed value. If the newly calculated share is less than or equal to userRewards[user], the pending reward will be 0, making the user unable to claim rewards.
Since userRewards[user] is set to the previous value of totalDistributed, the user will only receive rewards again if their voting power increases significantly or totalDistributed grows substantially, which may take a long time.
High. Users may be unable to claim rewards until totalDistributed increases significantly. However, totalDistributed grows based on protocol fees, which are distributed among multiple stakeholders (veRAACShare, burnShare, repairShare, treasuryShare). As a result, it could take a long time before a user is able to claim rewards again.
I have built a below test in Foundry. Paste below code in test folder and run test with following command:
forge test --mt test_audit_ClaimRewards -vvv
Test Output
Alice and Bob lock their RAAC tokens in the veRAACToken contract at different times.
The owner calls collectFee() in the FeeCollector contract, distributing fees across different categories.
After some time, the owner calls distributeCollectedFees().
Alice and Bob claim their rewards successfully for the first time.
Later, the owner repeats steps 2 and 3.
Alice tries to claim her rewards again but faces an InsufficientBalance() error because her pending rewards are now 0, even though she should be eligible.
Foundry
Update the userRewards mapping to collected rewards of user.
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.