# Quote that have already been liquidated can be liquidated again in some cases

Liquidated PartyB's open quotes remain in PartyA's OpenPositions, so a later PartyA liquidation counts them in uPnl, diverting funds from the new PartyB.

- Payout: Contest reward
- Severity: medium
- Program: Symmetrical
- Credited: nobody2018
- Disclosed: 2023-06-05
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-06-symmetrical-judging/issues/206
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-10
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:medium tech:solidity technique:chained technique:source-review vuln:accounting-error vuln:business-logic

**Target:** LiquidationFacetImpl / muon uPnl off-chain signer
**Root cause:** liquidatePartyB and liquidatePositionsPartyB are non-atomic; if finalization is skipped, quotes stay OPENED in OpenPositions and are still included in uPnl computation during a later PartyA liquidation.
**Steps:**
1. A1 sends short quote1, B1 opens it at price 100; quote1 added to both OpenPositions.
2. Price dumps to 50; liquidator calls `liquidatePartyB(B1)` which credits partyBAllocatedBalances[B1][A1] to allocatedBalances[A1] and zeroes it.
3. `liquidatePositionsPartyB` is never called (zero liquidator share, bot downtime, deferred tx), so quote1 stays OPENED in A1's OpenPositions.
4. A1 sends short quote2 at price 50; B2 opens it.
5. Price pumps to 200; A1 becomes liquidatable. Liquidator calls `liquidatePartyA`/`setSymbolsPrice`/`liquidatePositionsPartyA`.
6. Off-chain uPnl (crypto_v3.js) includes quote1, so liquidated B1 is allocated part of allocatedBalances[A1], shorting B2.
**How found:** Code reading of liquidation flow: noticed liquidatePartyB credits balances but leaves OpenPositions cleanup to a separate call, then traced how uPnl in crypto_v3.js iterates all OpenPositions including stale ones.
**Exploitation:** Liquidator (or anyone) exploits the non-atomic liquidation: after liquidatePartyB without liquidatePositionsPartyB, a later PartyA liquidation's off-chain uPnl signature still counts the stale quote, misallocating PartyA's remaining balance to the already-liquidated PartyB instead of the active counterparty.
**Insight:** When a protocol splits a state-mutating operation into two non-atomic calls (credit + cleanup), check every downstream consumer that iterates the un-cleaned array (off-chain signers, uPnl, reward loops) — the stale entries get re-counted.
