The FeeCollector contract calculates pending rewards for a user based on their voting power relative to the total voting power. However, when a user claims rewards via the claimRewards function, the contract updates the user's reward checkpoint by setting it to the global totalDistributed value rather than their individual accrued share. This misalignment causes subsequent pending reward calculations to be incorrect, potentially resulting in underpayment of rewards.
How the Reward Calculation Works
Pending Reward Calculation:
The pending reward for a user is computed as follows:
Here, share represents the user’s entitled rewards based on their voting power, while userRewards[user] acts as a checkpoint tracking the amount already claimed.
Faulty Checkpoint Update:
In the claimRewards function, after a successful claim, the checkpoint is updated as:
This is incorrect because the checkpoint should reflect the user’s share, not the total global distribution.
The Mathematical Impact
Example Scenario:
Suppose totalDistributed is 1,000 tokens.
Total voting power is 1,000.
A particular user has a voting power of 100.
Expected user share:
If the user has not claimed rewards before (userRewards[user] is 0), the pending reward is correctly 100 tokens.
Faulty Update Consequence:
The current implementation sets userRewards[user] = totalDistributed, i.e., 1,000.
On a subsequent claim (with no additional distributions), the calculated share remains 100 tokens.
The pending reward would be computed as:
This means that the user would be unable to claim any new rewards until totalDistributed increases sufficiently, leading to an underpayment.
Scenario Setup
Initial Conditions:
totalDistributed = 1,000 tokens.
Total voting power = 1,000.
A user’s voting power = 100.
The user’s checkpoint (userRewards[user]) initially = 0.
Calculation and Outcome
Expected Calculation:
User’s share:
Pending reward (if no previous claims):
Faulty Update:
Current implementation sets userRewards[user] = totalDistributed = 1,000.
On the next claim (assuming no new distributions), the computed share remains 100 tokens.
New pending reward becomes:
The user is effectively blocked from claiming any further rewards until totalDistributed increases by more than 900 tokens.
Incorrect Reward Accrual:
Users may receive less than their fair share of rewards because the checkpoint overshoots the user’s actual accrued amount.
Manual review
Update Checkpoint Correctly:
Modify the claimRewards function so that the user's checkpoint is updated to their individual share, not the global totalDistributed. The correct update should be:
Revised claimRewards Function
Explanation of Changes
Accurate Checkpoint Update:
By computing the user’s share based on the global totalDistributed and the user’s voting power, and then updating the checkpoint with this share, the contract accurately tracks the rewards already claimed by the user.
Future Claims:
With the checkpoint correctly updated, subsequent reward claims will only compute rewards based on any new distributions beyond what the user has already received.
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.