Core Contracts

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

Incorrect voting power calculation using non-decaying balance leads to unfair gauge weight allocation

Description

The GaugeController::vote function calculates voting power using veRAACToken.balanceOf() which represents the locked token balance without accounting for time-based decay. This allows users to maintain voting influence proportional to their original locked amount even after their actual voting power should have decayed over time. The system should instead use veRAACToken.getVotingPower() which properly accounts for time-based decay in the voting power calculation.

Proof of Concept

Add this test case in GaugeController.test.js:

it("demonstrates non-decaying voting power impact", async () => {
// Setup initial lock with full voting power
await veRAACToken.mint(user1.address, ethers.parseEther("1000"));
// Initial vote on RWA gauge
await gaugeController
.connect(user1)
.vote(await rwaGauge.getAddress(), WEIGHT_PRECISION);
const initialRWAWeight = await gaugeController.getGaugeWeight(
await rwaGauge.getAddress()
);
// Fast-forward 2 weeks (voting power should decay but balance remains)
await time.increase(15 * 24 * 3600);
await network.provider.send("evm_mine");
// Vote on RAAC gauge with same weight using undecayed balance
await gaugeController
.connect(user1)
.vote(await raacGauge.getAddress(), WEIGHT_PRECISION);
// Check weights
const finalRWAWeight = await gaugeController.getGaugeWeight(
await rwaGauge.getAddress()
);
const raacWeight = await gaugeController.getGaugeWeight(
await raacGauge.getAddress()
);
const totalWeight = await gaugeController.getTotalWeight();
// Corrected assertions
expect(totalWeight).to.equal(ethers.parseEther("2000")); // Total is sum of both gauges
expect(finalRWAWeight).to.equal(ethers.parseEther("1000")); // Weight remains on RWA
expect(raacWeight).to.equal(ethers.parseEther("1000")); // New weight added to RAAC
});

Impact

Medium Severity - Creates unfair gauge weight allocations where users with expired or decaying locks maintain disproportionate voting power. Distorts reward distribution and protocol incentives over time.

Recommendation

  1. Use time-decayed voting power:

contracts/core/governance/gauges/GaugeController.sol
function vote(address gauge, uint256 weight) external {
- uint256 votingPower = veRAACToken.balanceOf(msg.sender);
+ uint256 votingPower = veRAACToken.getVotingPower(msg.sender);
}
  • Add voting power snapshots:

mapping(address => uint256) public votingPowerSnapshots;
function vote(address gauge, uint256 weight) external {
uint256 votingPower = veRAACToken.getVotingPower(msg.sender);
votingPowerSnapshots[msg.sender] = votingPower;
// Use snapshot for weight calculations
}
Updates

Lead Judging Commences

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

BaseGauge::_applyBoost, GaugeController::vote, BoostController::calculateBoost use balanceOf() instead of getVotingPower() for vote-escrow tokens, negating time-decay mechanism

Support

FAQs

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