# `MultiInvoker._latest` calculates incorrect closable for the current oracle version causing some liquidations to revert

MultiInvoker and Vault compute closable including the current pending position, so liquidations revert when the current oracle version's pending position increases.

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

**Target:** MultiInvoker/Vault closable calculation vs Market invariant
**Root cause:** MultiInvoker._latest and StrategyLib iterate pending positions up to and including currentId, while Market computes closable only up to currentId-1 and enforces the current position as a delta from the previous one.
**Steps:**
1. Set latest settled position = 10.
2. Call update(20) in current oracle version t=200; pending position at currentId = 20.
3. Call update(0) in same version; pending position becomes 0, valid since closable is enforced as delta from previous position (10).
4. MultiInvoker._latest computes closable = 10 (latest) instead of 0.
5. Liquidation attempts to close 20-10=10, but Market._invariant computes closable=10 from latest=10, pending=10, so closing 10 reverts.
**How found:** Compared Market's closable loop (`id < currentId`) against MultiInvoker/Vault loops (`id <= currentId`) and reasoned through the edge case where the current pending position is updated within the active oracle version.
**Insight:** When reimplementing a protocol's invariant math off-chain or in a peripheral contract, mirror the exact loop bounds and ordering of the canonical check; including the in-flight current position changes the enforced delta semantics.
