nautechsystems/nautilus_trader · error

{type_name} raw value overflows U256 while rounding half to

Error message

{type_name} raw value overflows U256 while rounding half to even

What it means

Raised by round_u256_half_even when the round-up branch (remainder > half, or tie with odd quotient) calls quotient.checked_add(1) and the quotient is U256::MAX, so incrementing by one would overflow. It occurs during downscaling (decimals > fixed_precision) of a U256 raw value to fewer decimals. The library throws it instead of silently wrapping.

Source

Thrown at crates/adapters/blockchain/src/decode.rs:144

    if raw > raw_max {
        anyhow::bail!("{type_name} raw value {raw} exceeds {raw_max_name}={raw_max}");
    }

    Ok(())
}

fn round_u256_half_even(amount: U256, excess: u8, type_name: &str) -> anyhow::Result<U256> {
    let Some(divisor) = U256::from(10).checked_pow(U256::from(excess)) else {
        // The divisor exceeds U256::MAX, so every U256 amount is below half a retained unit
        return Ok(U256::ZERO);
    };
    let quotient = amount / divisor;
    let remainder = amount % divisor;
    let half = divisor / U256::from(2);

    if remainder > half || (remainder == half && quotient.bit(0)) {
        quotient.checked_add(U256::from(1)).ok_or_else(|| {
            anyhow::anyhow!("{type_name} raw value overflows U256 while rounding half to even")
        })
    } else {
        Ok(quotient)
    }
}

#[cfg(test)]
mod tests {
    use alloy::primitives::U256;
    use rstest::rstest;

    use super::*;

    #[rstest]
    #[case::zero(U256::ZERO, 6, 0, 6)]
    #[case::one(U256::from(1), 6, 10_000_000_000, 6)]
    #[case::above_f64_integer_limit(
        U256::from(9_007_199_254_740_993_u64),

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the input amount: quotient near U256::MAX indicates a bad or adversarial value; reject the record before calling the conversion
  2. Correct the token decimals metadata so the amount is interpreted as a realistic magnitude
  3. Clamp or pre-validate the amount against a sane upper bound (e.g., total supply) before conversion
  4. If legitimate huge values are required, this code path cannot support them with U256; use a wider integer representation

Example fix

// before
let qty = u256_to_price(raw, 60, 18, "TokenX")?; // downscale rounds a near-max value up
// after
ensure!(raw < U256::MAX, "amount implausibly large for TokenX");
let qty = u256_to_price(raw, 60, 18, "TokenX")?;
Defensive patterns

Strategy: validation

Validate before calling

fn rounds_within_u256(amount: U256, divisor: U256) -> bool {
    let quotient = amount / divisor;
    let remainder = amount % divisor;
    !(quotient == U256::MAX && (remainder * U256::from(2) >= divisor))
}

Try / catch

match scale_u256_to_raw_call(...) {
    Ok(v) => use(v),
    Err(e) if e.to_string().contains("rounding half to even") => reject_record(e),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling scale_u256_to_raw with decimals > fixed_precision and an amount whose scaled quotient rounds up to a value exceeding U256::MAX, i.e. quotient == U256::MAX with a positive rounding adjustment.

Common situations: Adversarial or malformed event payloads carrying values near 2^256; misconfigured decimals metadata causing a downscale that still produces a max-size quotient; fuzz/test inputs at U256 bounds.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/9366eee9bcd098d8. Report an issue: GitHub.