Core Contracts

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

In `GaugeController` time periods are not used for rewards distribution

Summary

The GaugeController contract implements time-weighted reward distribution to distribute rewards to the Gauges. However these are never used in actuall rewards calculations.

Vulnerability Details

We the Gauge is added new period is created with expected distribution parameters.

function addGauge(
address gauge,
GaugeType gaugeType,
uint256 initialWeight
) external onlyGaugeAdmin {
if (gauges[gauge].lastUpdateTime != 0) revert GaugeAlreadyExists();
if (gaugeType != GaugeType.RWA && gaugeType != GaugeType.RAAC) {
revert InvalidGaugeType();
}
// Use minimum weight (1) for period tracking if initialWeight is 0
uint256 periodWeight = initialWeight == 0 ? 1 : initialWeight;
uint256 duration = gaugeType == GaugeType.RWA ? 30 days : 7 days;
gauges[gauge] = Gauge({
weight: initialWeight,
typeWeight: 0,
lastUpdateTime: block.timestamp,
gaugeType: gaugeType,
isActive: true,
lastRewardTime: block.timestamp
});
// Initialize period with current timestamp
TimeWeightedAverage.Period storage period = gaugePeriods[gauge];
TimeWeightedAverage.createPeriod(
period,
block.timestamp, // Start from current timestamp
duration,
periodWeight,
periodWeight
);
_gaugeList.push(gauge);
emit GaugeAdded(gauge, gaugeType);
}

To distribute the rewards to gauges the distributeRewards function is used.

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

This function calls internal _calculateReward. As we can see it does not use created distribution periods but distributes rewards based on gauge share and period emission.

function _calculateReward(address gauge) internal view returns (uint256) {
Gauge storage g = gauges[gauge];
uint256 totalWeight = getTotalWeight();
if (totalWeight == 0) return 0;
uint256 gaugeShare = (g.weight * WEIGHT_PRECISION) / totalWeight;
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();
return (periodEmission * gaugeShare * typeShare) / (WEIGHT_PRECISION * WEIGHT_PRECISION);
}

Impact

Rewards should be distributed based on created distribution periods however they are distributed based on gauge share and period emission.

Tools Used

Manual Review, Hardhat

Recommendations

Change the rewards calculation function so that rewards are based on implemented distribution periods.

Updates

Lead Judging Commences

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

GaugeController uses current gauge weights instead of time-weighted averages for reward calculations

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

GaugeController uses current gauge weights instead of time-weighted averages for reward calculations

Support

FAQs

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