Core Contracts

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

No snapshot of voting power issue

Summary

Reward calculations must use historical staking balances, not current values.

veRAACToken.getVotingPower() returns current balances, not balances at distribution time.

Users can manipulate rewards by:

  1. Staking right before claiming → Inflate share.

  2. Unstaking immediately after → Repeat.

Vulnerability Details

// Uses CURRENT voting power for historical distributions
function _processDistributions() internal {
uint256 totalVeRAACSupply = veRAACToken.getTotalVotingPower(); // Current supply
TimeWeightedAverage.createPeriod(..., totalVeRAACSupply); // No snapshot
}
// Uses CURRENT user balance for all historical rewards
function _calculatePendingRewards(address user) internal view {
uint256 userVotingPower = veRAACToken.getVotingPower(user); // Current balance
uint256 totalVotingPower = veRAACToken.getTotalVotingPower(); // Current supply
uint256 share = (totalDistributed * userVotingPower) / totalVotingPower; // Flawed
}

Reward calculations use real-time voting power instead of historical snapshots, allowing users to manipulate their share by changing their veRAAC balance after distributions but before claiming.

First Distribution:

Total veRAAC supply = 100

totalDistributed = 100

Attacker stakes 100 veRAAC and claims 100 tokens (legitimate)

Attacker unstakes all 100 veRAAC → Total supply = 0

Second Distribution:

FeeCollector distributes 100 tokens to "veRAAC holders"

Since total supply = 0, all 100 tokens go to treasury (totalDistributed still increments to 200)

Attacker re-stakes 100 veRAAC (total supply = 100)

Claims: (100 * 200) / 100 = 200 tokens

Steals 100 tokens from Distribution 2 when they had 0 balance!

The root cause can be seen below:

// Tracks cumulative rewards but NOT historical state
uint256 public totalDistributed;
// Always uses live balances
function _calculatePendingRewards() {
// Fetches CURRENT balances
userVotingPower = veRAACToken.getVotingPower(user);
totalVotingPower = veRAACToken.getTotalVotingPower();
}
// _processDistributions()
TimeWeightedAverage.createPeriod(
distributionPeriod,
block.timestamp + 1,
7 days,
shares[0],
totalVeRAACSupply // No snapshot of supply at distribution time
);

Impact

Protocol distributes 500K tokens to legitimate stakers.

Attacker frontruns distribution → stakes → claims → unstakes.

Steals rewards from periods they weren't staked.

Tools Used

Foundry

Recommendations

Use veRAAC's historical balance snapshots for accurate rewards

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!