Summary
In GaugeController#vote() , if user first votes with a smaller weight (e.g., 2000), then acquires more veRAACToken and tries to vote again with a larger weight (e.g., 5000), the calculation in _updateGaugeWeight() can revert due to an underflow.
Vulnerability Details
Incorrect Order of Operations in _updateGaugeWeight()
uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
+ (newWeight * votingPower / WEIGHT_PRECISION);
If votingPower increases between votes, the subtraction can exceed oldGaugeWeight, causing an underflow and revert.
Proof Of Code
Testcode is written in GaugeController.test.js.
describe("Newspace POC", () => {
beforeEach(async () => {
await veRAACToken.mint(user1.address, ethers.parseEther("1000"));
});
it("newspace POC test", async () => {
await gaugeController.connect(user1).vote(await rwaGauge.getAddress(), 2000);
await veRAACToken.mint(user1.address, ethers.parseEther("1000"));
await gaugeController.connect(user1).vote(await rwaGauge.getAddress(), 5000);
const weight = await gaugeController.getGaugeWeight(await rwaGauge.getAddress());
expect(weight).to.be.gt(0);
});
});
Impact
Core function broken
The contract does not handle weight increases properly, making it impossible to adjust votes dynamically.
Tools Used
manual, hardhat
Recommendations
Modify _updateGaugeWeight() so that addition occurs first, preventing underflow.
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);
+ uint256 newGaugeWeight = oldGaugeWeight + (newWeight * votingPower / WEIGHT_PRECISION)
+ - (oldWeight * votingPower / WEIGHT_PRECISION);
g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
}