# Withdraw-then-close margin refund double count

Refund or close paths that credit margin/collateral already withdrawn, or refund from the wrong funding source

_Area: DeFi accounting and math bugs. Seen in 2 disclosed reports; latest 2026-03-13. Updated 2026-10-29._

## How it works
Bookkeeping tracks a position's initial margin (IM) as a stored basis, and separate code paths move that margin: withdrawal reduces the user's free balance, while close/settle computes a refund from the position's notional and leverage. The assumption that breaks is that the refund basis still reflects margin actually held. If withdrawal lets margin reach zero (because margin+upnl covers the requirement) but close still refunds `marginDelta = closedOpenNotional/leverage`, the same IM slice is paid out twice. A parallel failure: refunds drawn from a different funding source than the deposit path, so supply and collateral accounting diverge [E1](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-14.md)[E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-05.md).

## Where it shows up
- Withdraw checks that permit margin to hit 0 when margin+upnl covers the target [E1](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-14.md)
- Close/settle refunds computed from notional/leverage without consulting prior withdrawals [E1](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-14.md)
- Refund/withdraw paths using a different source than the matching deposit path [E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-05.md)
- Shared post-action validation applied to branches whose invariant it does not govern [E3](https://bugbountylibrary.com/r/c4%3A2024-06-size%3AM-10.md)
- Sibling functions (e.g. preRelease vs leave) handling the same balance differently [E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-05.md)

## How to review
For every refund or credit, trace the exact stored basis it reads and confirm it was decremented by any prior withdrawal of the same slice. Invariant: total margin refunded across withdraw+close never exceeds margin originally posted for that position. For funding sources, confirm refund source equals deposit source for the same balance. For shared validators, confirm the invariant applies to each branch (borrow-token withdrawal does not move collateral ratio) [E3](https://bugbountylibrary.com/r/c4%3A2024-06-size%3AM-10.md).

## Variants seen
- Withdraw-all-then-close refunds withdrawn IM as fresh collateral [E1](https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-14.md)
- leave() refunds genesis supply from collateralSource instead of supplySource [E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-05.md)
- Over-broad collateral-ratio check blocks legitimate borrow-token withdrawal [E3](https://bugbountylibrary.com/r/c4%3A2024-06-size%3AM-10.md)

## Impact and severity
E1 drains protocol funds via double-refunded margin; E2 drains higher-value collateral by source mismatch; E3 locks user funds. Severity rises when refunds are unbounded by stored basis and when parallel funding pools let value cross between them.

## Why fixes fail
Evidence shows sibling paths diverging (preRelease correct, leave wrong) [E2](https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-05.md), so patching one path leaves the mismatch; fixes must reconcile all refund paths against a single basis.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2025-08-gte-perps-and-launchpad%3AM-14.md
- E2: https://bugbountylibrary.com/r/c4%3A2025-02-recall%3AH-05.md
- E3: https://bugbountylibrary.com/r/c4%3A2024-06-size%3AM-10.md
