Core Contracts

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

Users can manipulate `distributeRewards` and distribute the max rewards every cycle

Summary

Users can spam call distributeRewards in order to distribute more rewards than the gauge percentage allocation allows

Vulnerability Details

Rewards are based off a couple of factors

  • gauge weight

  • typeWeights[RWA/RAAC] distribution, where currently it's 50% RWA and 50% RAAC

Where in order to distribute the max emissions both values need to be maxed out or only 1 gauge to exist.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/gauges/GaugeController.sol#L360

function _calculateReward(address gauge) internal view returns (uint256) {
Gauge storage g = gauges[gauge];
uint256 totalWeight = getTotalWeight();
if (totalWeight == 0) return 0;
// weight * 10k / total
uint256 gaugeShare = (g.weight * WEIGHT_PRECISION) / totalWeight;
// typeWeights[RWA/RAAC] * 10k / 10k
uint256 typeShare = (typeWeights[g.gaugeType] * WEIGHT_PRECISION) / MAX_TYPE_WEIGHT;
// Calculate period emissions based on gauge type
uint256 periodEmission = g.gaugeType == GaugeType.RWA
? _calculateRWAEmission()
: _calculateRAACEmission();
// (weight * 10k / total) * typeWeights[RWA/RAAC] * periodEmission / (10k * 10k)
return (periodEmission * gaugeShare * typeShare) / (WEIGHT_PRECISION * WEIGHT_PRECISION);
}

However currently distributeRewards can be called instantly as many times as users want in order to always distribute the max amount of rewards.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/gauges/GaugeController.sol#L323

function distributeRewards(
address gauge
) external override nonReentrant whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
if (!gauges[gauge].isActive) revert GaugeNotActive();
// This can be called constantly, no time delay
uint256 reward = _calculateReward(gauge);
if (reward == 0) return;
IGauge(gauge).notifyRewardAmount(reward);
emit RewardDistributed(gauge, msg.sender, reward);
}

Impact

Gauges are manipulated to distribute more rewards than their each percentage allocation allows to
Gauge rewards are only capped by their max emissions and their weight or typeWeights does not matter.
Users can spam distributeRewards in order to distribute rewards a couple of time and max out each gauge max reward emissions.

Tools Used

Manual review

Recommendations

Put a timer on distributeRewards or add admin modifiers in order to prevent users from calling it constantly. Else the whole weight gauge distribution does not matter if we are always distributing each max gauge.

Updates

Lead Judging Commences

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

GaugeController's distributeRewards lacks time-tracking, allowing attackers to repeatedly distribute full period rewards until hitting emission caps

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

GaugeController's distributeRewards lacks time-tracking, allowing attackers to repeatedly distribute full period rewards until hitting emission caps

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.