Core Contracts

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

`GaugeController::_updateGaugeWeight()` is flawed

Summary

The RAAC protocol has created a GaugeController to control gauge weights and reward distribution for RWA and RAAC emissions. Users who have veRAACTokens can vote on specific gauges (RWA and RAAC). However, with the current logic in the _updateGaugeWeight(), gauge weights get distorted when votes come in, eventually leading to a DoS of the action, and what is worse to invalid reward calculations, as they depend on the gauge weights.

Vulnerability Details

Gauges have specific weights assigned to them, which range from 1 to 10000 (i.e. percentage-based). Whenever users vote, the gauge weight is updated, based on the users voting power.

function vote(address gauge, uint256 weight) external override whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
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);
}

The issue comes from how the gauge weight is updated:

function _updateGaugeWeight(
address gauge,
uint256 oldWeight,
uint256 newWeight,
uint256 votingPower
) internal {
Gauge storage g = gauges[gauge];
uint256 oldGaugeWeight = g.weight;
uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
+ (newWeight * votingPower / WEIGHT_PRECISION); // @audit - gauge weights are in BIPS, voting powe is with 18 decimals -> revert after first vote
g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
}

Gauge weights and user-related weights are in BIPS (i.e. 0-10000), however, the votingPower is the veRAACToken balance, and the veRAACToken has 18 decimals, which distorts the gauge weight value (making it > BIPS), and eventually leading to an overflow.

Example:

  1. There is a RWA Gauge with a weight of 8000.

  2. Alice has 100e18 veRAACTokens and votes for this gauge for 6000

  3. In _updateGaugeWeight() -> newGaugeWeight = 8000 - 0 + (6000 * 100e18 / 10000) = 8000 + 60e18 ~ 60e18. The gauge weight is already invalid.

  4. Alice decides to lock some more RAACTokens and now has 200e18 veRAACTokens, and decides to vote again, this time to set the vote to `8000.

  5. In _updateGaugeWeight() -> newGaugeWeight = 60e18 - (6000 * 200e18 / 10000) + (8000 * 200e18 / 10000). This will revert as 60e18 - 120e18 will revert with an underflow, even before reachin the + (8000 * 200e18 / 10000).

Even if the revert case is not reached, the invalid gauge weights are then used to calculate the corresponding rewards and gauge distributions:

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

Gauge voting produces incorrect gauge weights and leads to DoS.

Tools Used

Manual review.

Recommendations

Fix is not trivial, as votingPower needs to be re-scaled to not be with e18 notation.

Updates

Lead Judging Commences

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

GaugeController::_updateGaugeWeight uses current voting power for both old and new vote calculations, causing underflows when voting power increases and incorrect gauge weights

Support

FAQs

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

Give us feedback!