{"record":{"id":"13abe07df263643b","repo":"nautechsystems/nautilus_trader","slug":"decimal-overflow-multiplying-lhs-by-rhs","errorCode":null,"errorMessage":"Decimal overflow multiplying {lhs} by {rhs}","messagePattern":"Decimal overflow multiplying (.+?) by (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/data/bet.rs","lineNumber":776,"sourceCode":"    if value.is_zero() {\n        anyhow::bail!(\"invalid {name}: must be non-zero\")\n    }\n    Ok(())\n}\n\nfn checked_add(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {\n    lhs.checked_add(rhs)\n        .ok_or_else(|| anyhow::anyhow!(\"Decimal overflow adding {lhs} and {rhs}\"))\n}\n\nfn checked_sub(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {\n    lhs.checked_sub(rhs)\n        .ok_or_else(|| anyhow::anyhow!(\"Decimal overflow subtracting {rhs} from {lhs}\"))\n}\n\nfn checked_mul(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {\n    lhs.checked_mul(rhs)\n        .ok_or_else(|| anyhow::anyhow!(\"Decimal overflow multiplying {lhs} by {rhs}\"))\n}\n\nfn checked_div(lhs: Decimal, rhs: Decimal) -> anyhow::Result<Decimal> {\n    check_nonzero_denominator(rhs, \"divisor\")?;\n    lhs.checked_div(rhs)\n        .ok_or_else(|| anyhow::anyhow!(\"Decimal overflow dividing {lhs} by {rhs}\"))\n}\n\n#[cfg(test)]\nmod tests {\n    use rstest::rstest;\n    use rust_decimal::Decimal;\n    use rust_decimal_macros::dec;\n\n    use super::*;\n\n    fn dec_str(s: &str) -> Decimal {\n        s.parse::<Decimal>().expect(\"Failed to parse Decimal\")","sourceCodeStart":758,"sourceCodeEnd":794,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/data/bet.rs#L758-L794","documentation":"`checked_mul` wraps `Decimal::checked_mul` and raises this error when multiplying `lhs` by `rhs` overflows the Decimal range. Exposure and liability calculations multiply stakes by multipliers/prices, so two moderately large values can still overflow.","triggerScenarios":"Callers `exposure_checked`, `liability_checked`, `profit_checked`, or `hedging_stake_checked` multiplying a stake by a price/multiplier where the product exceeds Decimal's max (~7.9e28).","commonSituations":"Very large stake inputs (mis-scaled currency units, e.g. cents vs dollars compounded); extreme odds/multipliers from a corrupt feed; repeated compounding of stakes across bets.","solutions":["Check the operand values in the message; identify whether the stake or the multiplier is unreasonably large.","Validate stake and odds ranges at input boundaries (e.g. reject stakes above a configured maximum).","Fix unit scaling so amounts use consistent currency units before multiplication.","Sanitize market feed multipliers/odds with upper-bound clamps before use in exposure math."],"exampleFix":"// before\nlet exposure = checked_mul(stake, multiplier)?;\n\n// after\nlet max_stake = Decimal::from(10_000_000);\nif stake > max_stake { return Err(anyhow!(\"stake {stake} exceeds limit\")); }\nlet exposure = checked_mul(stake, multiplier)?;","handlingStrategy":"validation","validationCode":"fn sane_stake(s: Decimal) -> bool { s > Decimal::ZERO && s < Decimal::from(10_000_000) }\nfn sane_multiplier(m: Decimal) -> bool { m > Decimal::ONE && m < Decimal::from(10_000) }","typeGuard":null,"tryCatchPattern":"let exposure = bet.exposure_checked()\n    .map_err(|e| { log::error!(\"exposure overflow: {e}\"); e })?;","preventionTips":["Enforce maximum stake and odds limits in your order/risk layer.","Verify unit scaling of stakes and multipliers before arithmetic.","Clamp feed multipliers to a sane upper bound."],"tags":["rust","decimal","overflow","arithmetic"],"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"}