# ClaimFees steals staking rewards

Permissionless claimFees sweeps the gauge's entire token0/token1 balance, so when rewardToken equals a pool token, staking rewards are redirected to the bribe contract.

- Payout: Contest reward
- Severity: medium
- Program: Hybra Finance
- Credited: 0xSeer
- Disclosed: 2025-11-20
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2025-10-hybra-finance#m-06
- Library page: https://bugbountylibrary.com/r/c4%3A2025-10-hybra-finance%3AM-06
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:access-control vuln:accounting-error vuln:dos

**Target:** CLGauge/GaugeCL.sol _claimFees / claimFees
**Root cause:** _claimFees transfers the gauge's full token0/token1 balance after collectFees() instead of only the delta collected, so pre-existing rewardToken balances are swept. claimFees is public with no access control.
**Steps:**
1. Deploy gauge for a pool where rewardToken == token0 (e.g. WETH/USDC rewarded in WETH), isForPair=true.
2. DISTRIBUTION calls `notifyRewardAmount(rewardToken, 10 ether)`, gauge balance = 10 WETH.
3. Attacker calls public `gauge.claimFees()`.
4. `clPool.collectFees()` adds ~0.1 WETH fees; function reads full balance 10.1 WETH.
5. Entire 10.1 WETH approved and sent to internal_bribe via `notifyRewardAmount`; stakers lose rewards.
**How found:** Code reading of _claimFees: noticed it measures fees by reading the gauge's whole token balance after collectFees() rather than a before/after delta, then cross-referenced notifyRewardAmount as a second token inflow path sharing the same token.
**Exploitation:** No capital needed: attacker front-runs or immediately follows the DISTRIBUTION reward deposit with a permissionless claimFees() call, causing the gauge to sweep reward tokens (plus any fees) to the internal_bribe contract, permanently denying stakers their epoch rewards.
**Escalation:** Secondary DoS: gauge accounting (rewardRate, _periodFinish) desyncs from balance, so later notifyRewardAmount calls may revert on `require(rewardRate <= contractBalance / epochTimeRemaining)`, blocking future funding.
**Insight:** When a contract has multiple inflow paths for the same token (fees + reward funding), any 'balance after call' accounting sweeps unrelated funds; check every function that reads balanceOf(this) as a proxy for a delta.
