Core Contracts

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

Incorrect Reward Tracking in FeeCollector:::claimRewards Function

Summary

The FeeCollector:::claimRewards function incorrectly updates userRewards[user] by setting it to totalDistributed, which is a global variable to store the total tokens distributed historically, instead of incrementing it with the user's actual claimed amount. This can result in users missing out on pending rewards or getting incorrect reward calculations over multiple claims.

Vulnerability Details

In the claimRewards function, the line userRewards[user] = totalDistributed;
overwrites the user’s claimed reward history with the total distributed rewards.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/collectors/FeeCollector.sol#L206

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;
// Transfer rewards
raacToken.safeTransfer(user, pendingReward);
emit RewardClaimed(user, pendingReward);
return pendingReward;
}

In the claimRewards function, the user's pendingReward is calcuated in _calculatePendingRewards.
https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/collectors/FeeCollector.sol#L479-L487

function _calculatePendingRewards(address user) internal view returns (uint256) {
uint256 userVotingPower = veRAACToken.getVotingPower(user);
if (userVotingPower == 0) return 0;
uint256 totalVotingPower = veRAACToken.getTotalVotingPower();
if (totalVotingPower == 0) return 0;
uint256 share = (totalDistributed * userVotingPower) / totalVotingPower;
return share > userRewards[user] ? share - userRewards[user] : 0;

Example Scenario:

Assume totalDistributed = 1000 RAAC, Bob's voting power = 1000 (10% of total), totalVotingPower = 10,000

  1. First Claim:

    share = (1000 * 1000) / 10000 = 100 RAAC

    share > userRewards[user] (0), so pending rewards = 100 - 0 = 100

    Bob claims 100 RAAC.

  2. Second claim:

    New totalDistributed = 2000 RAAC, Bob's voting power remains the same (10%), totalVotingPower remains 10,000.

    share = (2000 * 1000) / 10000 = 200 RAAC

    However, userRewards[user] = totalDistributed is updated to 1000 in previous claim.

    Since share 200 < userRewards[user] (1000), the function will return 0.

  3. Correct second claim:

    share = (2000 * 1000) / 10000 = 200 RAAC

    share = 200 - 100 (claimed in first claim) = 100

Impact

Users lose rewards over multiple claims. The system incorrectly assumes users have already claimed more than they actually have.

Tools Used

Manual code review

Recommended Mitigation

Replace:

userRewards[user] = totalDistributed;
with userRewards[user] += pendingReward;

This ensures proper accumulation of rewards and prevents incorrect deductions.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 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.

Give us feedback!