Core Contracts

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

Voting System Doesn’t Affect Rewards, Breaking Weight Tracking in BaseGauge

Summary

In the BaseGauge contract, user votes don’t change the weights used to calculate rewards. The protocol aims to track user and gauge weights, but the voting system (voteDirection) updates vote totals without connecting them to the weights that decide how rewards are shared. This means votes don’t influence rewards as they should, affecting RWAGauge and RAACGauge too.

Vulnerability Details

This issue starts in the BaseGauge contract, which is the foundation for other contracts like RWAGauge and RAACGauge. The voteDirection function lets users vote on a “direction” (like how rewards should be split). Here’s what it does:

function voteDirection(uint256 direction) public whenNotPaused updateReward(msg.sender) {
if (direction > 10000) revert InvalidWeight();
uint256 votingPower = IERC20(IGaugeController(controller).veRAACToken()).balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
totalVotes = processVote(userVotes[msg.sender], direction, votingPower, totalVotes);
emit DirectionVoted(msg.sender, direction, votingPower);
}

It takes a user’s vote (e.g., a number from 0 to 10,000) and their voting power (based on the veToken balance).
It updates totalVotes ( Total votes across all users) and userVotes (details of each user’s vote).
But it stops there, it doesn’t update anything about weights used for rewards.

Weights are tracked in a part of the contract called PeriodState, which includes votingPeriod

struct PeriodState {
TimeWeightedAverage.Period votingPeriod; // Voting period
uint256 emission; // Total period emission cap
uint256 distributed; // Amount distributed this period
uint256 periodStartTime; // Start timestamp of current period
}

votingPeriod is supposed to track weights over time, but it doesn’t use totalVotes or userVotes.

The contract also has a function called updatePeriod that refreshes weights for each new time period. This is where Weights Should Change, But Don’t

function updatePeriod() external override onlyController {
uint256 currentTime = block.timestamp;
uint256 periodEnd = periodState.periodStartTime + getPeriodDuration();
if (currentTime < periodEnd) revert PeriodNotElapsed();
uint256 periodDuration = getPeriodDuration();
uint256 avgWeight = periodState.votingPeriod.calculateAverage(periodEnd);
uint256 nextPeriodStart = ((currentTime / periodDuration) + 2) * periodDuration;
periodState.distributed = 0;
periodState.periodStartTime = nextPeriodStart;
TimeWeightedAverage.createPeriod(periodState.votingPeriod, nextPeriodStart, periodDuration, avgWeight, WEIGHT_PRECISION);
}

It takes the average weight from the last period (avgWeight) and uses it for the next period.
The Problem is that It doesn’t look at totalVotes or user votes from voteDirection. The new weights ignore what users voted for.
Another function, getTimeWeightedWeight, shows the current weight

function getTimeWeightedWeight() public view override returns (uint256) {
return periodState.votingPeriod.calculateAverage(block.timestamp);
}

Rewards depend on a user’s weight, calculated in getUserWeight:

function getUserWeight(address account) public view virtual returns (uint256) {
uint256 baseWeight = _getBaseWeight(account);
return _applyBoost(account, baseWeight);
}

_getBaseWeight gets the gauge’s weight from another contract (IGaugeController):

function _getBaseWeight(address account) internal view virtual returns (uint256) {
return IGaugeController(controller).getGaugeWeight(address(this));
}

According to the protocol, Rewards are intended to be distributed based on user weights. But totalVotes from voteDirection isn’t sent to the controller or used here.

Impact

  1. Users vote on how rewards should be shared (like yield or emission direction), but their votes don’t matter.

  2. Rewards stay based on old weights or controller settings, not what users want.
    The protocol aims to track weights, but without votes updating them, it fails to do this properly.

  3. People using RWAGauge or RAACGauge might think their votes change rewards, but they don’t, which could upset them or make the system seem unfair.

Tools Used

Manual Review

Recommendations

  1. Change voteDirection to update votingPeriod with totalVotes.

  2. Use the existing _updateWeights function to tie votes to votingPeriod.

Updates

Lead Judging Commences

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

BaseGauge::stake, voteDirection and withdraw don't call _updateWeights, causing outdated time-weighted average calculations that lead to unfair reward distribution

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

BaseGauge::stake, voteDirection and withdraw don't call _updateWeights, causing outdated time-weighted average calculations that lead to unfair reward distribution

Support

FAQs

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

Give us feedback!