Cairo Narrow Int Return
Overview
This detector flags functions that accept a felt252 argument and return a narrow integer type (u8, u16, u32, u64, their signed counterparts, or bool) without an upstream range check. Converting a felt252 to a narrow integer in Cairo is a fallible operation: the value must fit the target type. Cairo does not silently truncate the way some languages do — the conversion traps when the felt is out of range. That trap is a panic, which aborts the whole transaction.
The detector exists to help auditors confirm that a range check is present on this conversion path. It looks for the structural pattern (felt in, narrow integer out) and reports it only when the function contains no assertion that constrains the value with a comparison or a fallible conversion. The aim is to surface conversions whose safety has not been explicitly established, not to claim every such function is broken.
Why This Is an Issue
When the conversion is unprotected, every out-of-range input becomes a guaranteed abort. For an honest caller this is merely an inconvenience, but for an attacker it is a denial-of-service primitive: if a function on a critical path converts caller-supplied felts to a narrow type without bounds, the attacker can feed an out-of-range value and make the call fail deterministically. If other users or contracts depend on that call succeeding, the path is stalled.
This is a low-severity finding because the failure mode is an abort rather than a silent corruption — the contract does not enter a wrong state, it simply refuses to proceed. Still, availability matters, and a conversion that panics on adversarial input is worth a deliberate range check.
How to Resolve
Validate the felt fits the target range before converting, and surface a clear error rather than relying on the implicit conversion trap.
// Vulnerable: felt converted to u8 with no range check
fn to_byte(x: felt252) -> u8 {
x.try_into().unwrap() // panics for any x >= 256
}
// Fixed: explicit range assertion before conversion
fn to_byte(x: felt252) -> u8 {
let xb: u256 = x.into();
assert(xb < 256, 'out of range');
xb.try_into().unwrap()
}
The assertion both documents the precondition and converts the panic into an explicit, checked failure that callers can reason about.
Detection Methodology
For each function with at least one output, the detector checks whether any input is a felt type and whether any output is a narrow integer type. Functions that match both are candidates. It then looks for a range check among the function’s assertions — specifically an assertion expression containing a comparison operator or a fallible-conversion call. If such a check exists, the function is considered guarded and is skipped; otherwise it is reported.
Because a fallible conversion call is itself treated as a soft range signal, functions that perform the conversion through a checked path are given the benefit of the doubt, which keeps the detector’s confidence intentionally low.
Limitations
- The range-check heuristic accepts any assertion containing a comparison or conversion call, so a check that does not actually bound the converted value may suppress the finding.
- A range check performed in a caller rather than inside the converting function is not seen, since the analysis is per function.
- Treating a conversion call as a soft pass means some genuinely unprotected conversions are not reported; this is the deliberate trade-off behind the low confidence.
Related Detectors
- Cairo Unchecked Felt Arithmetic — unbounded felt arithmetic
- Cairo Unchecked Division — division without a divisor guard
- Missing Range Check — the Circom analogue for unbounded signal widths
References
Sample Sigvex Output
{
"detector": "cairo-narrow-int-return",
"severity": "low",
"confidence": 0.60,
"title": "Function `to_byte` returns narrow integer from felt input without range check",
"template": "to_byte",
"signal": "to_byte",
"line": 1,
"description": "Function `to_byte` takes a felt252 input and returns a narrow integer type (u8). Without an explicit range assertion, out-of-range inputs cause the transaction to panic, giving attackers an easy denial-of-service surface.",
"recommendation": "Assert the felt input fits the target range before converting, e.g. `assert(x < 0x100_u256.try_into().unwrap(), 'out of range');`"
}