# Liquidity provider fees can be stolen from any pair

Transferring LP tokens to the pair skips fee accounting, letting anyone call withdrawFees(pair) to drain pending LP fees.

- Payout: Contest reward
- Severity: high
- Program: Goat Trading
- Credited: AhmedAdam
- Disclosed: 2024-03-10
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2024-03-goat-trading-judging/issues/63
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-03-goat-trading%3AH-1
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic vuln:reward-distribution

**Target:** GoatV1Pair AMM exchange contract
**Root cause:** Transferring liquidity tokens to the pair itself skips _updateFeeRewards, leaving feesPerTokenPaid[pair]=0, and withdrawFees is permissionless so fees can be claimed on behalf of the pair, lowering _pendingLiquidityFee without real WETH inflow.
**Steps:**
1. Add liquidity to a pair via `pair.mint(attacker)` to receive LP tokens
2. Wait out lock time, then `pair.transfer(address(pair), pair.balanceOf(attacker))`
3. Call `pair.withdrawFees(address(pair))`; feesPerTokenPaid[pair]==0 causes WETH transfer pair->pair and lowers _pendingLiquidityFee
4. Swap the now-excess WETH via `pair.swap(goatOut, 0, attacker)`
5. Call `pair.burn(attacker)` to recover the transferred liquidity
**How found:** Code reading of the transfer hook: noticed `if (to != address(this))` skips fee update when sending LP tokens to the pair, combined with permissionless withdrawFees accepting arbitrary `to`.
**Exploitation:** Attacker needs only a portion of LP tokens; transfers them to the pair, calls withdrawFees(pair) to zero out pending LP fees while WETH stays in the pool, swaps that WETH out as tokens, then burns the LP tokens to recover principal.
**Escalation:** Repeatable on any pair and any new fees accrued; attacker holding 50% LP can capture 100% of rewards, leaving other LPs unable to withdraw.
**Insight:** When a token's transfer hook special-cases `address(this)`, check whether accounting state (feesPerTokenPaid, reward debt) stays stale and whether any permissionless function can act on behalf of the contract itself.
