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();
> userRewards[user] = totalDistributed;
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;
}