MultiInvoker liquidation action will revert due to incorrect closable amount calculation for invalid oracle versions
`MultiInvoker` calculates the `closable` amount in its `_latest` function. This function basically repeats the logic of `Market._settle`, but fails to repeat it correctly for the invalid oracle version settlement. When invalid oracle version is settled, `latestPosition` invalidation should increment, but the `latestPosition` should remain the same. This is achieved in the `Market._processPositionLocal` by adjusting `newPosition` after invalidation before the `latestPosition` is set to newPosition:\\n```\\nif (!version.valid) context.latestPosition.local.invalidate(newPosition);\\nnewPosition.adjust(context.latestPosition.local);\\n// rest of code\\ncontext.latestPosition.local.update(newPosition);\\n```\\n\\nHowever, `MultiInvoker` doesn't adjust the new position and simply sets `latestPosition` to new position both when oracle is valid or invalid:\\n```\\nif (!oracleVersion.valid) latestPosition.invalidate(pendingPosition);\\nlatestPosition.update(pendingPosition);\\n```\\n\\nThis leads to incorrect value of `closableAmount` afterwards:\\n```\\npreviousMagnitude = latestPosition.magnitude();\\nclosableAmount = previousMagnitude;\\n```\\n\\nFor example, if `latestPosition.market = 10`, `pendingPosition.market = 0` and pendingPosition has invalid oracle, then:\\n`Market` will invalidate (latestPosition.invalidation.market = 10), adjust (pendingPosition.market = 10), set `latestPosition` to new `pendingPosition` (latestPosition.maker = pendingPosition.maker = 10), so `latestPosition.maker` correctly remains 10.\\n`MultiInvoker` will invalidate (latestPosition.invalidation.market = 10), and immediately set `latestPosition` to `pendingPosition` (latestPosition.maker = pendingPosition.maker = 0), so `latestPosition.maker` is set to 0 incorrectly.\\nSince `LIQUIDATE` action of `MultiInvoker` uses `_latest` to calculate `closableAmount` and `liquidationFee`, these values will be calculated incorrectly and will revert when trying to update the market. See the `_liquidate` market update reducing `currentPosition` by `closable` (which is 0 when it must be bigger):\\n```\\nmarket.update(\\n    account,\\n    currentPosition.maker.isZero() ? UFixed6Lib.ZERO : currentPosition.maker.sub(closable),\\n    currentPosition.long.isZero() ? UFixed6Lib.ZERO : currentPosition.long.sub(closable),\\n    currentPosition.short.isZero() ? UFixed6Lib.ZERO : currentPosition.short.sub(closable),\\n    Fixed6Lib.from(-1, liquidationFee),\\n    true\\n);\\n```\\n\\nThis line will revert because `Market._invariant` verifies that `closableAmount` must be 0 after updating liquidated position:\\n```\\nif (protected && (\\n@@@ !closableAmount.isZero() ||\\n    context.latestPosition.local.maintained(\\n        context.latestVersion,\\n        context.riskParameter,\\n        collateralAfterFees.sub(collateral)\\n    ) ||\\n    collateral.lt(Fixed6Lib.from(-1, _liquidationFee(context, newOrder)))\\n)) revert MarketInvalidProtectionError();\\n```\\n
Both `Market` and `MultiInvoker` handle position settlement for invalid oracle versions incorrectly (Market issue with this was reported separately as it's completely different), so both should be fixed and the fix of this one will depend on how the `Market` bug is fixed. The way it is, `MultiInvoker` correctly adjusts pending position before invalidating `latestPosition` (which `Market` fails to do), however after such action `pendingPosition` must not be adjusted, because it was already adjusted and new adjustment should only change it by the difference from the last invalidation. The easier solution would be just not to change `latestPosition` in case of invalid oracle version, so the fix might be like this (just add else):\\n```\\n    if (!oracleVersion.valid) latestPosition.invalidate(pendingPosition);\\n    else latestPosition.update(pendingPosition);\\n```\\n\\nHowever, if the `Market` bug is fixed the way I proposed it (by changing `invalidate` function to take into account difference in invalidation of `latestPosition` and pendingPosition), then this fix will still be incorrect, because `invalidate` will expect unadjusted `pendingPosition`, so in this case `pendingPosition` should not be adjusted after loading it, but it will have to be adjusted for positions not yet settled. So the fix might look like this:\\n```\\n    Position memory pendingPosition = market.pendingPositions(account, id);\\n-   pendingPosition.adjust(latestPosition);\\n\\n    // load oracle version for that position\\n    OracleVersion memory oracleVersion = market.oracle().at(pendingPosition.timestamp);\\n    if (address(payoff) != address(0)) oracleVersion.price = payoff.payoff(oracleVersion.price);\\n\\n    // virtual settlement\\n    if (pendingPosition.timestamp <= latestTimestamp) {\\n        if (!oracleVersion.valid) latestPosition.invalidate(pendingPosition);\\n-        latestPosition.update(pendingPosition);\\n+       else {\\n+           pendingPosition.adjust(latestPosition);\\n+           latestPosition.update(pendingPosition);\\n+       }\\n        if (oracleVersion.valid) latestPrice = oracleVersion.price;\\n\\n        previousMagnitude = latestPosition.magnitude();\\n        closableAmount = previousMagnitude;\\n\\n    // process pending positions\\n    } else {\\n+       pendingPosition.adjust(latestPosition);\\n        closableAmount = closableAmount\\n            .sub(previousMagnitude.sub(pendingPosition.magnitude().min(previousMagnitude)));\\n        previousMagnitude = latestPosition.magnitude();\\n    }\\n```\\n
If there is an invalid oracle version during pending position settlement in `MultiInvoker` liquidation action, it will incorrectly revert and will cause loss of funds for the liquidator who should have received liquidation fee, but reverts instead.\\nSince this breaks important `MultiInvoker` functionality in some rare edge cases (invalid oracle version, user has unsettled position which should settle during user liquidation with `LIQUIDATION` action of MultiInvoker), this should be a valid medium finding.
```\\nif (!version.valid) context.latestPosition.local.invalidate(newPosition);\\nnewPosition.adjust(context.latestPosition.local);\\n// rest of code\\ncontext.latestPosition.local.update(newPosition);\\n```\\n