{"record":{"id":"eb78cab3cbf044c0","repo":"nautechsystems/nautilus_trader","slug":"liquidity-subtraction-underflow-x-current-y-y","errorCode":null,"errorMessage":"Liquidity subtraction underflow: x={current}, y={y}, delta={delta}","messagePattern":"Liquidity subtraction underflow: x=(.+?), y=(.+?), delta=(.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/defi/tick_map/liquidity_math.rs","lineNumber":67,"sourceCode":"///\n/// # Returns\n///\n/// The resulting liquidity after applying the delta.\n///\n/// # Panics\n///\n/// This function panics if:\n/// - Adding positive delta causes overflow.\n/// - Subtracting causes underflow.\n#[must_use]\npub fn liquidity_math_add(x: u128, y: i128) -> u128 {\n    match try_liquidity_math_add(x, y) {\n        Ok(value) => value,\n        Err(LiquidityMathError::Overflow { current, delta }) => {\n            panic!(\"Liquidity addition overflow: x={current}, y={y}, delta={delta}\")\n        }\n        Err(LiquidityMathError::Underflow { current, delta }) => {\n            panic!(\"Liquidity subtraction underflow: x={current}, y={y}, delta={delta}\")\n        }\n    }\n}\n\n/// Derives max liquidity per tick from a given tick spacing.\n///\n/// # Panics\n///\n/// Panics if `tick_spacing` is zero.\n#[must_use]\npub fn tick_spacing_to_max_liquidity_per_tick(tick_spacing: i32) -> u128 {\n    assert!(tick_spacing != 0, \"Tick spacing must be non-zero\");\n\n    // Calculate min and max tick aligned to tick spacing\n    let min_tick = (PoolTick::MIN_TICK / tick_spacing) * tick_spacing;\n    let max_tick = (PoolTick::MAX_TICK / tick_spacing) * tick_spacing;\n\n    // Calculate total number of ticks, cast to i64 to avoid potential overflow in subtraction","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/defi/tick_map/liquidity_math.rs#L49-L85","documentation":"liquidity_math_add() panics when the signed delta would drive u128 liquidity below zero (Underflow from try_liquidity_math_add), representing an invalid liquidity removal. The message includes x, the signed delta y, and the internal current/delta values.","triggerScenarios":"Calling liquidity_math_add(x, negative_y) where |y| > x; transitively via update_liquidity when removing liquidity that was never added at a tick, or apply_swap_quote with inconsistent state.","commonSituations":"Double-removing liquidity at a tick; off-by-one in tracking liquidity net deltas; replaying events out of order so a removal arrives before its addition.","solutions":["Use try_liquidity_math_add and handle LiquidityMathError::Underflow","Verify event ordering so removals never precede additions","Assert the tick's tracked liquidity is >= the removal amount before applying"],"exampleFix":"// before\nlet liquidity = liquidity_math_add(current, -delta); // panics if delta > current\n// after\nlet liquidity = try_liquidity_math_add(current, -delta)\n    .unwrap_or_else(|e| { /* log/handle underflow */ current });","handlingStrategy":"try-catch","validationCode":"// Pre-check removal against current liquidity\nfn remove_is_safe(x: u128, y: i128) -> bool {\n    y >= 0 || x.checked_sub(y.unsigned_abs()).is_some()\n}","typeGuard":null,"tryCatchPattern":"let liquidity = try_liquidity_math_add(x, y)\n    .unwrap_or_else(|e| { warn!(\"underflow: {e:?}\"); x });","preventionTips":["Track liquidity net per tick and assert non-negative before removing","Guarantee event ordering (additions before removals) in your ingestion pipeline","Use the try_ variant wherever state may be inconsistent"],"tags":["rust","panic","arithmetic-underflow","defi","liquidity"],"backgroundTag":"value-out-of-range","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}