1. Equity Exactly Equals MM Threshold
1. Equity Exactly Equals MM Threshold
equity == maintenanceMargin evaluates to false, and the position remains safe.
Attempting to liquidate such a position reverts with NotLiquidatable().2. Bad Debt
2. Bad Debt
|marketPnl| > imLocked:- Realized PnL: Capped at
-imLocked(the trader loses their entire locked margin but no more) - Market PnL: The uncapped mathematical PnL is recorded for accounting purposes
- Bad debt: The difference
|marketPnl| - imLockedis absorbed by pool equity - Event: A
BadDebtevent is emitted with the shortfall amount - Isolation: The trader’s free collateral and other open positions are completely unaffected
3. Oracle Returns Invalid
3. Oracle Returns Invalid
OracleInvalid():- Position open: Cannot compute entry price
- Unrealized PnL: Cannot mark positions to market
- Settlement: Cannot determine final PnL
- Liquidation: Cannot assess position equity
checkOracleAndPause()
if it detects persistent oracle invalidity, blocking all protocol operations until the oracle
is restored.publishForwardRound().4. Fixing Timestamp Business Day Roll
4. Fixing Timestamp Business Day Roll
DateTime.getNextBusinessDayFixing
library rolls it forward to the next business day:- Saturday (dayOfWeek = 6): Rolls forward +2 days to Monday
- Sunday (dayOfWeek = 0): Rolls forward +1 day to Monday
- Post-roll check: If the computed fixing time is less than or equal to
rawMaturityafter the weekend roll, it advances by +1 additional day and rechecks for Saturday
5. Fee Exceeds Available Balance
5. Fee Exceeds Available Balance
6. Insufficient Pool Liquidity
6. Insufficient Pool Liquidity
PoolVault.applyPnl() reverts with
InsufficientPoolLiquidity().This is a hard revert — the settlement cannot proceed. The position remains open until either:- LPs deposit additional liquidity
- Market prices move to reduce the trader’s profit
- The position is liquidated (if applicable)
7. Simultaneous Maturity + Liquidatable
7. Simultaneous Maturity + Liquidatable
batchSettle() function checks the liquidation condition and excludes such positions.These positions must be resolved through a separate liquidate() call. This design ensures that
the liquidation penalty fee is properly collected and distributed rather than being bypassed
through the standard settlement path.8. Zero Notional
8. Zero Notional
openPosition() with notional = 0 reverts with ZeroAmount(). The protocol enforces
a minimum position size via minPositionNotional, but the zero check fires first as a basic
input validation.9. Stale Oracle Price
9. Stale Oracle Price
- Forward staleness: If
(block.timestamp - publishTimestamp) > maxForwardAge, the forward price’sisValidflag isfalse. Operations that require a forward price will revert withOracleStale()orOracleInvalid(). - Spot staleness: If
(block.timestamp - cachedPublishTime) > maxOracleAge, the spot price’sisValidflag isfalse.
publishForwardRound() with a fresh forward price. For spot,
the caller must provide fresh Pyth price update data that the OracleModule forwards to the Pyth
contract.10. Mode Blocks Operation
10. Mode Blocks Operation
| Mode | Blocked Operations | Error |
|---|---|---|
| PAUSED | All trading, settlement, and liquidation operations | ProtocolPaused() |
| REDUCE_ONLY | openPosition(), increasePosition() | ReduceOnlyMode() |
| DEGRADED | openPosition(), increasePosition() | ReduceOnlyMode() |
| NORMAL | None | — |
ProtocolPaused() regardless of the specific
operation attempted.11. Rounding
11. Rounding
- PnL calculations: Truncated toward zero
- Fee calculations: Truncated toward zero (protocol receives less in edge cases)
- ERC-4626
redeem: Shares-to-assets conversion rounds down, which can produce a 1-wei difference from the expected withdrawal amount. Tests useassertApproxEqAbs(..., 1)to account for this.
12. Fixing Price Already Recorded
12. Fixing Price Already Recorded
recordFixingPriceFromPyth() function reverts with FixingPriceAlreadySet() if a fixing
price has already been recorded for the given (pairId, fixingTimestamp) combination.Fixing prices are immutable once recorded. They cannot be overwritten or corrected. The admin
setFixingPrice() function has the same constraint — it can only set a fixing price if one
does not already exist for that timestamp.13. Forward Clearing with Open Positions
13. Forward Clearing with Open Positions
clearForwardRound() function allows cleaning up stale forward data for past fixing
timestamps. However, if any open positions still reference the fixing timestamp being cleared,
the function silently skips rather than reverting.The forward data is preserved as long as any open position depends on it for PnL calculation.
Only after all positions at that fixing timestamp are settled or closed does clearing succeed.14. Race Between Settlement and Liquidation
14. Race Between Settlement and Liquidation
PositionNotOpen().15. Position Increase After Config Change
15. Position Increase After Config Change
imFactorBps is increased after a position is opened:- The existing position continues using its snapshotted IM rate (stored at open time)
- An
increasePosition()call uses the current config for the additional margin calculation - The resulting combined margin uses a weighted-average approach that preserves the existing position’s economics while applying the new rate to the incremental notional
16. Partial Reduction to Below Min Notional
16. Partial Reduction to Below Min Notional
reducePosition() validates that the remaining notional after reduction is greater than or equal
to minPositionNotional. If the reduction would leave the position with less than the minimum
notional, the transaction reverts.To close a position below the minimum notional threshold, the trader must execute a full close
(reduce to zero) rather than a partial reduction.