{"record":{"id":"785e64beacd59fad","repo":"nautechsystems/nautilus_trader","slug":"mul-div-rounding-up-failed","errorCode":null,"errorMessage":"mul_div_rounding_up failed","messagePattern":"mul_div_rounding_up failed","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/defi/tick_map/sqrt_price_math.rs","lineNumber":108,"sourceCode":"    sqrt_price_x96: U160,\n    liquidity: u128,\n    amount: U256,\n    add: bool,\n) -> U160 {\n    if amount.is_zero() {\n        return sqrt_price_x96;\n    }\n    let numerator = U256::from(liquidity) << 96;\n    let sqrt_price_x96 = U256::from(sqrt_price_x96);\n    let product = amount * sqrt_price_x96;\n\n    if add {\n        if product / amount == sqrt_price_x96 {\n            let denominator = numerator + product;\n            if denominator >= numerator {\n                // always fit to 160bits\n                let result = FullMath::mul_div_rounding_up(numerator, sqrt_price_x96, denominator)\n                    .expect(\"mul_div_rounding_up failed\");\n                return U160::from(result);\n            }\n        }\n\n        // Fallback: divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount))\n        let fallback_denominator = (numerator / sqrt_price_x96) + amount;\n        let result = FullMath::div_rounding_up(numerator, fallback_denominator)\n            .expect(\"div_rounding_up failed\");\n\n        // Check if result fits in U160\n        assert!(result <= U256::from(U160::MAX), \"Result overflows U160\");\n        U160::from(result)\n    } else {\n        // require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product);\n        assert!(\n            (product / amount) == sqrt_price_x96 && numerator > product,\n            \"Invalid conditions for amount0 removal: overflow or underflow detected\"\n        );","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/defi/tick_map/sqrt_price_math.rs#L90-L126","documentation":"get_next_sqrt_price_from_amount0_rounding_up computes the next sqrt price when adding token0, using mul_div_rounding_up(numerator, sqrt_price_x96, denominator). The expect panics when that operation fails, i.e. the multiplication numerator * sqrt_price_x96 overflows even after the overflow-safe fallback check, or the denominator arithmetic wraps (denominator < numerator).","triggerScenarios":"Trading token0 amounts so extreme that numerator + product overflows (denominator >= numerator fails) yet the code path still reaches the primary mul_div_rounding_up, or sqrt_price_x96 and amount combinations where product/amount no longer equals sqrt_price_x96 but the overflow-checked multiply still cannot be represented.","commonSituations":"Swap simulations with near-max U256 amounts; corrupted or malicious pool state with an inflated sqrt price; fuzz/tests exercising Uniswap V3's NextInitializedTickWithinOneWord-equivalent math at boundary values.","solutions":["Bound the swap amount so the resulting price stays within representable U160/Q96.64 range before calling get_next_sqrt_price_from_input.","Replace expect with error propagation (anyhow) so callers can reject the swap rather than panic.","Validate pool sqrt_price_x96 sanity (within Q64.96 bounds) before simulation."],"exampleFix":"// before\nlet result = FullMath::mul_div_rounding_up(numerator, sqrt_price_x96, denominator)\n    .expect(\"mul_div_rounding_up failed\");\n// after\nlet result = FullMath::mul_div_rounding_up(numerator, sqrt_price_x96, denominator)\n    .ok_or_else(|| anyhow::anyhow!(\"next sqrt price computation overflowed\"))?;","handlingStrategy":"validation","validationCode":"let max = max_swap_amount_for_price(sqrt_price_x96);\nif amount > max { return Err(anyhow!(\"amount exceeds representable next price\")); }","typeGuard":"fn price_in_bounds(sqrt_price_x96: U160) -> bool { sqrt_price_x96 >= MIN_SQRT_RATIO && sqrt_price_x96 <= MAX_SQRT_RATIO }","tryCatchPattern":"match FullMath::mul_div_rounding_up(numerator, sqrt_price_x96, denominator) {\n    Some(r) => U160::from(r),\n    None => return Err(anyhow!(\"next sqrt price overflow\")),\n}","preventionTips":["Clamp swap amounts to the MIN/MAX sqrt ratio range.","Sanity-check pool sqrt price before simulation.","Exercise swap math with U256-boundary fuzz inputs in CI."],"tags":["rust","panic","arithmetic-overflow","uniswap-v3","swap-math"],"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"}