Core Contracts

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

Wrong reward calculation in `FeeCollector`

Summary

The reward calculation in the FeeCollector contract uses incorrect reward tracking, which prevents users from claiming rewards after their first claim.

Vulnerability Details

The claimRewards function sets userRewards to totalDistributed instead of the actual claimed amount for the user:

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;
}

_calculatePendingRewards is called to calculate pendingReward, which calculates claimable rewards using userRewards:

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:

Claim 1:
userVotingPower = 100 (10%)
totalVotingPower = 1000
totalDistributed = 10
share = (10 * 100) / 1000 = 1
userRewards[user] = 0
claimable = 1 > 0 ? 1 - 0 : 0 = 1
After claim: userRewards[user] = 10
Claim 2 (Additional fees distributed):
userVotingPower = 200
totalVotingPower = 2000
totalDistributed = 30
share = (30 * 200) / 2000 = 3
userRewards[user] = 10
claimable = 3 > 10 ? 3 - 10 : 0 = 0

Impact

High: Users cannot claim rewards after their first claim.

Recommendations

Track actual claimed amounts instead of using 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;
+ userRewards[user] += pendingReward;
// Transfer rewards
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.