Summary
The claimRewards function incorrectly updates userRewards[user] by directly assigning it the global accumulated value totalDistributed, leading to miscalculations in rewards distribution.
Vulnerability Details
userRewards[user] should track the total rewards a user has claimed. However, in the current implementation, it is incorrectly set to the protocol-wide accumulated value totalDistributed.
* @notice Claims accumulated rewards for a user
* @param user Address of the user claiming rewards
* @return amount Amount of rewards claimed
*/
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();
@> userRewards[user] = totalDistributed;
raacToken.safeTransfer(user, pendingReward);
emit RewardClaimed(user, pendingReward);
return pendingReward;
}
As a result, when a user attempts to claim rewards again, the calculation in the following line:
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;
}
may erroneously return 0, preventing the user from claiming their rightful rewards.
Steps to Reproduce
Initial State:
First Claim:
`pending = 1000 * 10% - 0 = 100 RAAC userRewards[A] = 1000 (incorrect assignment) `
Second Distribution:
Second Claim Attempt:
`share = 2000 * 10% = 200 < userRewards[A] (1000)
Result:
Impact
Users may be unable to claim subsequent rewards, leading to an unfair distribution of incentives.
Tools Used
Manual
Recommendations
/**
* @notice Claims accumulated rewards for a user
* @param user Address of the user claiming rewards
* @return amount Amount of rewards claimed
*/
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;
}
This fix ensures that userRewards[user] correctly tracks only the total claimed rewards instead of being incorrectly reset to totalDistributed.