Cross-PartyB Liquidation Reserve Enforcement

v0.8.6 makes pending cross-PartyB liquidation settlement reserves reduce the shared PartyB availability used by quote, solvency, and liquidation checks.

Summary

In cross mode, one shared PartyB allocation backs positions against multiple PartyAs. When one of those PartyAs is liquidated, the liquidation path can release balances locked for its positions before that PartyA's positive PnL owed by the PartyB is actually paid. Core records the unpaid amount in partyBLiquidationSettlementReserve[partyB].

That reserve must behave like unavailable capital until settlePartyALiquidation consumes it or the Clearing House takeover path clears it. v0.8.6 moves the reserve check into the shared PartyB availability helpers so it is enforced consistently across cross-bucket operations.

The bug

The risky window existed after liquidatePositionsPartyA freed the cross PartyB locked balances but before the liquidator completed settlePartyALiquidation. During that window, raw partyBAllocatedBalances[partyB][address(0)] had not yet been debited for the pending settlement.

Affected paths

The reserve now applies to the shared cross-mode PartyB availability used by:

  • lockQuote, through partyBAvailableForQuote.
  • deallocateForPartyB in cross mode, through the same quote-availability helper.
  • safeDeallocateForPartyB in cross mode, through that same helper before its additional safe-path floors.
  • Open/close/funding/settlement solvency checks, through partyBAvailableBalanceForLiquidation.
  • liquidateCrossPartyB eligibility, so a PartyB can be liquidatable once reserved debt is counted.

Core tracks each PartyA liquidation's positive pending contribution in every PartyB margin mode. The availability helper returns zero for an isolated PartyB, but if that PartyB switches to cross mode before settlement completes, the recorded contribution becomes unavailable in the shared bucket. resolveLiquidationDispute resynchronizes the contribution when it overrides a pending amount. Normal PartyA settlement consumes it, and Clearing House takeover clears it. The aggregate reserve is subtracted only while the PartyB is currently in cross-mode accounting.

Fix

The fix centralizes reserve enforcement in LibAccount. Both quote availability and liquidation-style availability subtract partyBLiquidationSettlementReserve for cross-mode PartyBs.

return available - int256(partyBLiquidationSettlementReserve(accountLayout, partyB));

function partyBAvailableBalanceForLiquidation(
    int256 upnl,
    address partyB,
    address partyA
) internal view returns (int256) {
    return partyBAvailableBalanceForLiquidation(upnl, partyB, partyA, true);
}

The old one-off subtraction was removed from PartyBAccountFacetImpl.deallocateForPartyB. Deallocation now uses the same reserve-aware helper as quote locking, which avoids double-counting and keeps all cross-bucket availability checks on one rule.

Settlement top-up exception

One path intentionally bypasses the reserve check: settlePartyBUpnlForLiquidation. That function exists so a liquidator can realize a cross PartyB's positive uPNL from other solvent PartyAs before paying the pending liquidation settlement. If the reserve were applied to that pre-settlement solvency check, the top-up path could be blocked by the same reserve it is trying to fund.

To keep the bypass narrow, LibSettlement.settleUpnlUnified now accepts an explicit applyPartyBLiquidationReserve flag. Normal settlement and force-close settlement pass true. settlePartyBUpnlForLiquidation passes false and then requires the PartyB's cross allocated balance to be non-decreasing.

uint256 partyBBalanceBefore = accountLayout.partyBAllocatedBalances[sig.partyB][address(0)];
LibSettlement.settleUpnlUnified(sig, updatedPrices, true, false, false);
require(
    accountLayout.partyBAllocatedBalances[sig.partyB][address(0)] >= partyBBalanceBefore,
    "SettlementFacet: PartyB balance decreased"
);

Resulting invariant

  • Quote locking uses reserve-adjusted cross allocation, including the exact pass/fail boundary.
  • Cross-PartyB liquidation eligibility counts pending settlement debt even when raw allocation alone still appears solvent.
  • The liquidation top-up path alone bypasses the reserve, and it may not reduce the PartyB cross-allocation bucket.