Core Contracts

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

Incorrect gauge weight calculation due to voting power changes leads to unfair reward distribution

Relevant Context

The GaugeController contract implements a voting system where users can vote on gauges using their veToken balance as voting power. The gauge weights determine how rewards are distributed among different gauges. Users can vote by specifying a weight between 0 and 10000 (100%).

Finding Description

The GaugeController#vote and GaugeController#_updateGaugeWeight functions incorrectly calculate gauge weights when a user's voting power changes between votes. The current implementation uses the user's current voting power both for removing the old weight and adding the new weight, instead of using the historical voting power for removing the old weight.

When calculating the new gauge weight, the contract:

  1. Subtracts oldWeight * currentVotingPower / WEIGHT_PRECISION

  2. Adds newWeight * currentVotingPower / WEIGHT_PRECISION

This assumes the user's voting power hasn't changed since their last vote, which may not be true as veToken balances can change over time through various mechanisms (locking, unlocking, etc.).

Impact Explanation

High. The incorrect gauge weight calculation directly affects reward distribution across the protocol. When gauge weights are incorrectly calculated, it leads to unfair reward distribution where some gauges receive more or fewer rewards than they should based on the actual voting power allocated to them.

Likelihood Explanation

High. Changes in voting power are common in veToken systems due to the time-locked nature of the tokens. Users can increase their voting power by locking more tokens or see it decrease as locks expire.

Proof of Concept

  1. User has 10,000 voting power and votes on gauge A with weight 50%

    • Gauge A weight = 5,000 (50% * 10,000)

  2. User's voting power decreases to 5,000

  3. User votes again on gauge A with weight 50%

    • Current implementation:

      • Old contribution removed: 50% * 5,000 = 2,500 (incorrect)

      • New contribution added: 50% * 5,000 = 2,500

      • Final gauge weight = 5,000 - 2,500 + 2,500 = 5,000 (incorrect)

    • Expected implementation:

      • Old contribution removed: 50% * 10,000 = 5,000

      • New contribution added: 50% * 5,000 = 2,500

      • Final gauge weight = 5,000 - 5,000 + 2,500 = 2,500 (correct)

Recommendation

Store the actual voting power contribution instead of just the weight percentage:

- mapping(address => mapping(address => uint256)) public userGaugeVotes;
+ struct UserVote {
+ uint256 weight;
+ uint256 votingPower;
+ }
+ mapping(address => mapping(address => UserVote)) public userGaugeVotes;
function vote(address gauge, uint256 weight) external override whenNotPaused {
// ... existing checks ...
uint256 votingPower = veRAACToken.balanceOf(msg.sender);
- uint256 oldWeight = userGaugeVotes[msg.sender][gauge];
+ UserVote storage userVote = userGaugeVotes[msg.sender][gauge];
+ uint256 oldVotingPower = (userVote.weight * userVote.votingPower) / WEIGHT_PRECISION;
+ uint256 newVotingPower = (weight * votingPower) / WEIGHT_PRECISION;
- userGaugeVotes[msg.sender][gauge] = weight;
+ userVote.weight = weight;
+ userVote.votingPower = votingPower;
- _updateGaugeWeight(gauge, oldWeight, weight, votingPower);
+ _updateGaugeWeight(gauge, oldVotingPower, newVotingPower);
}
function _updateGaugeWeight(
address gauge,
- uint256 oldWeight,
- uint256 newWeight,
- uint256 votingPower
+ uint256 oldVotingPower,
+ uint256 newVotingPower
) internal {
Gauge storage g = gauges[gauge];
uint256 oldGaugeWeight = g.weight;
- uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
- + (newWeight * votingPower / WEIGHT_PRECISION);
+ uint256 newGaugeWeight = oldGaugeWeight - oldVotingPower + newVotingPower;
g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
}
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

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!