Core Contracts

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

claimRewards allows users with veRAAC voting power to claim retroactive rewards and steal rewards from others.

Summary

In FeeCollector.sol, users that have veRAAC voting power can call claimRewards() to claim a portion of the fees. The issue is that totalDistributed is ever increasing, and claimRewards() calculates the pendingReward first before setting userRewards to the current totalDistributed.

This means that every new user with veRAAC voting power can claim a portion of the total fees even though they had their voting power later.

This also means that users who had their votes earlier but did not claim their fees can have their fees stolen from them.

Vulnerability Details

When fees are calculated through _processDistribution(), totalDistributed will increase. totalDistributed will never decrease in any circumstance.

function _processDistributions(uint256 totalFees, uint256[4] memory shares) internal {
uint256 contractBalance = raacToken.balanceOf(address(this));
if (contractBalance < totalFees) revert InsufficientBalance();
if (shares[0] > 0) {
uint256 totalVeRAACSupply = veRAACToken.getTotalVotingPower();
if (totalVeRAACSupply > 0) {
TimeWeightedAverage.createPeriod(
distributionPeriod,
block.timestamp + 1,
7 days,
shares[0],
totalVeRAACSupply
);
> totalDistributed += shares[0];

When claimRewards() is called, _calculatePendingRewards() is called before updating userRewards

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() checks the user's voting power, then the total voting power, and then distributes the share proportionately.

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

A user that just have voting power is able to claim the full proportion of totalDistributed since their userRewards[user] is zero.

For example,

If the total voting power is 100, shared by 4 user who has 10,20,30,40 power respectively. totalDistributed is 10e18.

User A should get 1e18, user B should get 2e18, user C should get 3e18 and user D should get 4e18 of the fees. Their userRewards will then be updated to 10e18.

Now, if User Alice comes in and has a voting power of 50, the total voting power now becomes 150, but the totalDistributed is still at 10e18.

User Alice share = 10e18 * 50 / 150 = 3.33e18

Since Alice userRewards is zero, Alice will get 3.33e18 as rewards. Since there is only 10e18 rewards, if User A/B/C/D do not collect their rewards first, then Alice will be able to steal their shares.

Impact

Fees can be claimed retroactively, making it unfair for earlier users with voting power.

Tools Used

Manual Review

Recommendations

Not an easy fix. Ensure that veRAAC contract is working together with FeeCollector contract:

The user should only get fees from the moment they have voting power, and the fees they get should be from the totalDistributed point onwards.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Time-Weighted Average Logic is Not Applied to Reward Distribution in `FeeCollector`

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Time-Weighted Average Logic is Not Applied to Reward Distribution in `FeeCollector`

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.