# MultiInvoker liquidation action will revert most of the time due to incorrect closable amount initialization

MultiInvoker._latest leaves closableAmount uninitialized (0) when no pending positions settle, causing LIQUIDATE actions to revert and liquidators to lose fees.

- Payout: Contest reward
- Severity: medium
- Program: Perennial
- Credited: panprog
- Disclosed: 2023-09-14
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-09-perennial-judging/issues/44
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-09-perennial%3AM-4
- Tags: domain:web3 impact:dos kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:incomplete-fix technique:source-review vuln:accounting-error vuln:liquidation

**Target:** MultiInvoker liquidation action (Solidity smart contract)
**Root cause:** _latest initializes closableAmount to previousMagnitude only inside the pending-position settlement loop; when the loop is skipped or no position is virtually settled, closableAmount stays 0 instead of the latest settled position magnitude.
**Steps:**
1. Call MultiInvoker LIQUIDATE on an account whose local.latestId == local.currentId (no pending positions) or whose pending positions are not yet settleable.
2. _latest returns closableAmount = 0.
3. _liquidate calls market.update reducing currentPosition by closable=0.
4. Market._invariant sees !closableAmount.isZero() and reverts with MarketInvalidProtectionError.
5. Fix: initialize `closableAmount = previousMagnitude;` before the loop.
**How found:** Reading the fix for prior issue 49 (liquidations must close max closable amount) and tracing MultiInvoker._latest, noticing closableAmount is only assigned inside the settlement branch of the loop, so the common no-pending-position path returns 0.
**Insight:** When auditing a helper that mirrors core-contract logic (here MultiInvoker vs Market), diff the initialization of accumulator variables against the canonical implementation; a value assigned only inside a conditional branch silently defaults to zero on the skipped path.
