Core Contracts

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

Missing invocation of `BaseGauge::updatePeriod` will lead to failure of reward distribution

Summary

The reward distribution mechanism in GaugeController::_distributeToGauges allocates 80% of gauge shares based on each gauge's weight. Each gauge has an emissionCap, which limits its total distributed rewards. However, GaugeController::_distributeToGauges does not check these caps before distributing rewards. If a single gauge exceeds its emissionCap, the entire transaction reverts, preventing all gauges from receiving their share.

While BaseGauge::updatePeriod, callable only by GaugeController, can reset a gauge’s distributed rewards to 0, this function is never invoked within GaugeController, effectively making the distribution mechanism DoSed.

Vulnerability Details

Here is GaugeController::_distributeToGauges

function _distributeToGauges(
GaugeType gaugeType,
uint256 amount
) internal {
uint256 totalTypeWeight = 0;
uint256[] memory gaugeWeights = new uint256[](_gaugeList.length);
...SKIP...
if (totalTypeWeight == 0 || activeGaugeCount == 0) return;
// Second pass: distribute rewards
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
uint256 gaugeShare = (amount * gaugeWeights[i]) / totalTypeWeight;
if (gaugeShare > 0) {
@> IGauge(gauge).notifyRewardAmount(gaugeShare);
}
}
}
}

The function traverses the gauges array and distributes the gauge share amounts to the corresponding gauges via BaseGauge::notifyRewardAmount.

function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0)) {
if (amount > periodState.emission) revert RewardCapExceeded();
@> rewardRate = notifyReward(periodState, amount, periodState.emission, getPeriodDuration());
@> periodState.distributed += amount;
uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() > balance) {
revert InsufficientRewardBalance();
}
lastUpdateTime = block.timestamp;
emit RewardNotified(amount);
}

This function accumulates distributed rewards into periodState.distributed and checks if the new total exceeds emissionCap:

function notifyReward(
PeriodState storage state,
uint256 amount,
uint256 maxEmission,
uint256 periodDuration
) internal view returns (uint256) {
if (amount > maxEmission) revert RewardCapExceeded();
@> if (amount + state.distributed > state.emission) {
@> revert RewardCapExceeded();
}
uint256 rewardRate = amount / periodDuration;
if (rewardRate == 0) revert ZeroRewardRate();
return rewardRate;
}

If amount + state.distributed exceeds emissionCap, execution reverts, causing GaugeController::_distributeToGauges to fail entirely.

Since BaseGauge::updatePeriod (which resets periodState.distributed to 0) is never called within GaugeController, the issue persists indefinitely, rendering GaugeController::distributeRevenue non-functional.

Impact

GaugeController::distributeRevenue is responsible for allocating 80% of rewards to gauges through _distributeToGauges. Since the function can be DoSed due to unhandled emissionCap limits, this substantial share of rewards could remain undistributed, severely impacting the protocol's reward distribution mechanism.

Tools Used

Manual Review, VSCode

Recommendations

Solution 1: Integrate BaseGauge::updatePeriod in GaugeController:

Modify GaugeController to invoke BaseGauge::updatePeriod when a gauge reaches its emissionCap.

Solution 2: Add an Independent Function to Reset periodState.distributed

Add a function in GaugeController.sol to allow manual resetting:

function updateGaugePeriod(address gauge) public {
IGauge(gauge).updatePeriod();
}
Updates

Lead Judging Commences

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

GaugeController::updatePeriod doesn't call the gauge's updatePeriod function, preventing periodState.distributed from resetting and eventually causing distributeRewards to permanently fail

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

GaugeController::updatePeriod doesn't call the gauge's updatePeriod function, preventing periodState.distributed from resetting and eventually causing distributeRewards to permanently fail

Support

FAQs

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

Give us feedback!