Core Contracts

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

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

Summary

The contract references a TimeWeightedAverage mechanism and calls TimeWeightedAverage.createPeriod(...) in _processDistributions(). However, the actual reward calculation in _calculatePendingRewards() does not read or incorporate any time-weighted logic. Instead, rewards are distributed based on a simple snapshot fraction of totalDistributed.

Vulnerability Details

  • Missing Time Weighting: While the contract claims to use time-weighted distribution, it never actually applies the data from distributionPeriod or the TimeWeightedAverage library in the final reward calculation.

  • Simplistic Formula: The FeeCollector::_calculatePendingRewards() function merely does:

uint256 share = (totalDistributed * userVotingPower) / totalVotingPower;

This formula does not account for the passage of time of the distribution period.

Impact

Users who acquire veRAAC mid-period receive rewards based on their voting power at the snapshot, not over the duration they held it.

POC

Here is a POC of the issue:

it("should calculate pending rewards correctly", async function () {
const pendingRewardsBeforeUser1 = await feeCollector.getPendingRewards(user1.address);
console.log("User1 Pending Rewards Before Distribution:", pendingRewardsBeforeUser1.toString());
await feeCollector.connect(owner).distributeCollectedFees();
await time.increase(WEEK);
const pendingRewardsBeforeUser3 = await feeCollector.getPendingRewards(user3.address);
console.log("User3 Pending Rewards After Distribution and one week:", pendingRewardsBeforeUser3.toString());
await veRAACToken.connect(user3).lock(ethers.parseEther("1000"), ONE_YEAR);
const pendingRewardsAfterLockUser3 = await feeCollector.getPendingRewards(user3.address);
console.log(
"User3 Pending Rewards After Distribution, straight after lock:",
pendingRewardsAfterLockUser3.toString()
);
const pendingRewardsAfterWeekUser1 = await feeCollector.getPendingRewards(user1.address);
console.log(
"User1 Pending Rewards After Distribution, straight after user3 locked:",
pendingRewardsAfterWeekUser1.toString()
);
});

and the output:

User1 Pending Rewards Before Distribution: 0
User3 Pending Rewards After Distribution and one week: 0
User3 Pending Rewards After Distribution, straight after lock: 2000000000000000
User1 Pending Rewards After Distribution, straight after user3 locked: 1923287354134958

Tools Used

Manual Review

Recommendations

Incorporate Time Weighting in _calculatePendingRewards()

Updates

Lead Judging Commences

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

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

inallhonesty Lead Judge 3 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.