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;
uint256 performanceShare = amount * 20 / 100;
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;
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;
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.