linera-io/linera-protocol · error

Invalid `i32` stored in `i64`

Error message

Invalid `i32` stored in `i64`

What it means

linera-witty maps WIT primitive types onto WebAssembly flat types per the Component Model canonical ABI. When a value was 'joined' into an i64 flat slot (e.g. flattening a variant whose cases have different widths) and must be split back into an i32-backed type, `i64::try_into::<i32>()` fails for values outside [-2^31, 2^31-1]. The panic means the guest returned a flat value that does not fit the declared WIT type — a guest-side ABI contract violation, not normal control flow.

Source

Thrown at linera-witty/src/primitive_types/flat_type.rs:65

    fn split_from_f64(joined_f64: f64) -> Self;

    /// Splits off the `Target` flat type from this flat type.
    ///
    /// # Panics
    ///
    /// If the `Target` type can't be joined into this flat type.
    fn split_into<Target: FlatType>(self) -> Target;
}

impl FlatType for i32 {
    fn split_from_i32(joined_i32: i32) -> Self {
        joined_i32
    }

    fn split_from_i64(joined_i64: i64) -> Self {
        joined_i64
            .try_into()
            .expect("Invalid `i32` stored in `i64`")
    }

    fn split_from_f32(_joined_f32: f32) -> Self {
        unreachable!("`i32` is never joined into `f32`");
    }

    fn split_from_f64(_joined_f64: f64) -> Self {
        unreachable!("`i32` is never joined into `f64`");
    }

    fn split_into<Target: FlatType>(self) -> Target {
        Target::split_from_i32(self)
    }
}

impl FlatType for i64 {
    fn split_from_i32(_joined_i32: i32) -> Self {
        unreachable!("`i64` is never joined into `i32`");

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Range-check in the guest before returning any i32-backed WIT type (u32/s32/char/bool): return an error variant instead of an out-of-range value
  2. Regenerate and rebuild guest and host from the same WIT package version so the flat layouts match
  3. If values legitimately exceed i32, change the WIT type to u64/s64 and regenerate both sides
  4. Add a boundary unit test asserting every returned value fits its declared WIT type

Example fix

// before (guest): computes in i64, returns unchecked across the WIT boundary
fn score(delta: u64) -> u32 { big_computation(delta) as u32 } // joined i64 > i32::MAX -> host panics on split

// after (guest): validate at the boundary
fn score(delta: u64) -> Result<u32, Error> {
    let v = big_computation(delta);
    u32::try_from(v).map_err(|_| Error::Overflow)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// guest: guard every i32-backed return before crossing the WIT boundary
let v: i64 = compute();
assert!(i32::try_from(v).is_ok(), "value {v} overflows the declared WIT type");

Type guard

fn fits_i32(v: i64) -> bool { i32::try_from(v).is_ok() }

Prevention

When it happens

Trigger: A guest function whose result unflattens through an i64-to-i32 split returns a value such as 3_000_000_000; guest and host compiled from different WIT interfaces, so an i64-wide value is read where an i32-backed type (u32/s32/char) is declared.

Common situations: A WIT type changed from u32 to u64 (or vice versa) and only one side regenerated; a guest computing in i64 and returning across the boundary without a range check; fuzzing guests with unchecked arithmetic.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/c4e62e6d59db008d. Report an issue: GitHub.