# Likwid: frozen pairDelta in leverage=0 borrow drains vault

Likwid's leverage=0 margin borrow never updates pairDelta, so AMM reserves stay frozen and the same static quote can be borrowed repeatedly, draining 74.31 BNB.

- Payout: Real-world hack
- Severity: critical
- Program: Likwid
- Disclosed: 2026-09-18
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-09/Likwid_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20260918-likwid
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:business-logic vuln:oracle-manipulation

**Target:** LikwidMarginPosition.addMargin margin borrow path (BSC AMM)
**Root cause:** _executeAddCollateralAndBorrow sets lendDelta/mirrorDelta/marginDelta but never pairDelta, so the AMM pairReserves never move and SwapMath.getAmountOut returns an identical static quote on every call.
**Steps:**
1. Approve TOKEN to LikwidMarginPosition 0x6bec...abe8
2. Build PoolKey{currency0=BNB, currency1=TOKEN, fee=3000, marginFee=2500}
3. Call `addMargin(key, {marginForOne:true, leverage:0, marginAmount:13.66M TOKEN, borrowAmount:type(uint256).max, borrowAmountMax:0, recipient:self, deadline})`
4. Repeat 24 times with the listed marginAmounts; first 15 each borrow identical 4.787 BNB
5. Receive BNB from LikwidVault via raw call in receive()
**How found:** Compared _executeAddCollateralAndBorrow against _executeAddLeverage in verified source and noticed the missing `delta.pairDelta = toBalanceDelta(...)` assignment; recognized pairDelta is the only field feeding pairReserves through the unlock callback, so the getAmountOut quote would be static.
**Exploitation:** Attacker pre-held 218.58M TOKEN, then in one tx ran 24 unprivileged addMargin calls with leverage=0 and borrowAmount=max. Because pairDelta is never written, pairReserves stay static and each call borrows the same first-trade marginal quote (4.787 BNB for the first 15), tapering only when borrowMaxAmount hits 20% of shrinking realReserves. 211.8M TOKEN in, 74.31 BNB out of LikwidVault.
**Escalation:** Repeated the same call 24 times to compound the static quote until the 20%-of-realReserves cap limited further borrowing.
**Insight:** When auditing AMM-integrated margin/leverage contracts, diff sibling code paths (leverage>0 vs leverage==0) for missing delta/reserve write-backs; any borrow path that skips the reserve-update field lets you re-quote at the same marginal price repeatedly.
