Core Contracts

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

The gauge vote tracking mechanism is flawed, allowing stale votes influencing the reward distribution

Summary

Users lock RAAC tokens in exchange for veRAAC voting power, which decays linearly over time until reaching zero at the end of the lock period. However, the votes allocated to gauges do not decay—they remain valid even after a user's lock has expired. This allows expired voters to retain influence over reward distribution.

Vulnerability Details

In a voting-escrow system, voting power follows a linear decay model, defined as:

votingPower = bias − slope × timePassed

Where:

  • Bias: The initial voting power at the time of locking.

  • Slope: The rate at which voting power decreases per second.

Users call vote to allocate their veRAAC voting power to different gauges, determining how rewards are distributed. The specified weight of votingPower is added to the total gauge weight:
gauges[gauge].weight = newGaugeWeight;

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);
}
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);
@> g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
}

Note: There are other issues in the code snippets reported in separate submissions. These issues will be mentioned throughout this report to aid the reader in understanding how the system should behave.
The first extra issue is that the
balanceOf method doesn't return the decaying voting power. Instead, a method that computes the dynamic voting power (based on bias and slope) should be used.

The distributeRewards function uses _calculateReward to calculate the reward amount for a gauge. The reward is a percentage determined by the gauge's weight and the total weight allocated to all gauges.

gaugeRewards = totalRewards * gaugeWeight / totalWeight.

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

Note: typeWeights and _calculateRWAEmission / _calculateRAACEmission have same purpose and one of them should be removed.

The issue with this reward distribution system is that the gauge's weight doesn't decrease in accordance with the voter's actual votingPower.

Imagine the following scenario:

  • Alice locks 1000 RAAC tokens for 4 years, receiving 1000 veRAAC voting power.

  • Alice votes to allocate 1000 voting power to gaugeA.

  • After 4 years, Alice’s veRAAC decays to 0, but her gauge votes remain active.

  • Bob locks 1000 RAAC tokens for 4 years, receiving 1000 veRAAC voting power.

  • Bob votes for gaugeB, but Alice’s expired votes are still counted, giving her unfair influence while she can freely unlock her RAAC tokens.

This results in stale votes influencing the system, making it unfair for active users.

Impact

Decayed votes retain voting power, allowing them to influence gauge rewards distribution indefinitely.
Unfair reward allocation distorts the intended function of the gauge system.

Recommendations

The GaugeController contract should implement a decaying gauge weight mechanism similar to the veRAACToken decay model.

Updates

Lead Judging Commences

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

GaugeController doesn't decay gauge vote weights when veRAAC voting power expires, allowing users to influence reward distribution with expired voting power

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

GaugeController doesn't decay gauge vote weights when veRAAC voting power expires, allowing users to influence reward distribution with expired voting power

Support

FAQs

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