Core Contracts

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

Lack of active status verification in gauge voting process results in skewed reward distribution

Summary

The current implementation of the voting mechanism in the GaugeController contract allows users to cast votes for gauges that are inactive. This inconsistency can lead to skewed reward distribution.

Vulnerability Details

The vote() function does not verify if the gauge is active before allowing votes to be cast.

function vote(address gauge, uint256 weight) external override whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
>> // @audit-info No verification if gauge is active
if (weight > WEIGHT_PRECISION) revert InvalidWeight();
uint256 votingPower = veRAACToken.balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
uint256 oldWeight = userGaugeVotes[msg.sender][gauge];
userGaugeVotes[msg.sender][gauge] = weight;
_updateGaugeWeight(gauge, oldWeight, weight, votingPower);
emit WeightUpdated(gauge, oldWeight, weight);
}

This oversight means that users can allocate weights to gauges that are not operational.

When a user votes, their vote updates the userGaugeVotes mapping, which tracks how much weight each user has assigned to each gauge.
Now notice that the _updateGaugeWeight() called proceeds to update the weight of the entire gauge based on this vote:

uint256 oldGaugeWeight = g.weight;
>> uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
+ (newWeight * votingPower / WEIGHT_PRECISION);
>> g.weight = newGaugeWeight;

Now, if a gauge has accumulated a significant amount of votes while inactive, it will receive a disproportionate share of rewards when it is finally activated.

>> uint256 reward = _calculateReward(gauge);
if (reward == 0) return;
>> IGauge(gauge).notifyRewardAmount(reward);

Here is _calculateReward():

// @audit-info sum of weights for all gauges
>> uint256 totalWeight = getTotalWeight();
if (totalWeight == 0) return 0;
// @audit-info The g.weight updated during voting is now used to detemine gaugeShare
>> 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();
// @audit-info Value returned depends on the gaugeShare
>> return (periodEmission * gaugeShare * typeShare) / (WEIGHT_PRECISION * WEIGHT_PRECISION);

Impact

When the inactive gauge is reactivated, the weights assigned during its inactive state will still be in effect. This could lead to disproportionate share of rewards distributed to it.

Tools Used

Manual Review

Recommendations

Modify the vote() function to include a check for the gauge's active status before allowing votes.

function vote(address gauge, uint256 weight) external override whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
+ Gauge storage g = gauges[gauge];
+ if (!g.isActive) revert GaugeNotActive(); // Check if gauge is active
// ... existing code ...
}
Updates

Lead Judging Commences

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

GaugeController::vote allows users to waste voting power on inactive gauges that don't receive rewards

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

GaugeController::vote allows users to waste voting power on inactive gauges that don't receive rewards

Support

FAQs

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