Core Contracts

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

Gauge Weight Manipulation Through Precision Overflow

Summary

The gauge voting weight update mechanism allows total weight changes that could exceed the maximum precision (10000). This breaks the core voting power accounting and could lead to gauge weight manipulation.

The issue is in the GaugeController's vote function where a user's voting power is used to calculate gauge weights. When a user with significant voting power changes their vote, the total weight delta can exceed the WEIGHT_PRECISION constant (10000 basis points), violating a core system invariant.

// Example values:
votingPower = 20000e18
oldWeight = 0
newWeight = 10000 // Max weight
// Results in weight change > 10000

Vulnerability Details

When a veToken holder votes on a gauge, their voting power acts as a multiplier on the weight change. Think of it like a megaphone, the more veTokens you hold, the louder your vote becomes.

The GaugeController's _updateGaugeWeight() tracks these weights with a precision of 10000 basis points (100%). In normal operation, when users vote to adjust gauge weights, the changes should stay within these bounds. However, the current implementation creates an interesting dynamic where large veToken holders can amplify their vote beyond the intended limits.

function _updateGaugeWeight(
address gauge,
uint256 oldWeight,
uint256 newWeight,
uint256 votingPower
) internal {
Gauge storage g = gauges[gauge];
uint256 oldGaugeWeight = g.weight;
// 🚩 Critical calculation without bounds checking
uint256 newGaugeWeight = oldGaugeWeight - // 🚩 votingPower scaling can exceed WEIGHT_PRECISION
(oldWeight * votingPower / WEIGHT_PRECISION) // ⬇️ Removes old voting power influence
+ (newWeight * votingPower / WEIGHT_PRECISION); // ⬆️ Adds new voting power influence
g.weight = newGaugeWeight; // 💥 Can exceed WEIGHT_PRECISION here!
g.lastUpdateTime = block.timestamp; // ⏰ Updates timestamp
}

Let's walk through this example: A user holding 20000e18 veTokens (twice the WEIGHT_PRECISION) decides to vote on a gauge. When they set their vote weight to 10000 (100%), the actual impact on the gauge's weight becomes 20000, double what the protocol intended. This breaks a fundamental assumption that gauge weights should always normalize to 100%.

The impact is precise and measurable. A gauge receiving twice the intended weight would capture double its fair share of emissions. For a protocol distributing 500,000 RAAC tokens weekly through the RAACGauge, this means a single large holder could redirect an extra 250,000 tokens per week to their preferred gauge.

This vulnerability shares root with historical DeFi voting exploits, but with a unique twist. Instead of manipulating vote timing or lock duration, it exploits the mathematical relationship between voting power and weight calculation.

This becomes particularly interesting when considering the protocol's real estate focus. Just as real estate voting rights shouldn't allow a single property owner to have 200% voting power, gauge weights need similar bounds to maintain fair emission distribution.

Impact

  • Gauge weights can exceed 100%, breaking emission calculations

  • Users with large veToken holdings can manipulate gauge weights

  • Breaks core invariants of the gauge voting system

This could allow users with large veToken holdings to have outsized influence on gauge weights, potentially directing too many emissions to specific gauges. The precision overflow breaks core assumptions about weight normalization.

Recommendations

Add bounds checking on weight deltas preserving the protocol's core invariants around gauge weight normalization.

  1. Calculates the weight delta first

  2. Validates it against WEIGHT_PRECISION

  3. Proceeds with the weight update only if safe

  4. Maintains all existing functionality with added protection

Here's how.

function _updateGaugeWeight(
address gauge,
uint256 oldWeight,
uint256 newWeight,
uint256 votingPower
) internal {
Gauge storage g = gauges[gauge];
// 🔍 Calculate weight change impact
uint256 weightDelta = (newWeight * votingPower / WEIGHT_PRECISION) -
(oldWeight * votingPower / WEIGHT_PRECISION);
// ⚡ Core protection: Enforce weight change limits
require(weightDelta <= WEIGHT_PRECISION, "Weight change exceeds max");
// 📊 Update gauge weight with validated delta
uint256 oldGaugeWeight = g.weight;
uint256 newGaugeWeight = oldGaugeWeight -
(oldWeight * votingPower / WEIGHT_PRECISION) +
(newWeight * votingPower / WEIGHT_PRECISION);
// ✅ Safe state update
g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
// 🔔 Events would be emitted here
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!