Skip to content

Commit

Permalink
Serialize Array<Felt252> return value into output segment (#1764)
Browse files Browse the repository at this point in the history
* Fix bug

* Refactor

* Clippy

* Update changelog

* Typo

* Simplify logic

* Add fn to check that the return type is Array<Felt252>>

* Add check to cairo_run_program

* First draft

* Progress & conc

* Leave a gap for final builtin pointers if copy_to_output_builtin is true

* Reorder code so we do not lose variables in the middle

* progress

* Success

* 🧹

* Fix bug

* Handle panic flag

* Update fetch return values

* Add doc

* refactor & serialize_output changes

* Move check up

* Fix output builtin final ptr

* Add TODO

* Update tests

* Remove uneeded TODO

* Find the lost segment arena pointer

* Add serialized versions of programs used for cairo 1 testing

* Fix some tests

* Update tests

* Fix some test programs

* Impl custom Serde for tensor_new test

* Make test pass

* Doc

* Custom serialization for felt_dict tests

* More custom serde

* Custom serde

* Clippy

* Update doc

* Remove debug print

* Add CHANGELOG entry

* Update CHANGELOG.md

* Fix typos
  • Loading branch information
fmoletta authored May 22, 2024
1 parent 79cb881 commit f4a2214
Show file tree
Hide file tree
Showing 42 changed files with 1,065 additions and 145 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

#### Upcoming Changes

* feat(BREAKING): Serialize `Array<Felt252>` return value into output segment in cairo1-run crate:
* Checks that only `PanicResult<Array<Felt252>>` or `Array<Felt252>` can be returned by the program when running with either `--proof_mode` or `--append_return_values`.
* Serializes return values into the output segment under the previous conditions following the format:
* `PanicResult<Array<Felt252>>` -> `[panic_flag, array_len, arr[0], arr[1],.., arr[n]]`
* `<Array<Felt252>` -> `[array_len, arr[0], arr[1],.., arr[n]]`

* feat: Handle `BoundedInt` variant in `serialize_output`, `cairo1-run` crate [#1768](https://github.com/lambdaclass/cairo-vm/pull/1768)

* fix: make `OutputBuiltinState` public [#1769](https://github.com/lambdaclass/cairo-vm/pull/1769)
Expand Down
4 changes: 2 additions & 2 deletions cairo1-run/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ The cairo1-run cli supports the following optional arguments:

* `--memory_file <MEMORY_FILE>`: Receives the name of a file and outputs the relocated memory into it

* `--proof_mode`: Runs the program in proof_mode
* `--proof_mode`: Runs the program in proof_mode. Only allows `Array<felt252>` as return value.

* `--air_public_input <AIR_PUBLIC_INPUT>`: Receives the name of a file and outputs the AIR public inputs into it. Can only be used if proof_mode is also enabled.

* `--air_private_input <AIR_PRIVATE_INPUT>`: Receives the name of a file and outputs the AIR private inputs into it. Can only be used if proof_mode, trace_file & memory_file are also enabled.

* `--cairo_pie_output <CAIRO_PIE_OUTPUT>`: Receives the name of a file and outputs the Cairo PIE into it. Can only be used if proof_mode, is not enabled.

* `--append_return_values`: Adds extra instructions to the program in order to append the return values to the output builtin's segment. This is the default behaviour for proof_mode.
* `--append_return_values`: Adds extra instructions to the program in order to append the return values to the output builtin's segment. This is the default behaviour for proof_mode. Only allows `Array<felt252>` as return value.

# Running scarb projects

Expand Down
352 changes: 244 additions & 108 deletions cairo1-run/src/cairo_run.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions cairo1-run/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ pub enum Error {
param_index: usize,
arg_index: usize,
},
#[error("Only programs returning `Array<Felt252>` can be currently proven. Try serializing the final values before returning them")]
IlegalReturnValue,
}
119 changes: 86 additions & 33 deletions cairo1-run/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,67 +282,104 @@ mod tests {
#[case(
"ecdsa_recover.cairo",
"3490001189944926769628658346285649224182856084131963744896357527096042836716",
"[3490001189944926769628658346285649224182856084131963744896357527096042836716]",
None
)]
#[case("tensor_new.cairo", "[1 2] [1 false 1 true]", None)]
#[case("bytes31_ret.cairo", "123", None)]
#[case("null_ret.cairo", "null", None)]
#[case("felt_dict_squash.cairo", "{66675: [4 5 6] 66676: [1 2 3]}", None)]
#[case("dict_with_struct.cairo", "{0: 1 true 1: 1 false 2: 1 true}", None)]
#[case("nullable_box_vec.cairo", "{0: 10 1: 20 2: 30} 3", None)]
#[case("array_integer_tuple.cairo", "[1] 1", None)]
#[case("felt_dict.cairo", "{66675: [8 9 10 11] 66676: [1 2 3]}", None)]
#[case("felt_span.cairo", "[8 9 10 11]", None)]
#[case("nullable_dict.cairo", "", None)]
#[case("struct_span_return.cairo", "[[4 3] [2 1]]", None)]
#[case("null_ret.cairo", "null", None)]
#[case("with_input/tensor.cairo", "1", Some("[2 2] [1 2 3 4]"))]
#[case("with_input/array_input_sum.cairo", "12", Some("2 [1 2 3 4] 0 [9 8]"))]
#[case("with_input/array_length.cairo", "5", Some("[1 2 3 4] [1]"))]
#[case("with_input/array_length.cairo", "4", Some("[1 2 3 4] []"))]
#[case("with_input/branching.cairo", "0", Some("17"))]
#[case("with_input/branching.cairo", "1", Some("0"))]
#[case("dictionaries.cairo", "1024", None)]
#[case("simple_struct.cairo", "100", None)]
#[case("simple.cairo", "true", 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
)]
#[case("bytes31_ret.cairo", "123", "[123]", None)]
#[case("null_ret.cairo", "null", "[]", 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
)]
#[case(
"dict_with_struct.cairo",
"{0: 1 true 1: 1 false 2: 1 true}",
"[0 1 1 1 1 0 2 1 1]",
None
)]
#[case(
"nullable_box_vec.cairo",
"{0: 10 1: 20 2: 30} 3",
"[0 10 1 20 2 30 3]",
None
)]
#[case("array_integer_tuple.cairo", "[1] 1", "[1 1 1]", 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
)]
#[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(
"with_input/array_input_sum.cairo",
"12",
"[12]",
Some("2 [1 2 3 4] 0 [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(
"pedersen_example.cairo",
"1089549915800264549621536909767699778745926517555586332772759280702396009108",
"[1089549915800264549621536909767699778745926517555586332772759280702396009108]",
None
)]
#[case(
"poseidon_pedersen.cairo",
"1036257840396636296853154602823055519264738423488122322497453114874087006398",
"[1036257840396636296853154602823055519264738423488122322497453114874087006398]",
None
)]
#[case(
"poseidon.cairo",
"1099385018355113290651252669115094675591288647745213771718157553170111442461",
"[1099385018355113290651252669115094675591288647745213771718157553170111442461]",
None
)]
#[case("sample.cairo", "5050", None)]
#[case("sample.cairo", "5050", "[5050]", None)]
#[case(
"recursion.cairo",
"1154076154663935037074198317650845438095734251249125412074882362667803016453",
"[1154076154663935037074198317650845438095734251249125412074882362667803016453]",
None
)]
#[case("print.cairo", "", None)]
#[case("ops.cairo", "6", None)]
#[case("hello.cairo", "1234", None)]
#[case("print.cairo", "", "[]", None)]
#[case("ops.cairo", "6", "[6]", None)]
#[case("hello.cairo", "1234", "[1 1234]", None)]
#[case(
"enum_match.cairo",
"10 3618502788666131213697322783095070105623107215331596699973092056135872020471",
"[10 3618502788666131213697322783095070105623107215331596699973092056135872020471]",
None
)]
#[case("enum_flow.cairo", "300", None)]
#[case("array_get.cairo", "3", None)]
#[case("bitwise.cairo", "11772", None)]
#[case("factorial.cairo", "3628800", None)]
#[case("fibonacci.cairo", "89", 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)]

fn test_run_progarm(
#[case] program: &str,
#[case] expected_output: &str,
#[case] expected_serialized_output: &str,
#[case] inputs: Option<&str>,
#[values(
&["--cairo_pie_output", "/dev/null"], // Non proof-mode
Expand All @@ -361,28 +398,44 @@ mod tests {
"all_cairo",
];
let mut args = vec!["cairo1-run"];
let filename = format!("../cairo_programs/cairo-1-programs/{}", program);
let has_serialized_output = extra_flags
.iter()
.any(|flag| flag == &"--append_return_values" || flag == &"--proof_mode");
let filename = if has_serialized_output {
format!(
"../cairo_programs/cairo-1-programs/serialized_output/{}",
program
)
} else {
format!("../cairo_programs/cairo-1-programs/{}", program)
};

args.push(&filename);
args.extend_from_slice(common_flags);
args.extend_from_slice(extra_flags);
if let Some(inputs) = inputs {
args.extend_from_slice(&["--args", inputs])
}
let args = args.iter().cloned().map(String::from);
let expected_output = if has_serialized_output {
expected_serialized_output
} else {
expected_output
};
assert_matches!(run(args), Ok(Some(res)) if res == expected_output, "Program {} failed with flags {}", program, extra_flags.concat());
}

#[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/with_input/branching.cairo", "--layout", "all_cairo", "--proof_mode"].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);
}

#[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/with_input/branching.cairo", "--layout", "all_cairo", "--proof_mode", "--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);
Expand Down
2 changes: 1 addition & 1 deletion cairo_programs/cairo-1-programs/dict_with_struct.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ fn main() -> SquashedFelt252Dict<Nullable<FP16x16>> {
d.squash()
}

// TODO: remove this temporary fixed once fixed in cairo
// TODO: remove this temporary fix once fixed in cairo
#[inline(never)]
fn identity<T>(t: T) -> T { t }
2 changes: 1 addition & 1 deletion cairo_programs/cairo-1-programs/nullable_box_vec.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ fn main() -> NullableVec<u32> {
}
}

// TODO: remove this temporary fixed once fixed in cairo
// TODO: remove this temporary fix once fixed in cairo
#[inline(never)]
fn identity<T>(t: T) -> T { t }
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use array::ArrayTrait;

fn main() -> Array<felt252> {
let mut numbers = ArrayTrait::new();
numbers.append(4_u32);
numbers.append(2_u32);
let _x = numbers.pop_front();
let mut output: Array<felt252> = ArrayTrait::new();
numbers.serialize(ref output);
output
}
13 changes: 13 additions & 0 deletions cairo_programs/cairo-1-programs/serialized_output/array_get.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use array::ArrayTrait;

fn main() -> Array<felt252> {
let mut numbers = ArrayTrait::new();
numbers.append(4_u32);
numbers.append(3_u32);
numbers.append(2_u32);
numbers.append(1_u32);
let res = *numbers.at(1);
let mut output: Array<felt252> = ArrayTrait::new();
res.serialize(ref output);
output
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use core::array::ArrayTrait;


fn main() -> Array<felt252> {
let mut numbers = ArrayTrait::new();
numbers.append(1);

let res = (numbers, 1);
let mut output: Array<felt252> = ArrayTrait::new();
res.serialize(ref output);
output
}
13 changes: 13 additions & 0 deletions cairo_programs/cairo-1-programs/serialized_output/bitwise.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fn main() -> Array<felt252> {
let a = 1234_u128;
let b = 5678_u128;

let c0 = a & b;
let c1 = a ^ b;
let c2 = a | b;

let c3 = c0 + c1 + c2;
let mut output: Array<felt252> = ArrayTrait::new();
c3.serialize(ref output);
output
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() -> Array<felt252> {
let a: u128 = 123;
let b: bytes31 = a.into();
let mut output: Array<felt252> = ArrayTrait::new();
b.serialize(ref output);
output
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use core::nullable::{nullable_from_box, match_nullable, FromNullableResult};


#[derive(Drop, Copy, Serde)]
struct FP16x16 {
mag: u32,
sign: bool
}

fn main() -> Array<felt252> {
// Create the dictionary
let mut d: Felt252Dict<Nullable<FP16x16>> = Default::default();

let box_a = BoxTrait::new(identity(FP16x16 { mag: 1, sign: false }));
let box_b = BoxTrait::new(identity(FP16x16 { mag: 1, sign: true }));
let box_c = BoxTrait::new(identity(FP16x16 { mag: 1, sign: true }));

// Insert it as a `Span`
d.insert(0, nullable_from_box(box_c));
d.insert(1, nullable_from_box(box_a));
d.insert(2, nullable_from_box(box_b));

// We can't implement Serde for a Felt252Dict due to mutability requirements
// So we will serialize the dict explicitely
let mut output: Array<felt252> = ArrayTrait::new();
// Serialize entry 0
0.serialize(ref output);
let array_0 = d.get(0).deref();
array_0.serialize(ref output);
// Serialize entry 1
1.serialize(ref output);
let array_1 = d.get(1).deref();
array_1.serialize(ref output);
// Serialize entry 2
2.serialize(ref output);
let array_2 = d.get(2).deref();
array_2.serialize(ref output);
// Squash after serializing
d.squash();
output
}

// TODO: remove this temporary fix once fixed in cairo
#[inline(never)]
fn identity<T>(t: T) -> T { t }
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use dict::Felt252DictTrait;

fn main() -> Array<felt252> {
let mut dict_u8 = felt252_dict_new::<u8>();
let mut dict_felt = felt252_dict_new::<felt252>();
let _dict_felt2 = felt252_dict_new::<felt252>();

dict_u8.insert(10, 110);
dict_u8.insert(10, 110);

let _val10 = dict_u8[10]; // 110
let _val11 = dict_felt[11]; // 0
dict_felt.insert(11, 1024);
let res = dict_felt[11]; // 1024

let mut output: Array<felt252> = ArrayTrait::new();
res.serialize(ref output);
output
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

fn main() -> Array<felt252> {
let message_hash: felt252 = 0x503f4bea29baee10b22a7f10bdc82dda071c977c1f25b8f3973d34e6b03b2c;
let signature_r: felt252 = 0xbe96d72eb4f94078192c2e84d5230cde2a70f4b45c8797e2c907acff5060bb;
let signature_s: felt252 = 0x677ae6bba6daf00d2631fab14c8acf24be6579f9d9e98f67aa7f2770e57a1f5;
let res = core::ecdsa::recover_public_key(:message_hash, :signature_r, :signature_s, y_parity: false).unwrap();
let mut output: Array<felt252> = ArrayTrait::new();
res.serialize(ref output);
output
}
Loading

0 comments on commit f4a2214

Please sign in to comment.