Core Contracts

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

Immediate Voting Power Inflation Allows Users to Get Rewards Share They Were Not Eligible For

Summary

When a user locks their RAAC tokens, they immediately receive voting power. This instant increase affects the total supply of voting tokens and skews the distribution of rewards in FeeCollector. As a result, users can claim rewards that were originally intended for previous holders, effectively inflating their rewards unfairly.

Vulnerability Details

The reward distribution inside FeeCollector is calculated as follows:

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; // @audit-issue Inflates rewards as userVotingPower is accounted immediately
return share > userRewards[user] ? share - userRewards[user] : 0;
}

Here, the share of rewards is calculated based on the current voting power of a user and the total voting power at that moment. However, since totalVotingPower is updated immediately when voting power is minted, users who lock tokens after rewards have already been distributed can still claim those rewards, even though they were not eligible at the time.

Immediate Voting Power Inflation

The totalVotingPower is simply the total supply of voting tokens, which updates instantly when new tokens are locked:

function getTotalVotingPower() external view override returns (uint256) {
return totalSupply();
}

This issue is demonstrated in the veRAACToken.test.js proof of concept, where a user receives voting power immediately upon locking RAAC tokens:

it("should allow users to create a lock with valid parameters", async () => {
const amount = ethers.parseEther("1000");
const duration = 365 * 24 * 3600; // 1 year
// Create lock
const tx = await veRAACToken.connect(users[0]).lock(amount, duration);
// Wait for transaction
const receipt = await tx.wait();
// Verify voting power is immediately available
let power = await veRAACToken.getVotingPower(users[0].address);
expect(power).to.be.gt(0);
console.log("Voting power received immediately:", power);
});

Since the reward calculation in FeeCollector uses the current totalVotingPower and userVotingPower, a user who locks tokens after a reward distribution can still claim a portion of the past rewards, which inflates their reward earnings unfairly.

Impact

  • Users can unfairly claim past rewards by locking tokens right before claiming.

  • Rewards become inflated, leading to incorrect accounting and dilution of rewards for legitimate holders.

  • totalVotingPower is immediately inflated, which skews reward distribution calculations.

Tools Used

N/A

Recommendations

  • Implement checkpoint-based reward distribution where rewards are only claimable for users who had voting power before the reward distribution occurred.

  • Store a historical snapshot of totalVotingPower at the time of each reward distribution instead of using the current value.

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.