Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Incorrect reward tracking leads to loss of user rewards

Summary

The FeeCollector::claimRewards() function incorrectly updates user reward tracking by setting it to totalDistributed instead of adding the claimed amount, causing users to lose rewards as their voting power decreases over time.

Vulnerability Details

In FeeCollector::claimRewards(), after calculating pending rewards, the function updates userRewards[user] by setting it to totalDistributed:

function claimRewards(address user) external override nonReentrant whenNotPaused returns (uint256) {
if (user == address(0)) revert InvalidAddress();
uint256 pendingReward = _calculatePendingRewards(user);
if (pendingReward == 0) revert InsufficientBalance();
// Reset user rewards before transfer
@> userRewards[user] = totalDistributed; //Incorrect
// Transfer rewards
raacToken.safeTransfer(user, pendingReward);
emit RewardClaimed(user, pendingReward);
return pendingReward;
}

This is problematic because:

  1. userRewards[user] should track the cumulative amount of rewards claimed by the user

  2. totalDistributed represents all fees distributed to date

  3. Setting userRewards[user] = totalDistributed means future reward calculations will compare against the total distributed amount rather than what the user has actually claimed

The _calculatePendingRewards() function calculates rewards based on:

uint256 share = (totalDistributed * userVotingPower) / totalVotingPower;
return share > userRewards[user] ? share - userRewards[user] : 0;

Since voting power decreases over time, setting userRewards[user] = totalDistributed means the user's calculated share will always be less than their recorded userRewards, resulting in 0 rewards.

Proof of Concept

  1. User A deposits tokens and receives voting power of 1000

  2. totalDistributed is 10000 tokens

  3. User A claims rewards:

    • share = (10000 * 1000) / 10000 = 1000

    • userRewards[A] = 0

    • Claims 1000 tokens

    • userRewards[A] is set to 10000 (totalDistributed)

  4. Time passes, voting power decays to 800

  5. New rewards are distributed, totalDistributed = 12000

  6. User A tries to claim again:

    • share = (12000 * 800) / 10000 = 960

    • Since 960 < 10000 (userRewards[A]), user gets 0 rewards

Impact

Users are denied their rightful rewards because the system incorrectly compares their earned share against the total distributed amount instead of their previously claimed amount. Even when users should be eligible for new rewards due to their voting power, they receive nothing since their calculated share will always be less than the total distributed amount from their last claim.

Recommendations

Track actual claimed amounts:

- userRewards[user] = totalDistributed;
+ userRewards[user] += pendingReward;
Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

FeeCollector::claimRewards sets `userRewards[user]` to `totalDistributed` seriously grieving users from rewards

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.