Core Contracts

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

User can only claim rewards once, and subsequent claims will always fail due to incorrect accounting of user rewards

Summary

In FeeCollector.sol, user can claim their RAAC rewards via claimRewards(). However, in this function. the updating of userRewardsmapping is done incorrectly, leading to user being unable to claim any subsequent rewards after the first claim.

Vulnerability Details

In claimRewards(), the pending reward of a user is calculated via _calculatedPendingRewards(). However, as seen below, the userRewards mapping for the user is updated 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;
// Transfer rewards
raacToken.safeTransfer(user, pendingReward);
emit RewardClaimed(user, pendingReward);
return pendingReward;
}

TotalDistributedis the total RAAC tokens distributed historically to stakeholders - this amount would be the accounting for all distributions to veRAAC holders, treasury, repair fund, and to burn.

Assume this scenario that user had claimed rewards for the first time. Now, userRewards[user] = totalDistributed.

Now, user call claimRewards()for the second time. As seen in the code snippet above (Line 1), _calculatePendingRewards()is called.

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

After calculating userVotingPower and totalVotingPower, share is calculated. However, 0 will always be returned, because share will always be less than userRewards[user], since userRewards[user] = totalDistributed.

Impact

Once userRewards[user] has been set to be equals to totalDistributed, user can never claim any rewards - user can only claim once and never again.

Tools Used

Recommendations

Ensure the updating of userRewards[] mapping is done correctly in claimRewards()function in the FeeCollector.sol contract.

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!