Core Contracts

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

Incorrect Distribution of veRAACToken Holder Rewards Due to Missing RAACToken Transfer

Summary

The _processDistributions function in FeeCollector fails to distribute rewards to veRAACToken holders despite calculating their share.

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]; //@audit this does not transfer raacToken, and there is no other calls in FeeCollector to TimeWeightedAverage
} else {
shares[3] += shares[0]; // Add to treasury if no veRAAC holders
}
}
if (shares[1] > 0) raacToken.burn(shares[1]);
if (shares[2] > 0) raacToken.safeTransfer(repairFund, shares[2]);
if (shares[3] > 0) raacToken.safeTransfer(treasury, shares[3]);
}

Vulnerability Details

In the condition of shares[0] > 0, a TimeWeightAverage lock period is created, but there is no additional calls to TimeWeightAverage to unlock RAACTokens for veRAACToken holder.
The timelock is there, but there is no logic for users to claims their rewards distributed over time.

Impact

  1. veRAACToken holders never receive their share (shares[0])

  2. Tokens remain stuck in FeeCollector

  3. All reward accounting is incorrect

Tools Used

Manual

Recommendations

In _processDistributions transfer RAAC to the TimeWeightedAverage contract, and add withdraw logic there

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]; //@audit this does not transfer raacToken, and there is no other calls in FeeCollector to TimeWeightedAverage
+ raacToken.safeTransfer(address(TimeWeightedAverage), shares[0]);
} else {
shares[3] += shares[0]; // Add to treasury if no veRAAC holders
}
}
if (shares[1] > 0) raacToken.burn(shares[1]);
if (shares[2] > 0) raacToken.safeTransfer(repairFund, shares[2]);
if (shares[3] > 0) raacToken.safeTransfer(treasury, shares[3]);
}
+ withdraw logic in TimeWeightedAverage for veRAACHolders

OR

Add withdraw logic in FeeCollector that uses TimeWeightedAverage information

Note that first proposition seems better as FeeCollector holds RAACToken for multiple parties (veRAACShare, burn, repair and treasury) so it can be complicated to do all of that in one place.

Updates

Lead Judging Commences

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

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

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

Give us feedback!