Core Contracts

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

After a gauge is shutdown in emergency , the gauge loses it accumulated rewards

Summary

When a gauge is shutdown in emergency using emergencyShutdown() and later rewards are distributed the gauges loses accumulated rewards even if he had the most weight .

Vulnerability Details

For the sake of simplicity let say a gauge is shutdown in emergency for whatever reason, and shortly after ( maybe 1 minutes ) the revenue are distributed to gauges, we can see that the shutdown gauge gets nothing as revenue even if he was the gauge with the most weight, he will lose his revenue to the second most weight gauge.

See the emergencyShutdown function

function emergencyShutdown(address gauge) external {
if (!hasRole(EMERGENCY_ADMIN, msg.sender)) revert UnauthorizedCaller();
if (!isGauge(gauge)) revert GaugeNotFound();
gauges[gauge].isActive = false;
emit EmergencyShutdown(gauge, msg.sender);
}

It does not distribute revenue to the gauge before shutting it down.

And the distributeRevenue function only distribute revenue to active gauges

function distributeRevenue(
GaugeType gaugeType,
uint256 amount
) external onlyRole(EMERGENCY_ADMIN) whenNotPaused {
if (amount == 0) revert InvalidAmount();
uint256 veRAACShare = amount * 80 / 100; // 80% to veRAAC holders
uint256 performanceShare = amount * 20 / 100; // 20% performance fee
revenueShares[gaugeType] += veRAACShare;
_distributeToGauges(gaugeType, veRAACShare);
emit RevenueDistributed(gaugeType, amount, veRAACShare, performanceShare);
}
function _distributeToGauges(
GaugeType gaugeType,
uint256 amount
) internal {
uint256 totalTypeWeight = 0;
uint256[] memory gaugeWeights = new uint256[](_gaugeList.length);
uint256 activeGaugeCount = 0;
// First pass: calculate total weight and store gauge weights
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
gaugeWeights[i] = gauges[gauge].weight;
totalTypeWeight += gaugeWeights[i];
activeGaugeCount++;
}
}
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);
}
}
}
}

Impact

Gauges that were shutdown using emergencyShutdown function gets no revenue even if they were the top gauge

Tools Used

Manual review

Recommendations

After emergency shutdown of a gauge the distributeRevenue should be called immediately within the same transaction so that revenue are distributed equally among all gauges. If this is not done the owner can potentially abuse this flaw to shutdown gauges prior to revenue distribution in order to forfeit rewards for some gauges.

Updates

Lead Judging Commences

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

GaugeController::emergencyShutdown fails to distribute accumulated rewards before deactivating gauge, causing permanent loss of entitled funds

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

GaugeController::emergencyShutdown fails to distribute accumulated rewards before deactivating gauge, causing permanent loss of entitled funds

Appeal created

anonymousjoe Auditor
7 months ago
helium Submitter
7 months ago
inallhonesty Lead Judge
7 months ago
inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

GaugeController::emergencyShutdown fails to distribute accumulated rewards before deactivating gauge, causing permanent loss of entitled funds

Support

FAQs

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

Give us feedback!