diff --git a/CHANGELOG.md b/CHANGELOG.md index fdbe1a5d6f..f8ca0a11c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ #### Upcoming Changes +* feat(BREAKING): Serialize inputs into output segment in cairo1-run crate: + * Checks that only `Array` can be received by the program main function when running with with either `--proof_mode` or `--append_return_values`. + * Copies the input value to the output segment right after the output in the format `[array_len, arr[0], arr[1],.., arr[n]]`. + * fix: make MemorySegmentManager.finalize() public [#1771](https://github.com/lambdaclass/cairo-vm/pull/1771) * feat(BREAKING): Serialize `Array` return value into output segment in cairo1-run crate: diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index 8fd85f8dbe..ed91397a7c 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -133,9 +133,16 @@ pub fn cairo_run_program( let initial_gas = 9999999999999_usize; // Fetch return type data - let return_type_id = main_func.signature.ret_types.last(); + if (cairo_run_config.proof_mode || cairo_run_config.append_return_values) + && !check_only_array_felt_input_type( + &main_func.signature.param_types, + &sierra_program_registry, + ) + { + return Err(Error::IlegalInputValue); + }; if (cairo_run_config.proof_mode || cairo_run_config.append_return_values) && !check_only_array_felt_return_type(return_type_id, &sierra_program_registry) { @@ -623,7 +630,7 @@ fn create_entry_code( let local = ctx.add_var(CellExpression::Deref(deref!([fp + i.to_i16().unwrap()]))); casm_build_extend!(ctx, assert local = var;); } - // Deserialize return values into output segment + // Serialize return values into output segment let output_ptr = output_ptr.unwrap(); let outputs = (1..(return_type_size + 1)) .rev() @@ -650,11 +657,11 @@ fn create_entry_code( tempvar write_ptr = output_ptr; // Enter copying loop rescope{remaining_elements = remaining_elements, array_ptr = array_ptr, write_ptr = write_ptr}; - jump CopyArray if remaining_elements != 0; - jump End; + jump CopyOutputArray if remaining_elements != 0; + jump EndOutputCopy; // Main Loop - CopyArray: + CopyOutputArray: #{steps = 0;} // Write array value into output segment tempvar val = *(array_ptr++); @@ -666,10 +673,50 @@ fn create_entry_code( tempvar new_write_ptr = write_ptr; // Continue the loop rescope{remaining_elements = new_remaining_elements, array_ptr = new_array_ptr, write_ptr = new_write_ptr}; - jump CopyArray if remaining_elements != 0; + jump CopyOutputArray if remaining_elements != 0; - End: + EndOutputCopy: }; + if !actual_args_size.is_zero() { + // Serialize the input values into the output segment + // We lost the output_ptr var after re-scoping, so we need to create it again + // The last instruction will write the last output ptr so we can find it in [ap - 1] + let output_ptr = ctx.add_var(CellExpression::Deref(deref!([ap - 1]))); + // len(builtins - output) + len(builtins) + if segment_arena: segment_arena_ptr + info_ptr + 0 + (segment_arena_ptr + 3) + let offset = (2 * builtins.len() - 1 + 4 * got_segment_arena as usize) as i16; + let array_start_ptr = ctx.add_var(CellExpression::Deref(deref!([fp + offset]))); + let array_end_ptr = ctx.add_var(CellExpression::Deref(deref!([fp + offset + 1]))); + casm_build_extend! {ctx, + // Calculate size of array and write it into the output segment + tempvar array_size = array_end_ptr - array_start_ptr; + assert array_size = *(output_ptr++); + // Create loop variables + tempvar remaining_elements = array_size; + tempvar array_ptr = array_start_ptr; + tempvar write_ptr = output_ptr; + // Enter copying loop + rescope{remaining_elements = remaining_elements, array_ptr = array_ptr, write_ptr = write_ptr}; + jump CopyInputArray if remaining_elements != 0; + jump EndInputCopy; + + // Main Loop + CopyInputArray: + #{steps = 0;} + // Write array value into output segment + tempvar val = *(array_ptr++); + assert val = *(write_ptr++); + const one = 1; + // Create loop variables + tempvar new_remaining_elements = remaining_elements - one; + tempvar new_array_ptr = array_ptr; + tempvar new_write_ptr = write_ptr; + // Continue the loop + rescope{remaining_elements = new_remaining_elements, array_ptr = new_array_ptr, write_ptr = new_write_ptr}; + jump CopyInputArray if remaining_elements != 0; + + EndInputCopy: + }; + } // After we are done writing into the output segment, we can write the final output_ptr into locals: // The last instruction will write the final output ptr so we can find it in [ap - 1] let output_ptr = ctx.add_var(CellExpression::Deref(deref!([ap - 1]))); @@ -804,6 +851,40 @@ fn get_function_builtins( (builtins, builtin_offset) } +// Checks that the program input (if present) is of type Array +fn check_only_array_felt_input_type( + params: &[ConcreteTypeId], + sierra_program_registry: &ProgramRegistry, +) -> bool { + // Filter implicit arguments (builtins, gas) + let arg_types = params + .iter() + .filter(|ty| { + let info = get_info(sierra_program_registry, ty).unwrap(); + let generic_ty = &info.long_id.generic_id; + !(generic_ty != &SegmentArenaType::ID + || generic_ty == &GasBuiltinType::ID + || generic_ty == &BitwiseType::ID + || generic_ty == &EcOpType::ID + || generic_ty == &PedersenType::ID + || generic_ty == &PoseidonType::ID + || generic_ty == &RangeCheckType::ID + || generic_ty == &SegmentArenaType::ID + || generic_ty == &SystemType::ID) + }) + .collect_vec(); + if arg_types.is_empty() { + // No inputs + true + } else if arg_types.len() == 1 { + arg_types[0] + .debug_name + .as_ref() + .is_some_and(|name| name == "Array") + } else { + false + } +} // Checks that the return type is either an Array or a PanicResult> type fn check_only_array_felt_return_type( return_type_id: Option<&ConcreteTypeId>, @@ -899,25 +980,33 @@ fn fetch_return_values( // Output Builtin will always be on segment 2 let return_values = vm.get_continuous_range((2, 0).into(), vm.get_segment_size(2).unwrap())?; - // This means that the return value is not a PanicResult - return if result_inner_type_size.is_none() { - Ok(return_values) - // The return value is a PanicResult so we need to check the panic_flag + // Remove panic wrapper + let (return_values, panic_flag) = if result_inner_type_size.is_none() { + // return value is not a PanicResult + (&return_values[..], false) } else { - // PanicResult::Err - if return_values.first() != Some(&MaybeRelocatable::from(0)) { - return Err(Error::RunPanic( - return_values[1..] - .iter() - .map(|mr| mr.get_int().unwrap_or_default()) - .collect_vec(), - )); - // PanicResult::Ok - } else { - Ok(return_values[1..].to_vec()) - } + // return value is a PanicResult + ( + &return_values[1..], + return_values[0] != MaybeRelocatable::from(0), + ) }; + // Take only the output (as the output segment will also contain the input) + let output_len = return_values[0].get_int().unwrap().to_usize().unwrap() + 1; + let return_values = &return_values[0..output_len]; + // Return Ok or Err based on panic_flag + if panic_flag { + return Err(Error::RunPanic( + return_values + .iter() + .map(|mr| mr.get_int().unwrap_or_default()) + .collect_vec(), + )); + } else { + return Ok(return_values.to_vec()); + } } + let mut return_values = vm.get_continuous_range( (vm.get_ap() - (return_type_size + builtin_count) as usize).unwrap(), return_type_size as usize, @@ -1508,4 +1597,66 @@ mod tests { .unwrap(); assert_eq!(hash_a, hash_b) } + + #[rstest] + fn check_output_segment_contains_program_ouput_and_input( + #[values(true, false)] proof_mode: bool, + ) { + // tensor.cairo + // inputs: [2 2 2 4 1 2 3 4] + // outputs: [1] + // Compile to sierra + let sierra_program = compile_to_sierra( + "../cairo_programs/cairo-1-programs/serialized_output/with_input/tensor.cairo", + ); + // Set proof_mode + let cairo_run_config = Cairo1RunConfig { + proof_mode, + layout: LayoutName::all_cairo, + append_return_values: !proof_mode, // This is so we can test appending return values when not running in proof_mode + finalize_builtins: true, + args: &[FuncArg::Array(vec![ + 2.into(), + 2.into(), + 2.into(), + 4.into(), + 1.into(), + 2.into(), + 3.into(), + 4.into(), + ])], + ..Default::default() + }; + // Run program + let (runner, _, _) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); + // Check output segment + let expected_output_segment: Vec = vec![ + // panic_flag + 0.into(), + // output len + 1.into(), + // output + 1.into(), + // input len + 8.into(), + // input + 2.into(), + 2.into(), + 2.into(), + 4.into(), + 1.into(), + 2.into(), + 3.into(), + 4.into(), + ]; + let output_segment_size = runner.vm.get_segment_size(2).unwrap_or_default(); + let output_segment = runner + .vm + .get_integer_range((2, 0).into(), output_segment_size) + .unwrap() + .iter() + .map(|f| f.clone().into_owned()) + .collect_vec(); + assert_eq!(expected_output_segment, output_segment); + } } diff --git a/cairo1-run/src/error.rs b/cairo1-run/src/error.rs index f3a716cb1f..49f2c18cb9 100644 --- a/cairo1-run/src/error.rs +++ b/cairo1-run/src/error.rs @@ -61,4 +61,6 @@ pub enum Error { }, #[error("Only programs returning `Array` can be currently proven. Try serializing the final values before returning them")] IlegalReturnValue, + #[error("Only programs with `Array` as an input can be currently proven. Try inputing the serialized version of the input and deserializing it on main")] + IlegalInputValue, } diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index 5e9ab9839b..82e4f7a866 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -283,104 +283,140 @@ mod tests { "ecdsa_recover.cairo", "3490001189944926769628658346285649224182856084131963744896357527096042836716", "[3490001189944926769628658346285649224182856084131963744896357527096042836716]", + None, None )] #[case( "tensor_new.cairo", "[1 2] [1 false 1 true]", // Struct { span [1 2] span [struct {1 false} struct {1 true}]} "[2 1 2 2 1 0 1 1]", // len: 2 [1 2] len 2: [{1 0} {1 0}] - None + None, None )] - #[case("bytes31_ret.cairo", "123", "[123]", None)] - #[case("null_ret.cairo", "null", "[]", None)] + #[case("bytes31_ret.cairo", "123", "[123]", None, None)] + #[case("null_ret.cairo", "null", "[]", None, None)] #[case( "felt_dict_squash.cairo", "{66675: [4 5 6] 66676: [1 2 3]}", "[66675 3 4 5 6 66676 3 1 2 3]", + None, None )] #[case( "dict_with_struct.cairo", "{0: 1 true 1: 1 false 2: 1 true}", "[0 1 1 1 1 0 2 1 1]", + None, None )] #[case( "nullable_box_vec.cairo", "{0: 10 1: 20 2: 30} 3", "[0 10 1 20 2 30 3]", + None, None )] - #[case("array_integer_tuple.cairo", "[1] 1", "[1 1 1]", None)] + #[case("array_integer_tuple.cairo", "[1] 1", "[1 1 1]", None, None)] #[case( "felt_dict.cairo", "{66675: [8 9 10 11] 66676: [1 2 3]}", "[66675 4 8 9 10 11 66676 3 1 2 3]", + None, + None + )] + #[case("felt_span.cairo", "[8 9 10 11]", "[4 8 9 10 11]", None, None)] + #[case("nullable_dict.cairo", "", "[]", None, None)] + #[case( + "struct_span_return.cairo", + "[[4 3] [2 1]]", + "[2 2 4 3 2 2 1]", + None, None )] - #[case("felt_span.cairo", "[8 9 10 11]", "[4 8 9 10 11]", None)] - #[case("nullable_dict.cairo", "", "[]", None)] - #[case("struct_span_return.cairo", "[[4 3] [2 1]]", "[2 2 4 3 2 2 1]", None)] - #[case("null_ret.cairo", "null", "[]", None)] - #[case("with_input/tensor.cairo", "1", "[1]", Some("[2 2] [1 2 3 4]"))] + #[case("null_ret.cairo", "null", "[]", None, None)] + #[case( + "with_input/tensor.cairo", + "1", + "[1]", + Some("[2 2] [1 2 3 4]"), + Some("[2 2 2 4 1 2 3 4]") + )] #[case( "with_input/array_input_sum.cairo", "12", "[12]", - Some("2 [1 2 3 4] 0 [9 8]") + Some("2 [1 2 3 4] 0 [9 8]"), + Some("[2 4 1 2 3 4 0 2 9 8]") )] - #[case("with_input/array_length.cairo", "5", "[5]", Some("[1 2 3 4] [1]"))] - #[case("with_input/array_length.cairo", "4", "[4]", Some("[1 2 3 4] []"))] - #[case("with_input/branching.cairo", "0", "[0]", Some("17"))] - #[case("with_input/branching.cairo", "1", "[1]", Some("0"))] - #[case("dictionaries.cairo", "1024", "[1024]", None)] - #[case("simple_struct.cairo", "100", "[100]", None)] - #[case("simple.cairo", "true", "[1]", None)] + #[case( + "with_input/array_length.cairo", + "5", + "[5]", + Some("[1 2 3 4] [1]"), + Some("[4 1 2 3 4 1 1]") + )] + #[case( + "with_input/array_length.cairo", + "4", + "[4]", + Some("[1 2 3 4] []"), + Some("[4 1 2 3 4 0]") + )] + #[case("with_input/branching.cairo", "0", "[0]", Some("17"), Some("[17]"))] + #[case("with_input/branching.cairo", "1", "[1]", Some("0"), Some("[0]"))] + #[case("dictionaries.cairo", "1024", "[1024]", None, None)] + #[case("simple_struct.cairo", "100", "[100]", None, None)] + #[case("simple.cairo", "true", "[1]", None, None)] #[case( "pedersen_example.cairo", "1089549915800264549621536909767699778745926517555586332772759280702396009108", "[1089549915800264549621536909767699778745926517555586332772759280702396009108]", + None, None )] #[case( "poseidon_pedersen.cairo", "1036257840396636296853154602823055519264738423488122322497453114874087006398", "[1036257840396636296853154602823055519264738423488122322497453114874087006398]", + None, None )] #[case( "poseidon.cairo", "1099385018355113290651252669115094675591288647745213771718157553170111442461", "[1099385018355113290651252669115094675591288647745213771718157553170111442461]", + None, None )] - #[case("sample.cairo", "5050", "[5050]", None)] + #[case("sample.cairo", "5050", "[5050]", None, None)] #[case( "recursion.cairo", "1154076154663935037074198317650845438095734251249125412074882362667803016453", "[1154076154663935037074198317650845438095734251249125412074882362667803016453]", + None, None )] - #[case("print.cairo", "", "[]", None)] - #[case("ops.cairo", "6", "[6]", None)] - #[case("hello.cairo", "1234", "[1 1234]", None)] + #[case("print.cairo", "", "[]", None, None)] + #[case("ops.cairo", "6", "[6]", None, None)] + #[case("hello.cairo", "1234", "[1 1234]", None, None)] #[case( "enum_match.cairo", "10 3618502788666131213697322783095070105623107215331596699973092056135872020471", "[10 3618502788666131213697322783095070105623107215331596699973092056135872020471]", + None, None )] - #[case("enum_flow.cairo", "300", "[300]", None)] - #[case("array_get.cairo", "3", "[3]", None)] - #[case("bitwise.cairo", "11772", "[11772]", None)] - #[case("factorial.cairo", "3628800", "[3628800]", None)] - #[case("fibonacci.cairo", "89", "[89]", None)] + #[case("enum_flow.cairo", "300", "[300]", None, None)] + #[case("array_get.cairo", "3", "[3]", None, None)] + #[case("bitwise.cairo", "11772", "[11772]", None, None)] + #[case("factorial.cairo", "3628800", "[3628800]", None, None)] + #[case("fibonacci.cairo", "89", "[89]", None, None)] fn test_run_progarm( #[case] program: &str, #[case] expected_output: &str, #[case] expected_serialized_output: &str, #[case] inputs: Option<&str>, + #[case] serialized_inputs: Option<&str>, #[values( &["--cairo_pie_output", "/dev/null"], // Non proof-mode &["--cairo_pie_output", "/dev/null", "--append_return_values"], // Non proof-mode & appending return values to ouput @@ -413,7 +449,10 @@ mod tests { args.push(&filename); args.extend_from_slice(common_flags); args.extend_from_slice(extra_flags); - if let Some(inputs) = inputs { + if let (Some(inputs), false) = (inputs, has_serialized_output) { + args.extend_from_slice(&["--args", inputs]) + } + if let (Some(inputs), true) = (serialized_inputs, has_serialized_output) { args.extend_from_slice(&["--args", inputs]) } let args = args.iter().cloned().map(String::from); @@ -426,18 +465,18 @@ mod tests { } #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/with_input/branching.cairo", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/serialized_output/with_input/branching.cairo", "--layout", "all_cairo", "--cairo_pie_output", "/dev/null"].as_slice())] #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/serialized_output/with_input/branching.cairo", "--layout", "all_cairo", "--proof_mode"].as_slice())] fn test_run_branching_no_args(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Err(Error::ArgumentsSizeMismatch { expected, actual }) if expected == 1 && actual == 0); + assert_matches!(run(args), Err(Error::ArgumentsSizeMismatch { expected, actual }) if expected == 2 && actual == 0); } #[rstest] - #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/with_input/branching.cairo", "--layout", "all_cairo","--args", "1 2 3"].as_slice())] + #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/serialized_output/with_input/branching.cairo", "--layout", "all_cairo","--args", "1 2 3"].as_slice())] #[case(["cairo1-run", "../cairo_programs/cairo-1-programs/serialized_output/with_input/branching.cairo", "--layout", "all_cairo", "--proof_mode", "--args", "1 2 3"].as_slice())] fn test_run_branching_too_many_args(#[case] args: &[&str]) { let args = args.iter().cloned().map(String::from); - assert_matches!(run(args), Err(Error::ArgumentsSizeMismatch { expected, actual }) if expected == 1 && actual == 3); + assert_matches!(run(args), Err(Error::ArgumentsSizeMismatch { expected, actual }) if expected == 2 && actual == 3); } } diff --git a/cairo_programs/cairo-1-programs/serialized_output/with_input/array_input_sum.cairo b/cairo_programs/cairo-1-programs/serialized_output/with_input/array_input_sum.cairo index 44e71731bf..4655562c76 100644 --- a/cairo_programs/cairo-1-programs/serialized_output/with_input/array_input_sum.cairo +++ b/cairo_programs/cairo-1-programs/serialized_output/with_input/array_input_sum.cairo @@ -1,6 +1,9 @@ use array::ArrayTrait; -fn main(index_a: u32, array_a: Array, index_b: u32, array_b: Array) -> Array { +fn main(input: Array) -> Array { + let mut input = input.span(); + let (index_a, array_a, index_b, array_b): (u32, Array, u32, Array) = Serde::deserialize(ref input).unwrap(); + let res = *array_a.at(index_a) + *array_b.at(index_b); let mut output: Array = ArrayTrait::new(); diff --git a/cairo_programs/cairo-1-programs/serialized_output/with_input/array_length.cairo b/cairo_programs/cairo-1-programs/serialized_output/with_input/array_length.cairo index 0400c14ae8..4b1e7850f9 100644 --- a/cairo_programs/cairo-1-programs/serialized_output/with_input/array_length.cairo +++ b/cairo_programs/cairo-1-programs/serialized_output/with_input/array_length.cairo @@ -1,6 +1,9 @@ use array::ArrayTrait; -fn main(array_a: Array, array_b: Array) -> Array { +fn main(input: Array) -> Array { + let mut input = input.span(); + let (array_a, array_b): (Array, Array) = Serde::deserialize(ref input).unwrap(); + let res = array_a.len() + array_b.len(); let mut output: Array = ArrayTrait::new(); res.serialize(ref output); diff --git a/cairo_programs/cairo-1-programs/serialized_output/with_input/branching.cairo b/cairo_programs/cairo-1-programs/serialized_output/with_input/branching.cairo index 3844787ae4..cd65cfc9f6 100644 --- a/cairo_programs/cairo-1-programs/serialized_output/with_input/branching.cairo +++ b/cairo_programs/cairo-1-programs/serialized_output/with_input/branching.cairo @@ -1,4 +1,6 @@ -fn main(argc: u32) -> Array { +fn main(input: Array) -> Array { + let mut input = input.span(); + let argc: u32 = Serde::deserialize(ref input).unwrap(); let res = if argc == 0 { 1_u8 } else { diff --git a/cairo_programs/cairo-1-programs/serialized_output/with_input/tensor.cairo b/cairo_programs/cairo-1-programs/serialized_output/with_input/tensor.cairo index d613159110..624b197ce1 100644 --- a/cairo_programs/cairo-1-programs/serialized_output/with_input/tensor.cairo +++ b/cairo_programs/cairo-1-programs/serialized_output/with_input/tensor.cairo @@ -1,10 +1,11 @@ -#[derive(Copy, Drop)] +#[derive(Copy, Drop, Serde)] struct Tensor { shape: Span, data: Span } - -fn main(tensor: Tensor) -> Array { +fn main(input: Array) -> Array { + let mut input = input.span(); + let tensor : Tensor = Serde::deserialize(ref input).unwrap(); let res = *tensor.data.at(0); let mut output: Array = ArrayTrait::new(); diff --git a/fuzzer/Cargo.lock b/fuzzer/Cargo.lock index 31e011a071..b923c1d944 100644 --- a/fuzzer/Cargo.lock +++ b/fuzzer/Cargo.lock @@ -210,7 +210,7 @@ dependencies = [ [[package]] name = "cairo-vm" -version = "1.0.0-rc2" +version = "1.0.0-rc3" dependencies = [ "anyhow", "arbitrary",