cairo-vm icon indicating copy to clipboard operation
cairo-vm copied to clipboard

bug: erroneous hint reference

Open enitrat opened this issue 9 months ago • 13 comments

Describe the bug A clear and concise description of what the bug is.

In this code:

            // Prepare arguments
            // MARK: args assignment
            [ap] = range_check_ptr, ap++;
            [ap] = bitwise_ptr, ap++;
            [ap] = keccak_ptr, ap++;
            [ap] = poseidon_ptr, ap++;
            [ap] = range_check96_ptr, ap++;
            [ap] = add_mod_ptr, ap++;
            [ap] = mul_mod_ptr, ap++;
            [ap] = evm.value, ap++;

            call abs precompile_fn;

            let range_check_ptr = [ap - 9];
            let bitwise_ptr = cast([ap - 8], BitwiseBuiltin*);
            let keccak_ptr = cast([ap - 7], KeccakBuiltin*);
            let poseidon_ptr = cast([ap - 6], PoseidonBuiltin*);
            let range_check96_ptr = cast([ap - 5], felt*);
            let add_mod_ptr = cast([ap - 4], ModBuiltin*);
            let mul_mod_ptr = cast([ap - 3], ModBuiltin*);
            let evm = Evm(cast([ap - 2], EvmStruct*));
            let err = cast([ap - 1], EthereumException*);

            %{
                precompile_address_bytes = ids.precompile_address.to_bytes(20, "little")
                print(f"[CAIRO] PrecompileEnd: {precompile_address_bytes}")
            %}

When executing the hint, we load up all the ids_data into an ids dict, based on the HintReference for each object in scope. However, for the Evm object, we get:

reference: HintReference { offset1: Value(0), offset2: Value(0), inner_dereference: false, outer_dereference: false, ap_tracking_data: None, cairo_type: Some("ethereum.cancun.vm.evm_impl.Evm") }

I'm unable to get any relevant information regarding evm here - I cannot get the address of the variable as a relocatable (as it's a let reference), and i'm unable to get it's ap offset, because offset1 and offset2 are zero)

Expected behavior I should have proper offsets in the HintReference data

What version/commit are you on? For example: 5c56712768586e979166296bb82176892b5a3113

Additional context I can't provide an easy repro. But I can check periodically how things go.

enitrat avatar Mar 11 '25 15:03 enitrat

Hi @enitrat , at first glance, it looks like let range_check_ptr = [ap - 9]; it trying to access the ap with an out of bounds offset. Does precompile_fn function increment the op too?

FrancoGiachetta avatar Mar 20 '25 12:03 FrancoGiachetta

Hi, yes ap gets increased in the function call. I will try to provide a small reproducible example when I have some time

enitrat avatar Mar 20 '25 12:03 enitrat

Hi @FrancoGiachetta here's a MRE:

%builtins output pedersen range_check ecdsa bitwise ec_op keccak poseidon range_check96 add_mod mul_mod

from starkware.cairo.common.cairo_builtins import (
    BitwiseBuiltin,
    KeccakBuiltin,
    PoseidonBuiltin,
    ModBuiltin,
    HashBuiltin,
    SignatureBuiltin,
    EcOpBuiltin,
)
from starkware.cairo.common.registers import get_label_location

struct MyStruct {
    value: MyStructPointer*,

}

struct MyStructPointer {
    a: felt,
    b: felt,
}



func foo(my_struct: MyStruct) -> MyStruct {
    tempvar my_new_struct = MyStruct(new MyStructPointer(my_struct.value.a+1, my_struct.value.b+1));
    return my_new_struct;
}


func main{
    output_ptr: felt*,
    pedersen_ptr: HashBuiltin*,
    range_check_ptr,
    ecdsa_ptr: SignatureBuiltin*,
    bitwise_ptr: BitwiseBuiltin*,
    ec_op_ptr: EcOpBuiltin*,
    keccak_ptr: KeccakBuiltin*,
    poseidon_ptr: PoseidonBuiltin*,
    range_check96_ptr: felt*,
    add_mod_ptr: ModBuiltin*,
    mul_mod_ptr: ModBuiltin*,
}() {
    let (foo_label) = get_label_location(foo);
    tempvar my_struct = MyStruct(new MyStructPointer(1,2));

    call abs foo_label;

    let my_struct = MyStruct(cast([ap-1], MyStructPointer*));

    %{breakpoint()%} // This could be any hint, what's important for us is to just see what's in the HintReference object

    return();
}

Here's what the HintReference object looks like:

Hint References:
  range_check_ptr => HintReference { offset1: Reference(FP, -11, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("felt") }
  output_ptr => HintReference { offset1: Reference(FP, -13, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("felt*") }
  __temp4 => HintReference { offset1: Reference(AP, -1, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: Some(ApTracking { group: 6, offset: 15 }), cairo_type: Some("felt") }
  ec_op_ptr => HintReference { offset1: Reference(FP, -8, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("starkware.cairo.common.cairo_builtins.EcOpBuiltin*") }
  bitwise_ptr => HintReference { offset1: Reference(FP, -9, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("starkware.cairo.common.cairo_builtins.BitwiseBuiltin*") }
  ecdsa_ptr => HintReference { offset1: Reference(FP, -10, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("starkware.cairo.common.cairo_builtins.SignatureBuiltin*") }
  pedersen_ptr => HintReference { offset1: Reference(FP, -12, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("starkware.cairo.common.cairo_builtins.HashBuiltin*") }
  poseidon_ptr => HintReference { offset1: Reference(FP, -6, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("starkware.cairo.common.cairo_builtins.PoseidonBuiltin*") }
  my_struct => HintReference { offset1: Value(0), offset2: Value(0), inner_dereference: false, outer_dereference: false, ap_tracking_data: None, cairo_type: Some("__main__.MyStruct") }
  add_mod_ptr => HintReference { offset1: Reference(FP, -4, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("starkware.cairo.common.cairo_builtins.ModBuiltin*") }
  foo_label => HintReference { offset1: Reference(AP, -1, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: Some(ApTracking { group: 6, offset: 7 }), cairo_type: Some("felt*") }
  range_check96_ptr => HintReference { offset1: Reference(FP, -5, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("felt*") }
  mul_mod_ptr => HintReference { offset1: Reference(FP, -3, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("starkware.cairo.common.cairo_builtins.ModBuiltin*") }
  keccak_ptr => HintReference { offset1: Reference(FP, -7, false, true), offset2: Value(0), inner_dereference: false, outer_dereference: true, ap_tracking_data: None, cairo_type: Some("starkware.cairo.common.cairo_builtins.KeccakBuiltin*") }
--Return--

We can see for my_struct:

my_struct => HintReference { offset1: Value(0), offset2: Value(0), inner_dereference: false, outer_dereference: false, ap_tracking_data: None, cairo_type: Some("__main__.MyStruct") }

enitrat avatar Mar 23 '25 12:03 enitrat

Okay, thanks. I don't get what would be the exepected behavior. In the issue you say it should have proper offsets. Why is it that you believe this is an error and not a misuse?

FrancoGiachetta avatar Mar 25 '25 16:03 FrancoGiachetta

In the Hint References you sent, the offset1 of foo_label is ap - 1, which is that you are trying to cast as MyStructPointer*. Couldn't that be the error?

FrancoGiachetta avatar Mar 25 '25 17:03 FrancoGiachetta

In the Hint References you sent, the offset1 of foo_label is ap - 1, which is that you are trying to cast as MyStructPointer*. Couldn't that be the error?

I double-checked and can attest that memory[ap-1] is indeed the value returned by foo, in our case it's my_struct as expected. Not foo_label

Thus, I suspect that the offsets returned by the hint reference are not correct (namely - my_struct should be at ap - 1)

enitrat avatar Mar 25 '25 17:03 enitrat

Sorry for being insistent, but doesn't this statement return () in foo mean it returns nothing?

FrancoGiachetta avatar Mar 26 '25 14:03 FrancoGiachetta

no, because my_struct is an implicit arg, it's always returned. It's true that I could've made it explicit, though, it's equivalent. I will edit the program in my previous message

enitrat avatar Mar 26 '25 15:03 enitrat

Mmm, in that case, this code should work right? I mean, ignoring the current error:

%builtins output pedersen range_check ecdsa

from starkware.cairo.common.cairo_builtins import (
    HashBuiltin,
    SignatureBuiltin,
)
from starkware.cairo.common.registers import get_label_location

struct MyStruct {
    value: MyStructPointer*,
}

struct MyStructPointer {
    a: felt,
    b: felt,
}

func foo(my_struct: MyStruct) -> MyStruct {
    tempvar my_new_struct = MyStruct(new MyStructPointer(my_struct.value.a+1, my_struct.value.b+1));
    return my_new_struct;
}

func main{
    output_ptr: felt*,
    pedersen_ptr: HashBuiltin*,
    range_check_ptr,
    ecdsa_ptr: SignatureBuiltin*,
}() {
    let (foo_label) = get_label_location(foo);
    
    call abs foo_label;
    
    tempvar my_struct = MyStruct(new MyStructPointer(1,2));

    let my_struct = MyStruct(cast([ap-1], MyStructPointer*));

    assert my_struct = MyStruct(new MyStructPointer(1,2));
    
    // %{%} // This could be any hint, what's important for us is to just see what's in the HintReference object

    return();
}

I'm running this code with both python vm and rust vm, and both return the same error:

Rust

Couldn't run program: VmException(VmException { pc: Relocatable { segment_index: 0, offset: 44 }, inst_location: Some(Location { end_line: 49, end_col: 59, input_file: InputFile { filename: "custom_hint.cairo" }, parent_location: None, start_line: 49, start_col: 5 }), inner_exc: DiffAssertValues((RelocatableValue(Relocatable { segment_index: 1, offset: 25 }), RelocatableValue(Relocatable { segment_index: 1, offset: 33 }))), error_attr_value: None, traceback: None })

Python

An ASSERT_EQ instruction failed: 1:25 != 1:33.
assert my_struct = MyStruct(new MyStructPointer(1,2));
^****************************************************^

FrancoGiachetta avatar Mar 26 '25 20:03 FrancoGiachetta

Nevermind, I was mistaken.

FrancoGiachetta avatar Mar 26 '25 20:03 FrancoGiachetta

Hey @FrancoGiachetta here's an example of a case where this issue lead to a VM execution that did not match the definition of the program - i think it's related given that changing let to local solved the issue in that case

https://github.com/feltroidprime/garaga-zero/pull/14

enitrat avatar Apr 10 '25 13:04 enitrat

Hi @enitrat! I've run the program you'd passed before but with the python vm, something I hadn't done before, but instead of calling breakpoint, calling print(ids.my_struct) in the hint, and got this error:

Error at pc=0:36:
Got an exception while executing a hint.
    %{print(ids.my_struct)%} // This could be any hint, what's important for us is to just see what's in the HintReference object
    ^**********************^
Traceback (most recent call last):
  File "examples/program.cairo", line 46, in <module>
    %{print(ids.my_struct)%} // This could be any hint, what's important for us is to just see what's in the HintReference object
  File "/Users/franco/Documents/Projects/cairo-vm/cairo-vm-env/lib/python3.9/site-packages/starkware/cairo/lang/vm/vm_consts.py", line 61, in __getattr__
    return self.get_or_set_value(name, None)
  File "/Users/franco/Documents/Projects/cairo-vm/cairo-vm-env/lib/python3.9/site-packages/starkware/cairo/lang/vm/vm_consts.py", line 155, in get_or_set_value
    return getattr(self, handler_name)(name, value, scope, set_value)
  File "/Users/franco/Documents/Projects/cairo-vm/cairo-vm-env/lib/python3.9/site-packages/starkware/cairo/lang/vm/vm_consts.py", line 216, in handle_ReferenceDefinition
    expr, expr_type = simplify_type_system(expr, identifiers=self._context.identifiers)
  File "/Users/franco/Documents/Projects/cairo-vm/cairo-vm-env/lib/python3.9/site-packages/starkware/cairo/lang/compiler/type_system_visitor.py", line 412, in simplify_type_system
    return TypeSystemVisitor(
  File "/Users/franco/Documents/Projects/cairo-vm/cairo-vm-env/lib/python3.9/site-packages/starkware/cairo/lang/compiler/ast/visitor.py", line 42, in visit
    return getattr(self, f"visit_{type(obj).__name__}", self._visit_default)(obj)
  File "/Users/franco/Documents/Projects/cairo-vm/cairo-vm-env/lib/python3.9/site-packages/starkware/cairo/lang/compiler/type_system_visitor.py", line 348, in visit_ExprCast
    raise CairoTypeError(
starkware.cairo.lang.compiler.type_casts.CairoTypeError: :1:1: Cannot cast 'felt' to '__main__.MyStruct'.
cast(([ap + (-1)]), __main__.MyStruct)
^************************************^

FrancoGiachetta avatar Apr 11 '25 19:04 FrancoGiachetta

Hi @FrancoGiachetta I guess this is because we're doing a cast inside the let my_struct instruction, but because this one is still a reference when you try to print it, it's not yet accessible as a VmConst.

If you run this

    let my_struct_ptr = cast([ap-1], MyStructPointer*);
    let my_struct = MyStruct(my_struct_ptr);

    %{breakpoint()%}

You'll see

(Pdb) ids.my_struct_ptr
<starkware.cairo.lang.vm.vm_consts.VmConstsReference object at 0x1066cb790>
(Pdb) ids.my_struct
*** starkware.cairo.lang.compiler.type_casts.CairoTypeError: :1:1: Cannot cast 'felt' to '__main__.MyStruct'.
cast(([ap + (-1)]), __main__.MyStruct)

ids.my_struct_ptr is accessible

enitrat avatar Apr 11 '25 19:04 enitrat