Core Contracts

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

Double Claiming Risk in FeeCollector Due to Incorrect Reward State Reset

Relevant GitHub Links

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

Summary

The FeeCollector's claimRewards function incorrectly resets user reward state by setting it to the total distributed amount instead of tracking claimed rewards, enabling users to claim rewards multiple times.

Vulnerability Details

In FeeCollector.sol, when users claim rewards, their reward state is reset to totalDistributed:

function claimRewards(address user) external nonReentrant whenNotPaused returns (uint256) {
if (user == address(0)) revert InvalidAddress();
uint256 pendingReward = _calculatePendingRewards(user);
if (pendingReward == 0) revert InsufficientBalance();
// Incorrect: Sets user rewards to total distributed instead of tracking claimed amount
userRewards[user] = totalDistributed;
raacToken.safeTransfer(user, pendingReward);
emit RewardClaimed(user, pendingReward);
return pendingReward;
}

The issue arises when:

  1. A user claims their rewards

  2. New rewards are distributed (totalDistributed increases)

  3. User can claim a portion of rewards again because userRewards[user] is set to the previous totalDistributed

Impact

  1. Users can claim the same rewards multiple times

  2. Protocol distributes more rewards than intended

  3. Rewards pool will be depleted faster than designed

  4. Potential economic loss for protocol and other users

Tools Used

Manual Review

Recommendations

Track claimed rewards separately from total distributed rewards:

mapping(address => uint256) public claimedRewards;
function claimRewards(address user) external nonReentrant whenNotPaused returns (uint256) {
if (user == address(0)) revert InvalidAddress();
uint256 pendingReward = _calculatePendingRewards(user);
if (pendingReward == 0) revert InsufficientBalance();
// Update claimed amount instead of resetting to total
claimedRewards[user] += pendingReward;
raacToken.safeTransfer(user, pendingReward);
emit RewardClaimed(user, pendingReward);
return pendingReward;
}
Updates

Lead Judging Commences

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