Repository: lambdaclass/cairo_native
Pull request #1171: Fix into_u96_guarantee not handling other types
Author: login: JulianGCalderon
Created: 2025-04-08T20:06:46Z | Merged: 2025-04-11T22:18:08Z

This PR fixes `into_u96_guarantee` libfunc so that it handles:

- BoundedInts with non zero lower range
- Smaller data types (like u8)

It used to be a noop because we though it always received an u96.

This PR fixes a bug in some contracts, caused by using an `u8` as input to this libfunc:
- `0x1aa0ae9d42edb490e438b161504f01b885e487f73d146a4d10baf78bb3f155`
- `0xc9e956f41095d8b61d7254e6955521121527ffbaad87c6475b71247aa009d4`

Note: Independly from the bug fix, i added a `#[track_caller]` attribute that was missing. It does not change the fix.



Diff:
diff --git a/src/error.rs b/src/error.rs
index 02b64b19ce..82d3d5959f 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -155,6 +155,7 @@ pub mod panic {
     impl std::error::Error for NativeAssertError {}
 
     impl NativeAssertError {
+        #[track_caller]
         pub fn new(msg: String) -> Self {
             let backtrace = Backtrace::capture();
             let info = if BacktraceStatus::Captured == backtrace.status() {
diff --git a/src/libfuncs/circuit.rs b/src/libfuncs/circuit.rs
index bf4f8637ca..d9612ea59c 100644
--- a/src/libfuncs/circuit.rs
+++ b/src/libfuncs/circuit.rs
@@ -7,6 +7,7 @@ use crate::{
     error::{Result, SierraAssertError},
     libfuncs::r#struct::build_struct_value,
     metadata::MetadataStorage,
+    native_panic,
     types::{circuit::build_u384_struct_type, TypeBuilder},
     utils::{get_integer_layout, layout_repeat, BlockExt, ProgramRegistryExt},
 };
@@ -33,6 +34,7 @@ use melior::{
     },
     Context,
 };
+use num_traits::Signed;
 
 /// Select and call the correct libfunc builder function from the selector.
 pub fn build<'ctx, 'this>(
@@ -66,11 +68,10 @@ pub fn build<'ctx, 'this>(
         CircuitConcreteLibfunc::FailureGuaranteeVerify(info) => build_failure_guarantee_verify(
             context, registry, entry, location, helper, metadata, info,
         ),
-        CircuitConcreteLibfunc::IntoU96Guarantee(SignatureAndTypeConcreteLibfunc {
-            signature,
-            ..
-        })
-        | CircuitConcreteLibfunc::U96SingleLimbLessThanGuaranteeVerify(
+        CircuitConcreteLibfunc::IntoU96Guarantee(info) => {
+            build_into_u96_guarantee(context, registry, entry, location, helper, metadata, info)
+        }
+        CircuitConcreteLibfunc::U96SingleLimbLessThanGuaranteeVerify(
             SignatureOnlyConcreteLibfunc { signature, .. },
         )
         | CircuitConcreteLibfunc::U96GuaranteeVerify(SignatureOnlyConcreteLibfunc { signature }) => {
@@ -1083,6 +1084,52 @@ fn build_array_slice<'ctx>(
     )
 }
 
+/// Converts input to an U96Guarantee.
+/// Input type must fit inside of an u96.
+///
+/// # Signature
+/// ```cairo
+/// extern fn into_u96_guarantee<T>(val: T) -> U96Guarantee nopanic;
+/// ```
+fn build_into_u96_guarantee<'ctx, 'this>(
+    context: &'ctx Context,
+    registry: &ProgramRegistry<CoreType, CoreLibfunc>,
+    entry: &'this Block<'ctx>,
+    location: Location<'ctx>,
+    helper: &LibfuncHelper<'ctx, 'this>,
+    _metadata: &mut MetadataStorage,
+    info: &SignatureAndTypeConcreteLibfunc,
+) -> Result<()> {
+    let src = entry.argument(0)?.into();
+
+    let src_ty = registry.get_type(&info.param_signatures()[0].ty)?;
+
+    let src_range = src_ty.integer_range(registry)?;
+
+    // We expect the input value to be unsigned, but we check it just in case.
+    if src_range.lower.is_negative() {
+        native_panic!("into_u96_guarantee expects an unsigned integer")
+    }
+
+    // Extend the input value to an u96
+    let mut dst = entry.extui(src, IntegerType::new(context, 96).into(), location)?;
+
+    // If the lower bound is positive, we offset the value by the lower bound
+    // to obtain the actual value.
+    if src_range.lower.is_positive() {
+        let klower = entry.const_int_from_type(
+            context,
+            location,
+            src_range.lower,
+            IntegerType::new(context, 96).into(),
+        )?;
+        dst = entry.addi(dst, klower, location)?
+    }
+
+    entry.append_operation(helper.br(0, &[dst], location));
+    Ok(())
+}
+
 #[cfg(test)]
 mod test {
 
@@ -1094,8 +1141,8 @@ mod test {
         values::Value,
     };
     use cairo_lang_sierra::extensions::utils::Range;
-    use num_bigint::BigUint;
-    use num_traits::Num;
+    use num_bigint::{BigInt, BigUint};
+    use num_traits::{Num, One};
     use starknet_types_core::felt::Felt;
 
     fn u384(limbs: [&str; 4]) -> Value {
@@ -1404,4 +1451,45 @@ mod test {
             ),
         );
     }
+
+    #[test]
+    fn run_into_u96_guarantee() {
+        let program = load_cairo!(
+            use core::circuit::{into_u96_guarantee, U96Guarantee};
+            use core::internal::bounded_int::BoundedInt;
+
+            fn main() -> (U96Guarantee, U96Guarantee, U96Guarantee) {
+                (
+                    into_u96_guarantee::<BoundedInt<0, 79228162514264337593543950335>>(123),
+                    into_u96_guarantee::<BoundedInt<100, 1000>>(123),
+                    into_u96_guarantee::<u8>(123),
+                )
+            }
+        );
+
+        let range = Range {
+            lower: BigInt::ZERO,
+            upper: BigInt::one() << 96,
+        };
+
+        run_program_assert_output(
+            &program,
+            "main",
+            &[],
+            jit_struct!(
+                Value::BoundedInt {
+                    value: 123.into(),
+                    range: range.clone()
+                },
+                Value::BoundedInt {
+                    value: 123.into(),
+                    range: range.clone()
+                },
+                Value::BoundedInt {
+                    value: 123.into(),
+                    range: range.clone()
+                }
+            ),
+        );
+    }
 }
diff --git a/src/values.rs b/src/values.rs
index a748145a8f..7e7c8ecf92 100644
--- a/src/values.rs
+++ b/src/values.rs
@@ -15,6 +15,7 @@ use crate::{
 use bumpalo::Bump;
 use cairo_lang_sierra::{
     extensions::{
+        circuit::CircuitTypeConcrete,
         core::{CoreLibfunc, CoreType, CoreTypeConcrete},
         starknet::{secp256::Secp256PointTypeConcrete, StarknetTypeConcrete},
         utils::Range,
@@ -966,6 +967,23 @@ impl Value {
                         range: info.range.clone(),
                     }
                 }
+                CoreTypeConcrete::Circuit(CircuitTypeConcrete::U96Guarantee(_)) => {
+                    let data = BigInt::from_biguint(
+                        Sign::Plus,
+                        BigUint::from_bytes_le(slice::from_raw_parts(
+                            ptr.cast::<u8>().as_ptr(),
+                            12,
+                        )),
+                    );
+
+                    Self::BoundedInt {
+                        value: data.into(),
+                        range: Range {
+                            lower: BigInt::ZERO,
+                            upper: BigInt::one() << 96,
+                        },
+                    }
+                }
                 CoreTypeConcrete::Coupon(_)
                 | CoreTypeConcrete::Circuit(_)
                 | CoreTypeConcrete::RangeCheck96(_) => native_panic!("implement from_ptr"),


Reviews:
user: login: FrancoGiachetta
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T20:59:04Z
user: login: azteca1998
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T09:33:20Z
user: login: JulianGCalderon
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T19:44:26Z
user: login: azteca1998
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T19:21:59Z

Review comments:
diff_hunk: @@ -1083,6 +1084,52 @@ fn build_array_slice<'ctx>(
     )
 }
 
+/// Converts input to an U96Guarantee.
+/// Input type must fit inside of an u96.
+///
+/// # Signature
+/// ```cairo
+/// extern fn into_u96_guarantee<T>(val: T) -> U96Guarantee nopanic;
+/// ```
+fn build_into_u96_guarantee<'ctx, 'this>(
+    context: &'ctx Context,
+    registry: &ProgramRegistry<CoreType, CoreLibfunc>,
+    entry: &'this Block<'ctx>,
+    location: Location<'ctx>,
+    helper: &LibfuncHelper<'ctx, 'this>,
+    _metadata: &mut MetadataStorage,
+    info: &SignatureAndTypeConcreteLibfunc,
+) -> Result<()> {
+    let src = entry.argument(0)?.into();
+
+    let src_ty = registry.get_type(&info.param_signatures()[0].ty)?;
+
+    let src_range = src_ty.integer_range(registry)?;
+
+    // We expect the input value to be unsigned, but we check it just in case.
+    if src_range.lower.is_negative() {
+        native_panic!("into_u96_guarantee expects an unsigned integer")
+    }
+
+    // Extend the input value to an u96
+    let mut dst = entry.extui(src, IntegerType::new(context, 96).into(), location)?;
path: src/libfuncs/circuit.rs
user: login: azteca1998
body: What if the range is larger than an `u96`? The generated MLIR would still be invalid.
created_at: 2025-04-09T09:33:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1083,6 +1084,52 @@ fn build_array_slice<'ctx>(
     )
 }
 
+/// Converts input to an U96Guarantee.
+/// Input type must fit inside of an u96.
+///
+/// # Signature
+/// ```cairo
+/// extern fn into_u96_guarantee<T>(val: T) -> U96Guarantee nopanic;
+/// ```
+fn build_into_u96_guarantee<'ctx, 'this>(
+    context: &'ctx Context,
+    registry: &ProgramRegistry<CoreType, CoreLibfunc>,
+    entry: &'this Block<'ctx>,
+    location: Location<'ctx>,
+    helper: &LibfuncHelper<'ctx, 'this>,
+    _metadata: &mut MetadataStorage,
+    info: &SignatureAndTypeConcreteLibfunc,
+) -> Result<()> {
+    let src = entry.argument(0)?.into();
+
+    let src_ty = registry.get_type(&info.param_signatures()[0].ty)?;
+
+    let src_range = src_ty.integer_range(registry)?;
+
+    // We expect the input value to be unsigned, but we check it just in case.
+    if src_range.lower.is_negative() {
+        native_panic!("into_u96_guarantee expects an unsigned integer")
+    }
+
+    // Extend the input value to an u96
+    let mut dst = entry.extui(src, IntegerType::new(context, 96).into(), location)?;
path: src/libfuncs/circuit.rs
user: login: JulianGCalderon
body: Compiler doesn't allow to specify a size greater than that value.

And if that ever happens, MLIR will catch the error instead of allowing it to run, right?
created_at: 2025-04-09T19:44:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-08T20:34:39Z
body: ## **Benchmark results Main vs HEAD.**
### Base

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `base dict_insert.cairo (JIT)` | 4.981 ± 0.047 | 4.913 | 5.069 | 1.03 ± 0.01 |
| `base dict_insert.cairo (AOT)` | 4.854 ± 0.031 | 4.811 | 4.918 | 1.00 |

### Head

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `head dict_insert.cairo (JIT)` | 4.760 ± 0.036 | 4.704 | 4.828 | 1.01 ± 0.01 |
| `head dict_insert.cairo (AOT)` | 4.703 ± 0.025 | 4.663 | 4.754 | 1.00 |

### Base

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `base dict_snapshot.cairo (JIT)` | 4.782 ± 0.089 | 4.668 | 4.934 | 1.01 ± 0.02 |
| `base dict_snapshot.cairo (AOT)` | 4.734 ± 0.053 | 4.612 | 4.779 | 1.00 |

### Head

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `head dict_snapshot.cairo (JIT)` | 4.666 ± 0.028 | 4.627 | 4.714 | 1.03 ± 0.01 |
| `head dict_snapshot.cairo (AOT)` | 4.542 ± 0.033 | 4.495 | 4.601 | 1.00 |

### Base

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `base factorial_2M.cairo (JIT)` | 5.262 ± 0.046 | 5.159 | 5.312 | 1.03 ± 0.02 |
| `base factorial_2M.cairo (AOT)` | 5.128 ± 0.108 | 4.990 | 5.258 | 1.00 |

### Head

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `head factorial_2M.cairo (JIT)` | 4.998 ± 0.033 | 4.954 | 5.053 | 1.01 ± 0.01 |
| `head factorial_2M.cairo (AOT)` | 4.961 ± 0.030 | 4.907 | 5.001 | 1.00 |

### Base

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `base fib_2M.cairo (JIT)` | 4.585 ± 0.037 | 4.542 | 4.676 | 1.00 |
| `base fib_2M.cairo (AOT)` | 4.594 ± 0.057 | 4.504 | 4.675 | 1.00 ± 0.01 |

### Head

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `head fib_2M.cairo (JIT)` | 4.557 ± 0.024 | 4.509 | 4.589 | 1.01 ± 0.01 |
| `head fib_2M.cairo (AOT)` | 4.505 ± 0.024 | 4.479 | 4.562 | 1.00 |

### Base

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `base linear_search.cairo (JIT)` | 4.710 ± 0.023 | 4.669 | 4.732 | 1.03 ± 0.01 |
| `base linear_search.cairo (AOT)` | 4.569 ± 0.028 | 4.537 | 4.628 | 1.00 |

### Head

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `head linear_search.cairo (JIT)` | 4.728 ± 0.026 | 4.693 | 4.764 | 1.03 ± 0.01 |
| `head linear_search.cairo (AOT)` | 4.602 ± 0.024 | 4.559 | 4.642 | 1.00 |

### Base

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `base logistic_map.cairo (JIT)` | 4.798 ± 0.032 | 4.755 | 4.864 | 1.04 ± 0.01 |
| `base logistic_map.cairo (AOT)` | 4.622 ± 0.011 | 4.601 | 4.634 | 1.00 |

### Head

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `head logistic_map.cairo (JIT)` | 4.842 ± 0.047 | 4.797 | 4.959 | 1.04 ± 0.01 |
| `head logistic_map.cairo (AOT)` | 4.673 ± 0.027 | 4.632 | 4.698 | 1.00 |


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-08T20:39:15Z
body: # Benchmarking results
## Benchmark for program `dict_insert`

<details><summary>Open benchmarks</summary>

<br>

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `Cairo-vm (Rust, Cairo 1)` | 15.263 ± 0.075 | 15.193 | 15.443 | 3.16 ± 0.05 |
| `cairo-native (embedded AOT)` | 4.824 ± 0.068 | 4.729 | 4.933 | 1.00 |
| `cairo-native (embedded JIT using LLVM's ORC Engine)` | 4.850 ± 0.053 | 4.747 | 4.917 | 1.01 ± 0.02 |

</details>

## Benchmark for program `dict_snapshot`

<details><summary>Open benchmarks</summary>

<br>

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `Cairo-vm (Rust, Cairo 1)` | 687.0 ± 13.0 | 672.5 | 711.7 | 1.00 |
| `cairo-native (embedded AOT)` | 4765.2 ± 143.0 | 4589.1 | 5104.5 | 6.94 ± 0.25 |
| `cairo-native (embedded JIT using LLVM's ORC Engine)` | 4791.0 ± 54.7 | 4709.2 | 4879.2 | 6.97 ± 0.15 |

</details>

## Benchmark for program `factorial_2M`

<details><summary>Open benchmarks</summary>

<br>

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `Cairo-vm (Rust, Cairo 1)` | 9.139 ± 0.026 | 9.112 | 9.198 | 1.82 ± 0.01 |
| `cairo-native (embedded AOT)` | 5.049 ± 0.032 | 5.000 | 5.096 | 1.00 ± 0.01 |
| `cairo-native (embedded JIT using LLVM's ORC Engine)` | 5.033 ± 0.034 | 4.976 | 5.097 | 1.00 |

</details>

## Benchmark for program `fib_2M`

<details><summary>Open benchmarks</summary>

<br>

| Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `Cairo-vm (Rust, Cairo 1)` | 8.618 ± 0.031 | 8.577 | 8.677 | 1.89 ± 0.02 |
| `cairo-native (embedded AOT)` | 4.554 ± 0.049 | 4.468 | 4.613 | 1.00 |
| `cairo-native (embedded JIT using LLVM's ORC Engine)` | 4.719 ± 0.050 | 4.647 | 4.820 | 1.04 ± 0.02 |

</details>

## Benchmark for program `linear_search`

<details><summary>Open benchmarks</summary>

<br>

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `Cairo-vm (Rust, Cairo 1)` | 699.2 ± 12.4 | 680.8 | 723.7 | 1.00 |
| `cairo-native (embedded AOT)` | 4653.7 ± 32.6 | 4597.5 | 4699.5 | 6.66 ± 0.13 |
| `cairo-native (embedded JIT using LLVM's ORC Engine)` | 4833.6 ± 68.9 | 4732.1 | 4938.2 | 6.91 ± 0.16 |

</details>

## Benchmark for program `logistic_map`

<details><summary>Open benchmarks</summary>

<br>

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `Cairo-vm (Rust, Cairo 1)` | 440.8 ± 4.2 | 434.5 | 449.2 | 1.00 |
| `cairo-native (embedded AOT)` | 4904.3 ± 63.3 | 4799.0 | 5006.2 | 11.13 ± 0.18 |
| `cairo-native (embedded JIT using LLVM's ORC Engine)` | 4876.5 ± 39.2 | 4825.2 | 4947.2 | 11.06 ± 0.14 |

</details>


author_association: NONE
user: login: codecov-commenter
created_at: 2025-04-11T21:34:50Z
body: ## [Codecov](https://app.codecov.io/gh/lambdaclass/cairo_native/pull/1171?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lambdaclass) Report
Attention: Patch coverage is `98.80952%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 80.40%. Comparing base [(`d89d171`)](https://app.codecov.io/gh/lambdaclass/cairo_native/commit/d89d171aa6bbc070aede5c50e41fc6618a6cf9ab?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lambdaclass) to head [(`715fca3`)](https://app.codecov.io/gh/lambdaclass/cairo_native/commit/715fca3328098aa6486ca2b17d68b2e6fd501ae9?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lambdaclass).

| [Files with missing lines](https://app.codecov.io/gh/lambdaclass/cairo_native/pull/1171?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lambdaclass) | Patch % | Lines |
|---|---|---|
| [src/libfuncs/circuit.rs](https://app.codecov.io/gh/lambdaclass/cairo_native/pull/1171?src=pr&el=tree&filepath=src%2Flibfuncs%2Fcircuit.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lambdaclass#diff-c3JjL2xpYmZ1bmNzL2NpcmN1aXQucnM=) | 98.55% | [1 Missing :warning: ](https://app.codecov.io/gh/lambdaclass/cairo_native/pull/1171?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lambdaclass) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1171      +/-   ##
==========================================
+ Coverage   80.34%   80.40%   +0.05%     
==========================================
  Files         110      110              
  Lines       29498    29581      +83     
==========================================
+ Hits        23701    23784      +83     
  Misses       5797     5797              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/lambdaclass/cairo_native/pull/1171?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lambdaclass).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lambdaclass).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE