Core Contracts

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

Time-weighted reward not actually functional in FeeCollector.sol

Description

The FeeCollector contract clearly intends to implement a time-weighted reward distribution system as is evident in natspec:

File: contracts/core/collectors/FeeCollector.sol
15: /**
16: * @title Fee Collector Contract
17: * @author RAAC Protocol Team
18:@---> * @notice Manages protocol fee collection and distribution with time-weighted rewards
19: * @dev Core contract for handling all protocol fee operations
20: * Key features:
21: * - Fee collection from different protocol activities
22:@---> * - Time-weighted reward distribution to veRAAC holders
23: * - Configurable fee splits between stakeholders
24: * - Emergency controls and access role management
25: */
26: contract FeeCollector is IFeeCollector, AccessControl, ReentrancyGuard, Pausable {

To implement this the function _processDistributions() even calls TimeWeightedAverage.createPeriod here to create a 7 day period:

File: contracts/core/collectors/FeeCollector.sol
401: function _processDistributions(uint256 totalFees, uint256[4] memory shares) internal {
402: uint256 contractBalance = raacToken.balanceOf(address(this));
403: if (contractBalance < totalFees) revert InsufficientBalance();
404:
405: if (shares[0] > 0) {
406: uint256 totalVeRAACSupply = veRAACToken.getTotalVotingPower();
407: if (totalVeRAACSupply > 0) {
408:@---> TimeWeightedAverage.createPeriod(
409: distributionPeriod,
410: block.timestamp + 1,
411:@---> 7 days,
412: shares[0],
413: totalVeRAACSupply
414: );
415: totalDistributed += shares[0];
416: } else {
417: shares[3] += shares[0]; // Add to treasury if no veRAAC holders
418: }
419: }
420:
421: if (shares[1] > 0) raacToken.burn(shares[1]);
422: if (shares[2] > 0) raacToken.safeTransfer(repairFund, shares[2]);
423: if (shares[3] > 0) raacToken.safeTransfer(treasury, shares[3]);
424: }

The intention most likely is:

  • Track user voting power over time windows (7 day periods)

  • Calculate rewards based on average voting power over those periods

  • This would make rewards fairer by using averaged voting power rather than instantaneous voting power at claim time

  • It would help prevent gaming by users who might try to time their claims based on voting power fluctuations

However, this period is never actually linked to _calculatePendingRewards() which simply uses current voting power ratios against total distributions instead of time-weighted averages:

File: contracts/core/collectors/FeeCollector.sol
474: /**
475:@---> * @dev Calculates pending rewards for a user using time-weighted average
476: * @param user Address of the user
477: * @return pendingAmount Amount of pending rewards
478: */
479: function _calculatePendingRewards(address user) internal view returns (uint256) {
480:@---> uint256 userVotingPower = veRAACToken.getVotingPower(user);
481: if (userVotingPower == 0) return 0;
482:
483: uint256 totalVotingPower = veRAACToken.getTotalVotingPower();
484: if (totalVotingPower == 0) return 0;
485:
486:@---> uint256 share = (totalDistributed * userVotingPower) / totalVotingPower;
487: return share > userRewards[user] ? share - userRewards[user] : 0;
488: }

Note that L475 says "time-weighted" but the userVotingPower on L480 is really just the decayed veRAACToken balance at the current point of time.

Impact

Time-weighted voting power & reward never calculated in reality. Users can lock tokens to increase rewards instantaneously.

Mitigation

Make use of the time-weighted period inside _calculatePendingRewards().

Updates

Lead Judging Commences

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

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

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