From 14ec3e33cb33287cf3a012b852af9932364b0bf5 Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Fri, 27 Sep 2024 19:17:37 -0300 Subject: [PATCH 1/2] Add dynamic layout basic support (#1824) * Fix Zero segment location. * Fixed has_zero_segment naming * Fix prover input. * Fixed version reading when no version is supplied * Added change to changelog. * fix test_from_serializable() * fix panic_impl error * fix cairo version * Add dummy changelog * Pin wasm-bindgen * Register change in CHANGELOG * Update Cargo.lock * Remove changes from CHANGELOG * Add argument parsing for layout params file * Add dynamic support (no implement) * Add cairo_layout_params_file.example.json * Implement dynamic layout creation * Update CHANGELOG * Add cli dynamic support for cairo 1 * Make wasm compatible * Use public_memory_fraction = 4 vy default * Deserialize bool from int * Add comparison with python-vm (failing) * Rebuild .rs files in makefile * Use 8 as dynamic public_memory_fraction The same value is used in python-vm * Use None ratio for dynamic unused builtins * Add rangecheck96 to private inputs * Make dyn py files depend on params_file * Use cpu_component_step=1 by default * Fix typo in private inputs * Add range check value to air private input test * Fix zero segment location * Use zero builtin instead of None * Add debug scripts * Remove dup makefile recipes * remove outdated test * Enable ensure-no_std on test * Fix tests * Add correct test * Rename tset * Add comment * Add debugging document * Update cairo layout params file * Remove duplicated range check * Remove dup * Remove debugging and scrippts (moveed to another branch) * Add comment * Add tests * Add dynamic test to cairo-vm-cli * Add parse test * Remove compare all dynamic * Add script for comparing with dynamic layouts * Add tests to workflow * Delete logic changes They are going to be moved to another branch * Delete more logic changes * Update rust.yml * Rename compare_outputs_dynamic_layout.sh to compare_outputs_dynamic_layouts.sh * Update test script * Add more tests * Rename parameter for clarity * Enable mod builtin only on feature with dynamic layout * Remove debug assert * Refactor errors into variants * Fix failing test * Move cairo_layout_params_file to test folder * Document cairo_layout_param_file in the README.md * Fix clippy warning * Use mod_builtin feature in tests --------- Co-authored-by: Alon Titelman Co-authored-by: Yuval Goldberg Co-authored-by: Omri Eshhar --- .github/workflows/rust.yml | 39 ++ .gitignore | 1 + CHANGELOG.md | 5 + README.md | 2 + bench/criterion_benchmark.rs | 12 +- bench/iai_benchmark.rs | 10 +- cairo-vm-cli/src/main.rs | 25 ++ cairo1-run/src/cairo_run.rs | 7 +- cairo1-run/src/main.rs | 25 ++ fuzzer/Cargo.lock | 2 +- vm/src/cairo_run.rs | 12 +- vm/src/tests/cairo_layout_params_file.json | 29 ++ vm/src/tests/cairo_run_test.rs | 1 + .../tests/compare_outputs_dynamic_layouts.sh | 164 +++++++++ vm/src/tests/mod.rs | 2 + .../builtins_instance_def.rs | 84 +++-- vm/src/types/layout.rs | 335 +++++++++++++++++- vm/src/types/layout_name.rs | 2 +- vm/src/utils.rs | 9 +- vm/src/vm/errors/runner_errors.rs | 4 + vm/src/vm/runners/builtin_runner/modulo.rs | 3 +- vm/src/vm/runners/cairo_runner.rs | 29 +- 22 files changed, 744 insertions(+), 58 deletions(-) create mode 100644 vm/src/tests/cairo_layout_params_file.json create mode 100755 vm/src/tests/compare_outputs_dynamic_layouts.sh diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0efa960359..6dde21ab95 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -725,6 +725,45 @@ jobs: - name: Run script run: ./vm/src/tests/compare_factorial_outputs_all_layouts.sh + compare-outputs-dynamic-layouts: + name: Compare outputs with dynamic layouts + needs: [ build-programs, build-release ] + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Python3 Build + uses: actions/setup-python@v4 + with: + python-version: '3.9' + cache: 'pip' + + - name: Install cairo-lang and deps + run: pip install -r requirements.txt + + - name: Fetch release binary + uses: actions/cache/restore@v3 + with: + key: cli-bin-rel-${{ github.sha }} + path: target/release/cairo-vm-cli + fail-on-cache-miss: true + + - uses: actions/download-artifact@master + with: + name: proof_programs + path: cairo_programs/proof_programs/ + + - name: Fetch programs + uses: actions/cache/restore@v3 + with: + path: ${{ env.CAIRO_PROGRAMS_PATH }} + key: cairo_proof_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'examples/wasm-demo/src/array_sum.cairo') }} + fail-on-cache-miss: true + + - name: Run script + run: ./vm/src/tests/compare_outputs_dynamic_layouts.sh + compare-run-from-cairo-pie-all-outputs: name: Compare all outputs from running Cairo PIEs needs: [ build-programs, build-release, run-cairo-release ] diff --git a/.gitignore b/.gitignore index 69a905dfdf..69fa82e403 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ cairo-vm-cli/air_input.pub ensure-no_std/Cargo.lock cairo_programs/proof_programs/*.cairo +!cairo_layout_params_file.json !vm/src/tests/cairo_pie_test_output.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 03c22eb7fd..3317000d50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ #### Upcoming Changes +* feat(BREAKING): [#1824](https://github.com/lambdaclass/cairo-vm/pull/1824): + * Add support for dynamic layout + * CLI change(BREAKING): The flag `cairo_layout_params_file` must be specified when using dynamic layout. + * Signature change(BREAKING): Both `CairoRunner::new` and `CairoRunner::new_v2` now receive an `Option`, used only with dynamic layout. + * chore: bump pip `cairo-lang` 0.13.2 [#1827](https://github.com/lambdaclass/cairo-vm/pull/1827) * chore: bump `cairo-lang-` dependencies to 2.8.0 [#1833](https://github.com/lambdaclass/cairo-vm/pull/1833/files) diff --git a/README.md b/README.md index 0618a0fcea..a7ecf920dc 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,8 @@ The cairo-vm-cli supports the following optional arguments: - `run_from_cairo_pie`: Runs a Cairo PIE instead of a compiled json file. The name of the file will be the first argument received by the CLI (as if it were to run a normal compiled program). Can only be used if proof_mode is not enabled. +- `cairo_layout_params_file`: Only used with dynamic layout. Receives the name of a json file with the dynamic layout parameters. + For example, to obtain the air public inputs from a fibonacci program run, we can run : ```bash diff --git a/bench/criterion_benchmark.rs b/bench/criterion_benchmark.rs index 7078ad21a8..d473fe61f6 100644 --- a/bench/criterion_benchmark.rs +++ b/bench/criterion_benchmark.rs @@ -30,6 +30,7 @@ fn build_many_runners(c: &mut Criterion) { CairoRunner::new( black_box(&program), black_box(LayoutName::starknet_with_keccak), + black_box(None), black_box(false), black_box(false), ) @@ -45,7 +46,16 @@ fn load_program_data(c: &mut Criterion) { let program = Program::from_bytes(program.as_slice(), Some("main")).unwrap(); c.bench_function("initialize", |b| { b.iter_batched( - || CairoRunner::new(&program, LayoutName::starknet_with_keccak, false, false).unwrap(), + || { + CairoRunner::new( + &program, + LayoutName::starknet_with_keccak, + None, + false, + false, + ) + .unwrap() + }, |mut runner| _ = black_box(runner.initialize(false).unwrap()), BatchSize::SmallInput, ) diff --git a/bench/iai_benchmark.rs b/bench/iai_benchmark.rs index b5bff69dce..441f79b5bc 100644 --- a/bench/iai_benchmark.rs +++ b/bench/iai_benchmark.rs @@ -34,6 +34,7 @@ fn build_runner() { let runner = CairoRunner::new( black_box(&program), LayoutName::starknet_with_keccak, + None, false, false, ) @@ -47,7 +48,14 @@ fn build_runner_helper() -> CairoRunner { //Picked the biggest one at the time of writing let program = include_bytes!("../cairo_programs/benchmarks/keccak_integration_benchmark.json"); let program = Program::from_bytes(program.as_slice(), Some("main")).unwrap(); - CairoRunner::new(&program, LayoutName::starknet_with_keccak, false, false).unwrap() + CairoRunner::new( + &program, + LayoutName::starknet_with_keccak, + None, + false, + false, + ) + .unwrap() } #[inline(never)] diff --git a/cairo-vm-cli/src/main.rs b/cairo-vm-cli/src/main.rs index 08095128ca..51cb3a649a 100644 --- a/cairo-vm-cli/src/main.rs +++ b/cairo-vm-cli/src/main.rs @@ -6,6 +6,7 @@ use cairo_vm::cairo_run::{self, EncodeTraceError}; use cairo_vm::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; #[cfg(feature = "with_tracer")] use cairo_vm::serde::deserialize_program::DebugInfo; +use cairo_vm::types::layout::CairoLayoutParams; use cairo_vm::types::layout_name::LayoutName; use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; use cairo_vm::vm::errors::trace_errors::TraceError; @@ -43,8 +44,13 @@ struct Args { entrypoint: String, #[structopt(long = "memory_file")] memory_file: Option, + /// When using dynamic layout, it's parameters must be specified through a layout params file. #[clap(long = "layout", default_value = "plain", value_enum)] layout: LayoutName, + /// Required when using with dynamic layout. + /// Ignored otherwise. + #[clap(long = "cairo_layout_params_file", required_if_eq("layout", "dynamic"))] + cairo_layout_params_file: Option, #[structopt(long = "proof_mode")] proof_mode: bool, #[structopt(long = "secure_run")] @@ -162,6 +168,11 @@ fn run(args: impl Iterator) -> Result<(), Error> { let trace_enabled = args.trace_file.is_some() || args.air_public_input.is_some(); + let cairo_layout_params = match args.cairo_layout_params_file { + Some(file) => Some(CairoLayoutParams::from_file(&file)?), + None => None, + }; + let cairo_run_config = cairo_run::CairoRunConfig { entrypoint: &args.entrypoint, trace_enabled, @@ -170,6 +181,7 @@ fn run(args: impl Iterator) -> Result<(), Error> { proof_mode: args.proof_mode, secure_run: args.secure_run, allow_missing_builtins: args.allow_missing_builtins, + dynamic_layout_params: cairo_layout_params, ..Default::default() }; @@ -405,6 +417,19 @@ mod tests { assert_matches!(run(args), Err(Error::Runner(_))); } + #[test] + fn test_run_dynamic_params() { + let mut args = vec!["cairo-vm-cli".to_string()]; + args.extend_from_slice(&["--layout".to_string(), "dynamic".to_string()]); + args.extend_from_slice(&[ + "--cairo_layout_params_file".to_string(), + "../vm/src/tests/cairo_layout_params_file.json".to_string(), + ]); + args.push("../cairo_programs/proof_programs/fibonacci.json".to_string()); + + assert_matches!(run(args.into_iter()), Ok(_)); + } + //Since the functionality here is trivial, I just call the function //to fool Codecov. #[test] diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs index 3f8095e160..bf654e9724 100644 --- a/cairo1-run/src/cairo_run.rs +++ b/cairo1-run/src/cairo_run.rs @@ -38,8 +38,8 @@ use cairo_vm::{ math_utils::signed_felt, serde::deserialize_program::{ApTracking, FlowTrackingData, HintParams, ReferenceManager}, types::{ - builtin_name::BuiltinName, layout_name::LayoutName, program::Program, - relocatable::MaybeRelocatable, + builtin_name::BuiltinName, layout::CairoLayoutParams, layout_name::LayoutName, + program::Program, relocatable::MaybeRelocatable, }, vm::{ errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError}, @@ -86,6 +86,7 @@ pub struct Cairo1RunConfig<'a> { pub relocate_mem: bool, /// Cairo layout chosen for the run pub layout: LayoutName, + pub dynamic_layout_params: Option, /// Run in proof_mode pub proof_mode: bool, /// Should be true if either air_public_input or cairo_pie_output are needed @@ -106,6 +107,7 @@ impl Default for Cairo1RunConfig<'_> { proof_mode: false, finalize_builtins: false, append_return_values: false, + dynamic_layout_params: None, } } } @@ -248,6 +250,7 @@ pub fn cairo_run_program( let mut runner = CairoRunner::new_v2( &program, cairo_run_config.layout, + cairo_run_config.dynamic_layout_params.clone(), runner_mode, cairo_run_config.trace_enabled, )?; diff --git a/cairo1-run/src/main.rs b/cairo1-run/src/main.rs index 47c9abfbca..edad364df1 100644 --- a/cairo1-run/src/main.rs +++ b/cairo1-run/src/main.rs @@ -4,6 +4,7 @@ use cairo1_run::{cairo_run_program, Cairo1RunConfig, FuncArg}; use cairo_lang_compiler::{ compile_prepared_db, db::RootDatabase, project::setup_project, CompilerConfig, }; +use cairo_vm::types::layout::CairoLayoutParams; use cairo_vm::{ air_public_input::PublicInputError, types::layout_name::LayoutName, vm::errors::trace_errors::TraceError, Felt252, @@ -24,8 +25,13 @@ struct Args { trace_file: Option, #[structopt(long = "memory_file")] memory_file: Option, + /// When using dynamic layout, it's parameters must be specified through a layout params file. #[clap(long = "layout", default_value = "plain", value_enum)] layout: LayoutName, + /// Required when using with dynamic layout. + /// Ignored otherwise. + #[clap(long = "cairo_layout_params_file", required_if_eq("layout", "dynamic"))] + cairo_layout_params_file: Option, #[clap(long = "proof_mode", value_parser)] proof_mode: bool, #[clap(long = "air_public_input", requires = "proof_mode")] @@ -153,6 +159,11 @@ fn run(args: impl Iterator) -> Result, Error> { args.args = process_args(&std::fs::read_to_string(filename)?).unwrap(); } + let cairo_layout_params = match args.cairo_layout_params_file { + Some(file) => Some(CairoLayoutParams::from_file(&file)?), + None => None, + }; + let cairo_run_config = Cairo1RunConfig { proof_mode: args.proof_mode, serialize_output: args.print_output, @@ -162,6 +173,7 @@ fn run(args: impl Iterator) -> Result, Error> { args: &args.args.0, finalize_builtins: args.air_public_input.is_some() || args.cairo_pie_output.is_some(), append_return_values: args.append_return_values, + dynamic_layout_params: cairo_layout_params, }; // Try to parse the file as a sierra program @@ -478,6 +490,19 @@ mod tests { assert_matches!(run(args), Ok(Some(res)) if res == expected_output, "Program {} failed with flags {}", program, extra_flags.concat()); } + #[test] + fn test_run_dynamic_params() { + let mut args = vec!["cairo1-run".to_string()]; + args.extend_from_slice(&["--layout".to_string(), "dynamic".to_string()]); + args.extend_from_slice(&[ + "--cairo_layout_params_file".to_string(), + "../vm/src/tests/cairo_layout_params_file.json".to_string(), + ]); + args.push("../cairo_programs/cairo-1-programs/fibonacci.cairo".to_string()); + + assert_matches!(run(args.into_iter()), Ok(_)); + } + // these tests are separated so as to run them without --append_return_values and --proof_mode options // since they require to use the squashed version of felt252 #[rstest] diff --git a/fuzzer/Cargo.lock b/fuzzer/Cargo.lock index e1ccedbfb8..e1ef312c3a 100644 --- a/fuzzer/Cargo.lock +++ b/fuzzer/Cargo.lock @@ -216,7 +216,7 @@ dependencies = [ [[package]] name = "cairo-vm" -version = "1.0.0-rc5" +version = "1.0.1" dependencies = [ "anyhow", "arbitrary", diff --git a/vm/src/cairo_run.rs b/vm/src/cairo_run.rs index de2540fb2e..0e61856a3b 100644 --- a/vm/src/cairo_run.rs +++ b/vm/src/cairo_run.rs @@ -1,6 +1,9 @@ use crate::{ hint_processor::hint_processor_definition::HintProcessor, - types::{builtin_name::BuiltinName, layout_name::LayoutName, program::Program}, + types::{ + builtin_name::BuiltinName, layout::CairoLayoutParams, layout_name::LayoutName, + program::Program, + }, vm::{ errors::{ cairo_run_errors::CairoRunError, runner_errors::RunnerError, vm_exception::VmException, @@ -26,6 +29,9 @@ pub struct CairoRunConfig<'a> { pub trace_enabled: bool, pub relocate_mem: bool, pub layout: LayoutName, + /// The `dynamic_layout_params` argument should only be used with dynamic layout. + /// It is ignored otherwise. + pub dynamic_layout_params: Option, pub proof_mode: bool, pub secure_run: Option, pub disable_trace_padding: bool, @@ -43,6 +49,7 @@ impl<'a> Default for CairoRunConfig<'a> { secure_run: None, disable_trace_padding: false, allow_missing_builtins: None, + dynamic_layout_params: None, } } } @@ -65,6 +72,7 @@ pub fn cairo_run_program_with_initial_scope( let mut cairo_runner = CairoRunner::new( program, cairo_run_config.layout, + cairo_run_config.dynamic_layout_params.clone(), cairo_run_config.proof_mode, cairo_run_config.trace_enabled, )?; @@ -151,6 +159,7 @@ pub fn cairo_run_pie( let mut cairo_runner = CairoRunner::new( &program, cairo_run_config.layout, + cairo_run_config.dynamic_layout_params.clone(), false, cairo_run_config.trace_enabled, )?; @@ -223,6 +232,7 @@ pub fn cairo_run_fuzzed_program( let mut cairo_runner = CairoRunner::new( &program, cairo_run_config.layout, + cairo_run_config.dynamic_layout_params.clone(), cairo_run_config.proof_mode, cairo_run_config.trace_enabled, )?; diff --git a/vm/src/tests/cairo_layout_params_file.json b/vm/src/tests/cairo_layout_params_file.json new file mode 100644 index 0000000000..f4d0d736f5 --- /dev/null +++ b/vm/src/tests/cairo_layout_params_file.json @@ -0,0 +1,29 @@ +{ + "rc_units": 4, + "log_diluted_units_per_step": 4, + "cpu_component_step": 8, + "memory_units_per_step": 8, + "uses_pedersen_builtin": true, + "pedersen_ratio": 256, + "uses_range_check_builtin": true, + "range_check_ratio": 8, + "uses_ecdsa_builtin": true, + "ecdsa_ratio": 2048, + "uses_bitwise_builtin": true, + "bitwise_ratio": 16, + "uses_ec_op_builtin": true, + "ec_op_ratio": 1024, + "uses_keccak_builtin": true, + "keccak_ratio": 2048, + "uses_poseidon_builtin": true, + "poseidon_ratio": 256, + "uses_range_check96_builtin": true, + "range_check96_ratio": 8, + "range_check96_ratio_den": 1, + "uses_add_mod_builtin": true, + "add_mod_ratio": 128, + "add_mod_ratio_den": 1, + "uses_mul_mod_builtin": true, + "mul_mod_ratio": 256, + "mul_mod_ratio_den": 1 +} diff --git a/vm/src/tests/cairo_run_test.rs b/vm/src/tests/cairo_run_test.rs index ce42ca31dd..55126c98df 100644 --- a/vm/src/tests/cairo_run_test.rs +++ b/vm/src/tests/cairo_run_test.rs @@ -1182,6 +1182,7 @@ fn run_program_with_custom_mod_builtin_params( let mut cairo_runner = CairoRunner::new( &program, cairo_run_config.layout, + cairo_run_config.dynamic_layout_params, cairo_run_config.proof_mode, cairo_run_config.trace_enabled, ) diff --git a/vm/src/tests/compare_outputs_dynamic_layouts.sh b/vm/src/tests/compare_outputs_dynamic_layouts.sh new file mode 100755 index 0000000000..7077edaa44 --- /dev/null +++ b/vm/src/tests/compare_outputs_dynamic_layouts.sh @@ -0,0 +1,164 @@ +#!/usr/bin/env bash +# +# Compares programs with different dynamic layouts against cairo-lang + + +# Build temporary dynamic layout params files +TEMP_FOLDER=$(mktemp -d) +cat < "$TEMP_FOLDER/all_cairo.json" +{ + "rc_units": 4, + "log_diluted_units_per_step": 4, + "cpu_component_step": 8, + "memory_units_per_step": 8, + "uses_pedersen_builtin": true, + "pedersen_ratio": 256, + "uses_range_check_builtin": true, + "range_check_ratio": 8, + "uses_ecdsa_builtin": true, + "ecdsa_ratio": 2048, + "uses_bitwise_builtin": true, + "bitwise_ratio": 16, + "uses_ec_op_builtin": true, + "ec_op_ratio": 1024, + "uses_keccak_builtin": true, + "keccak_ratio": 2048, + "uses_poseidon_builtin": true, + "poseidon_ratio": 256, + "uses_range_check96_builtin": true, + "range_check96_ratio": 8, + "range_check96_ratio_den": 1, + "uses_add_mod_builtin": true, + "add_mod_ratio": 128, + "add_mod_ratio_den": 1, + "uses_mul_mod_builtin": true, + "mul_mod_ratio": 256, + "mul_mod_ratio_den": 1 +} +EOF +cat < "$TEMP_FOLDER/double_all_cairo.json" +{ + "rc_units": 8, + "log_diluted_units_per_step": 8, + "cpu_component_step": 16, + "memory_units_per_step": 16, + "uses_pedersen_builtin": true, + "pedersen_ratio": 512, + "uses_range_check_builtin": true, + "range_check_ratio": 16, + "uses_ecdsa_builtin": true, + "ecdsa_ratio": 4096, + "uses_bitwise_builtin": true, + "bitwise_ratio": 32, + "uses_ec_op_builtin": true, + "ec_op_ratio": 2048, + "uses_keccak_builtin": true, + "keccak_ratio": 4096, + "uses_poseidon_builtin": true, + "poseidon_ratio": 512, + "uses_range_check96_builtin": true, + "range_check96_ratio": 16, + "range_check96_ratio_den": 1, + "uses_add_mod_builtin": true, + "add_mod_ratio": 256, + "add_mod_ratio_den": 1, + "uses_mul_mod_builtin": true, + "mul_mod_ratio": 512, + "mul_mod_ratio_den": 1 +} +EOF + +# Build cases to execute +CASES=( + "cairo_programs/proof_programs/factorial.json;all_cairo" + "cairo_programs/proof_programs/factorial.json;double_all_cairo" + "cairo_programs/proof_programs/fibonacci.json;all_cairo" + "cairo_programs/proof_programs/fibonacci.json;double_all_cairo" + "cairo_programs/proof_programs/bigint.json;all_cairo" + "cairo_programs/proof_programs/bigint.json;double_all_cairo" + "cairo_programs/proof_programs/dict.json;all_cairo" + "cairo_programs/proof_programs/dict.json;double_all_cairo" + "cairo_programs/proof_programs/sha256.json;all_cairo" + "cairo_programs/proof_programs/sha256.json;double_all_cairo" + "cairo_programs/proof_programs/keccak.json;all_cairo" + "cairo_programs/proof_programs/keccak.json;double_all_cairo" +) + +passed_tests=0 +failed_tests=0 +exit_code=0 + +for case in "${CASES[@]}"; do + IFS=";" read -r program layout <<< "$case" + + full_program="$program" + full_layout="$TEMP_FOLDER/$layout.json" + + # Run cairo-vm + echo "Running cairo-vm with case: $case" + cargo run -p cairo-vm-cli --features mod_builtin --release -- "$full_program" \ + --layout "dynamic" --cairo_layout_params_file "$full_layout" --proof_mode \ + --trace_file program_rs.trace --memory_file program_rs.memory --air_public_input program_rs.air_public_input --air_private_input program_rs.air_private_input + + # Run cairo-lang + echo "Running cairo-lang with case: $case" + cairo-run --program "$full_program" \ + --layout "dynamic" --cairo_layout_params_file "$full_layout" --proof_mode \ + --trace_file program_py.trace --memory_file program_py.memory --air_public_input program_py.air_public_input --air_private_input program_py.air_private_input + + # Compare trace + echo "Running trace comparison for case: $case" + if ! diff -q program_rs.trace program_py.trace; then + echo "Trace differs for case: $case" + exit_code=1 + failed_tests=$((failed_tests + 1)) + else + passed_tests=$((passed_tests + 1)) + fi + + # Compare memory + echo "Running memory comparison for case: $case" + if ! ./vm/src/tests/memory_comparator.py program_rs.memory program_py.memory; then + echo "Memory differs for case: $case" + exit_code=1 + failed_tests=$((failed_tests + 1)) + else + passed_tests=$((passed_tests + 1)) + fi + + # Compare air public input + echo "Running air public input comparison for case: $case" + if ! ./vm/src/tests/air_public_input_comparator.py program_rs.air_public_input program_py.air_public_input; then + echo "Air public input differs for case: $case" + exit_code=1 + failed_tests=$((failed_tests + 1)) + else + passed_tests=$((passed_tests + 1)) + fi + + # Compare air private input + echo "Running air private input comparison for case: $case" + if ! ./vm/src/tests/air_private_input_comparator.py program_rs.air_private_input program_py.air_private_input; then + echo "Air private input differs for case: $case" + exit_code=1 + failed_tests=$((failed_tests + 1)) + else + passed_tests=$((passed_tests + 1)) + fi + + # Clean files generated by the script + echo "Cleaning files" + rm program_rs.* + rm program_py.* +done + +if test $failed_tests != 0; then + echo "Comparisons: $failed_tests failed, $passed_tests passed, $((failed_tests + passed_tests)) total" +elif test $passed_tests = 0; then + echo "No tests ran!" + exit_code=2 +else + echo "All $passed_tests tests passed; no discrepancies found" +fi + +exit "${exit_code}" diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index 6df6e743fe..9141c3a0f9 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -113,6 +113,7 @@ fn run_cairo_1_entrypoint( let mut runner = CairoRunner::new( &(contract_class.clone().try_into().unwrap()), LayoutName::all_cairo, + None, false, false, ) @@ -217,6 +218,7 @@ fn run_cairo_1_entrypoint_with_run_resources( let mut runner = CairoRunner::new( &(contract_class.clone().try_into().unwrap()), LayoutName::all_cairo, + None, false, false, ) diff --git a/vm/src/types/instance_definitions/builtins_instance_def.rs b/vm/src/types/instance_definitions/builtins_instance_def.rs index ab13cf14fb..cf7d420636 100644 --- a/vm/src/types/instance_definitions/builtins_instance_def.rs +++ b/vm/src/types/instance_definitions/builtins_instance_def.rs @@ -1,3 +1,5 @@ +use crate::types::layout::CairoLayoutParams; + use super::mod_instance_def::ModInstanceDef; use super::{ bitwise_instance_def::BitwiseInstanceDef, ec_op_instance_def::EcOpInstanceDef, @@ -192,25 +194,60 @@ impl BuiltinsInstanceDef { } } - pub(crate) fn dynamic() -> BuiltinsInstanceDef { + pub(crate) fn dynamic(params: CairoLayoutParams) -> BuiltinsInstanceDef { + let pedersen = Some(PedersenInstanceDef { + ratio: Some(params.pedersen_ratio), + }); + let range_check = Some(RangeCheckInstanceDef { + ratio: Some(params.range_check_ratio), + }); + let ecdsa = Some(EcdsaInstanceDef { + ratio: Some(params.ecdsa_ratio), + }); + let bitwise = Some(BitwiseInstanceDef { + ratio: Some(params.bitwise_ratio), + }); + let ec_op = Some(EcOpInstanceDef { + ratio: Some(params.ec_op_ratio), + }); + let keccak = Some(KeccakInstanceDef { + ratio: Some(params.keccak_ratio), + }); + let poseidon = Some(PoseidonInstanceDef { + ratio: Some(params.poseidon_ratio), + }); + let range_check96 = Some(RangeCheckInstanceDef { + ratio: Some(params.range_check96_ratio), + }); + #[cfg(feature = "mod_builtin")] + let add_mod = Some(ModInstanceDef { + ratio: Some(params.add_mod_ratio), + word_bit_len: 96, + batch_size: 1, + }); + #[cfg(feature = "mod_builtin")] + let mul_mod = Some(ModInstanceDef { + ratio: Some(params.mul_mod_ratio), + word_bit_len: 96, + batch_size: 1, + }); + #[cfg(not(feature = "mod_builtin"))] + let add_mod = None; + #[cfg(not(feature = "mod_builtin"))] + let mul_mod = None; + BuiltinsInstanceDef { output: true, - pedersen: Some(PedersenInstanceDef::new(None)), - range_check: Some(RangeCheckInstanceDef::new(None)), - ecdsa: Some(EcdsaInstanceDef::new(None)), - bitwise: Some(BitwiseInstanceDef::new(None)), - ec_op: Some(EcOpInstanceDef::new(None)), - keccak: None, - poseidon: None, - range_check96: None, - #[cfg(feature = "mod_builtin")] - add_mod: Some(ModInstanceDef::new(None, 1, 96)), - #[cfg(feature = "mod_builtin")] - mul_mod: Some(ModInstanceDef::new(None, 1, 96)), - #[cfg(not(feature = "mod_builtin"))] - add_mod: None, - #[cfg(not(feature = "mod_builtin"))] - mul_mod: None, + pedersen, + range_check, + ecdsa, + bitwise, + ec_op, + keccak, + poseidon, + range_check96, + add_mod, + mul_mod, } } } @@ -342,17 +379,4 @@ mod tests { assert!(builtins.keccak.is_none()); assert!(builtins.poseidon.is_none()); } - - #[test] - fn get_builtins_dynamic() { - let builtins = BuiltinsInstanceDef::dynamic(); - assert!(builtins.output); - assert!(builtins.pedersen.is_some()); - assert!(builtins.range_check.is_some()); - assert!(builtins.ecdsa.is_some()); - assert!(builtins.bitwise.is_some()); - assert!(builtins.ec_op.is_some()); - assert!(builtins.keccak.is_none()); - assert!(builtins.poseidon.is_none()); - } } diff --git a/vm/src/types/layout.rs b/vm/src/types/layout.rs index ee0fae3196..b1f52e7698 100644 --- a/vm/src/types/layout.rs +++ b/vm/src/types/layout.rs @@ -1,12 +1,16 @@ -use crate::types::layout_name::LayoutName; +use crate::{types::layout_name::LayoutName, vm::errors::runner_errors::RunnerError}; -use super::instance_definitions::{ - builtins_instance_def::BuiltinsInstanceDef, diluted_pool_instance_def::DilutedPoolInstanceDef, +use super::{ + builtin_name::BuiltinName, + instance_definitions::{ + builtins_instance_def::BuiltinsInstanceDef, + diluted_pool_instance_def::DilutedPoolInstanceDef, + }, }; pub(crate) const MEMORY_UNITS_PER_STEP: u32 = 8; -use serde::Serialize; +use serde::{Deserialize, Deserializer, Serialize}; #[derive(Serialize, Debug)] pub struct CairoLayout { @@ -117,20 +121,208 @@ impl CairoLayout { } } - pub(crate) fn dynamic_instance() -> CairoLayout { + pub(crate) fn dynamic_instance(params: CairoLayoutParams) -> CairoLayout { CairoLayout { name: LayoutName::dynamic, - rc_units: 16, - builtins: BuiltinsInstanceDef::dynamic(), + rc_units: params.rc_units, public_memory_fraction: 8, - diluted_pool_instance_def: Some(DilutedPoolInstanceDef::default()), + diluted_pool_instance_def: Some(DilutedPoolInstanceDef { + units_per_step: 2_u32.pow(params.log_diluted_units_per_step), + ..DilutedPoolInstanceDef::default() + }), + builtins: BuiltinsInstanceDef::dynamic(params), + } + } +} + +#[cfg(feature = "test_utils")] +use arbitrary::{self, Arbitrary}; + +#[cfg_attr(feature = "test_utils", derive(Arbitrary))] +#[derive(Deserialize, Debug, Clone, Default)] +#[serde(try_from = "RawCairoLayoutParams")] +pub struct CairoLayoutParams { + pub rc_units: u32, + pub log_diluted_units_per_step: u32, + pub pedersen_ratio: u32, + pub range_check_ratio: u32, + pub ecdsa_ratio: u32, + pub bitwise_ratio: u32, + pub ec_op_ratio: u32, + pub keccak_ratio: u32, + pub poseidon_ratio: u32, + pub range_check96_ratio: u32, + pub add_mod_ratio: u32, + pub mul_mod_ratio: u32, + // the following are not used right now + pub cpu_component_step: u32, + pub memory_units_per_step: u32, + pub range_check96_ratio_den: u32, + pub mul_mod_ratio_den: u32, + pub add_mod_ratio_den: u32, +} + +impl CairoLayoutParams { + #[cfg(feature = "std")] + pub fn from_file(params_path: &std::path::Path) -> std::io::Result { + let params_file = std::fs::File::open(params_path)?; + let params = serde_json::from_reader(params_file)?; + Ok(params) + } +} + +// The CairoLayoutParams contains aditional constraints that can't be validated by serde alone. +// To work around this. we use an aditional structure `RawCairoLayoutParams` that gets deserialized by serde +// and then its tranformed into `CairoLayoutParams`. + +#[derive(Deserialize, Debug, Default, Clone)] +pub struct RawCairoLayoutParams { + pub rc_units: u32, + pub log_diluted_units_per_step: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_pedersen_builtin: bool, + pub pedersen_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_range_check_builtin: bool, + pub range_check_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_ecdsa_builtin: bool, + pub ecdsa_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_bitwise_builtin: bool, + pub bitwise_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_ec_op_builtin: bool, + pub ec_op_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_keccak_builtin: bool, + pub keccak_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_poseidon_builtin: bool, + pub poseidon_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_range_check96_builtin: bool, + pub range_check96_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_add_mod_builtin: bool, + pub add_mod_ratio: u32, + #[serde(deserialize_with = "bool_from_int_or_bool")] + pub uses_mul_mod_builtin: bool, + pub mul_mod_ratio: u32, + // the following are not used right now + pub cpu_component_step: u32, + pub memory_units_per_step: u32, + pub range_check96_ratio_den: u32, + pub mul_mod_ratio_den: u32, + pub add_mod_ratio_den: u32, +} + +impl TryFrom for CairoLayoutParams { + type Error = RunnerError; + + fn try_from(value: RawCairoLayoutParams) -> Result { + if !value.uses_pedersen_builtin && value.pedersen_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::pedersen, + )); + } + if !value.uses_range_check_builtin && value.range_check_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::range_check, + )); + } + if !value.uses_ecdsa_builtin && value.ecdsa_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::ecdsa, + )); + } + if !value.uses_bitwise_builtin && value.bitwise_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::bitwise, + )); + } + if !value.uses_ec_op_builtin && value.ec_op_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::ec_op, + )); + } + if !value.uses_keccak_builtin && value.keccak_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::keccak, + )); + } + if !value.uses_poseidon_builtin && value.poseidon_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::poseidon, + )); + } + if !value.uses_range_check96_builtin && value.range_check96_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::range_check96, + )); } + if !value.uses_add_mod_builtin && value.add_mod_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::add_mod, + )); + } + if !value.uses_mul_mod_builtin && value.mul_mod_ratio != 0 { + return Err(RunnerError::BadDynamicLayoutBuiltinRatio( + BuiltinName::mul_mod, + )); + } + + Ok(CairoLayoutParams { + rc_units: value.rc_units, + log_diluted_units_per_step: value.log_diluted_units_per_step, + cpu_component_step: value.cpu_component_step, + memory_units_per_step: value.memory_units_per_step, + range_check96_ratio_den: value.range_check96_ratio_den, + mul_mod_ratio_den: value.mul_mod_ratio_den, + add_mod_ratio_den: value.add_mod_ratio_den, + pedersen_ratio: value.pedersen_ratio, + range_check_ratio: value.range_check_ratio, + ecdsa_ratio: value.ecdsa_ratio, + bitwise_ratio: value.bitwise_ratio, + ec_op_ratio: value.ec_op_ratio, + keccak_ratio: value.keccak_ratio, + poseidon_ratio: value.poseidon_ratio, + range_check96_ratio: value.range_check96_ratio, + add_mod_ratio: value.add_mod_ratio, + mul_mod_ratio: value.mul_mod_ratio, + }) + } +} + +fn bool_from_int_or_bool<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum IntOrBool { + Int(i64), + Boolean(bool), + } + + match IntOrBool::deserialize(deserializer)? { + IntOrBool::Int(0) => Ok(false), + IntOrBool::Int(_) => Ok(true), + IntOrBool::Boolean(v) => Ok(v), } } #[cfg(test)] mod tests { use super::*; + #[cfg(feature = "mod_builtin")] + use crate::types::instance_definitions::mod_instance_def::ModInstanceDef; + use crate::types::instance_definitions::{ + bitwise_instance_def::BitwiseInstanceDef, ec_op_instance_def::EcOpInstanceDef, + ecdsa_instance_def::EcdsaInstanceDef, keccak_instance_def::KeccakInstanceDef, + pedersen_instance_def::PedersenInstanceDef, poseidon_instance_def::PoseidonInstanceDef, + range_check_instance_def::RangeCheckInstanceDef, + }; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::*; @@ -257,15 +449,130 @@ mod tests { #[test] fn get_dynamic_instance() { - let layout = CairoLayout::dynamic_instance(); - let builtins = BuiltinsInstanceDef::dynamic(); + // dummy cairo layout params + let params = CairoLayoutParams { + rc_units: 32, + log_diluted_units_per_step: 5, + pedersen_ratio: 32, + range_check_ratio: 32, + ecdsa_ratio: 32, + bitwise_ratio: 32, + ec_op_ratio: 32, + keccak_ratio: 32, + mul_mod_ratio: 32, + ..Default::default() // + // cpu_component_step: todo!(), + // memory_units_per_step: todo!(), + // range_check96_ratio_den: todo!(), + // add_mod_ratio_den: todo!(), + // mul_mod_ratio_den: todo!(), + }; + + let layout = CairoLayout::dynamic_instance(params); + assert_eq!(layout.name, LayoutName::dynamic); - assert_eq!(layout.rc_units, 16); - assert_eq!(layout.builtins, builtins); - assert_eq!(layout.public_memory_fraction, 8); + assert_eq!(layout.rc_units, 32); + assert_eq!(layout.public_memory_fraction, 8); // hardcoded assert_eq!( layout.diluted_pool_instance_def, - Some(DilutedPoolInstanceDef::default()) + Some(DilutedPoolInstanceDef { + units_per_step: 32, + ..DilutedPoolInstanceDef::default() // hardcoded + }) + ); + + assert!(layout.builtins.output); + assert_eq!( + layout.builtins.pedersen, + Some(PedersenInstanceDef { ratio: Some(32) }) + ); + assert_eq!( + layout.builtins.range_check, + Some(RangeCheckInstanceDef { ratio: Some(32) }) + ); + assert_eq!( + layout.builtins.ecdsa, + Some(EcdsaInstanceDef { ratio: Some(32) }) + ); + assert_eq!( + layout.builtins.bitwise, + Some(BitwiseInstanceDef { ratio: Some(32) }) + ); + assert_eq!( + layout.builtins.ec_op, + Some(EcOpInstanceDef { ratio: Some(32) }) + ); + assert_eq!( + layout.builtins.keccak, + Some(KeccakInstanceDef { ratio: Some(32) }) + ); + assert_eq!( + layout.builtins.poseidon, + Some(PoseidonInstanceDef { ratio: Some(0) }), ); + assert_eq!( + layout.builtins.range_check96, + Some(RangeCheckInstanceDef { ratio: Some(0) }) + ); + #[cfg(feature = "mod_builtin")] + { + assert_eq!( + layout.builtins.mul_mod, + Some(ModInstanceDef { + ratio: Some(32), + word_bit_len: 96, // hardcoded + batch_size: 1 // hardcoded + }), + ); + assert_eq!( + layout.builtins.add_mod, + Some(ModInstanceDef { + ratio: Some(0), + word_bit_len: 96, // hardcoded + batch_size: 1 // hardcoded + }) + ); + } + #[cfg(not(feature = "mod_builtin"))] + { + assert_eq!(layout.builtins.mul_mod, None,); + assert_eq!(layout.builtins.add_mod, None,); + } + } + + #[test] + fn parse_dynamic_instance() { + let cairo_layout_params_json = "{\n\ + \"rc_units\": 4,\n\ + \"log_diluted_units_per_step\": 4,\n\ + \"cpu_component_step\": 8,\n\ + \"memory_units_per_step\": 8,\n\ + \"uses_pedersen_builtin\": true,\n\ + \"pedersen_ratio\": 256,\n\ + \"uses_range_check_builtin\": true,\n\ + \"range_check_ratio\": 8,\n\ + \"uses_ecdsa_builtin\": true,\n\ + \"ecdsa_ratio\": 2048,\n\ + \"uses_bitwise_builtin\": true,\n\ + \"bitwise_ratio\": 16,\n\ + \"uses_ec_op_builtin\": true,\n\ + \"ec_op_ratio\": 1024,\n\ + \"uses_keccak_builtin\": true,\n\ + \"keccak_ratio\": 2048,\n\ + \"uses_poseidon_builtin\": true,\n\ + \"poseidon_ratio\": 256,\n\ + \"uses_range_check96_builtin\": true,\n\ + \"range_check96_ratio\": 8,\n\ + \"range_check96_ratio_den\": 1,\n\ + \"uses_add_mod_builtin\": true,\n\ + \"add_mod_ratio\": 128,\n\ + \"add_mod_ratio_den\": 1,\n\ + \"uses_mul_mod_builtin\": true,\n\ + \"mul_mod_ratio\": 256,\n\ + \"mul_mod_ratio_den\": 1\n\ + }\n\ + "; + + serde_json::from_str::(cairo_layout_params_json).unwrap(); } } diff --git a/vm/src/types/layout_name.rs b/vm/src/types/layout_name.rs index 8cfd792dc0..4082743f42 100644 --- a/vm/src/types/layout_name.rs +++ b/vm/src/types/layout_name.rs @@ -36,7 +36,7 @@ impl LayoutName { LayoutName::recursive_with_poseidon => "recursive_with_poseidon", LayoutName::all_solidity => "all_solidity", LayoutName::all_cairo => "all_cairo", - LayoutName::dynamic => "all_cairo", + LayoutName::dynamic => "dynamic", } } } diff --git a/vm/src/utils.rs b/vm/src/utils.rs index 070efcaf6d..760c5201ad 100644 --- a/vm/src/utils.rs +++ b/vm/src/utils.rs @@ -252,19 +252,23 @@ pub mod test_utils { crate::vm::runners::cairo_runner::CairoRunner::new( &$program, crate::types::layout_name::LayoutName::all_cairo, + None, false, false, ) .unwrap() }; ($program:expr, $layout:expr) => { - crate::vm::runners::cairo_runner::CairoRunner::new(&$program, $layout, false, false) - .unwrap() + crate::vm::runners::cairo_runner::CairoRunner::new( + &$program, $layout, None, false, false, + ) + .unwrap() }; ($program:expr, $layout:expr, $proof_mode:expr) => { crate::vm::runners::cairo_runner::CairoRunner::new( &$program, $layout, + None, $proof_mode, false, ) @@ -274,6 +278,7 @@ pub mod test_utils { crate::vm::runners::cairo_runner::CairoRunner::new( &$program, $layout, + None, $proof_mode, $trace_enabled, ) diff --git a/vm/src/vm/errors/runner_errors.rs b/vm/src/vm/errors/runner_errors.rs index 561f89997e..98c0787810 100644 --- a/vm/src/vm/errors/runner_errors.rs +++ b/vm/src/vm/errors/runner_errors.rs @@ -132,6 +132,10 @@ pub enum RunnerError { CairoPieProofMode, #[error("{0}: Invalid additional data")] InvalidAdditionalData(BuiltinName), + #[error("dynamic layout params is missing")] + MissingDynamicLayoutParams, + #[error("dynamic layout {0} ratio should be 0 when disabled")] + BadDynamicLayoutBuiltinRatio(BuiltinName), } #[cfg(test)] diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs index b4aa0b7fa6..572ccf1897 100644 --- a/vm/src/vm/runners/builtin_runner/modulo.rs +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -699,7 +699,8 @@ mod tests { let mut hint_processor = BuiltinHintProcessor::new_empty(); let program = Program::from_bytes(program_data, Some("main")).unwrap(); - let mut runner = CairoRunner::new(&program, LayoutName::all_cairo, true, false).unwrap(); + let mut runner = + CairoRunner::new(&program, LayoutName::all_cairo, None, true, false).unwrap(); let end = runner.initialize(false).unwrap(); // Modify add_mod & mul_mod params diff --git a/vm/src/vm/runners/cairo_runner.rs b/vm/src/vm/runners/cairo_runner.rs index ecf47892b0..a283885d57 100644 --- a/vm/src/vm/runners/cairo_runner.rs +++ b/vm/src/vm/runners/cairo_runner.rs @@ -1,13 +1,18 @@ use crate::{ air_private_input::AirPrivateInput, air_public_input::{PublicInput, PublicInputError}, + math_utils::safe_div_usize, stdlib::{ any::Any, collections::{HashMap, HashSet}, ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, prelude::*, }, - types::{builtin_name::BuiltinName, layout::MEMORY_UNITS_PER_STEP, layout_name::LayoutName}, + types::{ + builtin_name::BuiltinName, + layout::{CairoLayoutParams, MEMORY_UNITS_PER_STEP}, + layout_name::LayoutName, + }, vm::{ runners::builtin_runner::SegmentArenaBuiltinRunner, trace::trace_entry::{relocate_trace_register, RelocatedTraceEntry}, @@ -17,7 +22,6 @@ use crate::{ use crate::{ hint_processor::hint_processor_definition::{HintProcessor, HintReference}, - math_utils::safe_div_usize, types::{ errors::{math_errors::MathError, program_errors::ProgramError}, exec_scope::ExecutionScopes, @@ -168,9 +172,12 @@ pub enum RunnerMode { } impl CairoRunner { + /// The `dynamic_layout_params` argument should only be used with dynamic layout. + /// It is ignored otherwise. pub fn new_v2( program: &Program, layout: LayoutName, + dynamic_layout_params: Option, mode: RunnerMode, trace_enabled: bool, ) -> Result { @@ -185,7 +192,12 @@ impl CairoRunner { LayoutName::recursive_with_poseidon => CairoLayout::recursive_with_poseidon(), LayoutName::all_cairo => CairoLayout::all_cairo_instance(), LayoutName::all_solidity => CairoLayout::all_solidity_instance(), - LayoutName::dynamic => CairoLayout::dynamic_instance(), + LayoutName::dynamic => { + let params = + dynamic_layout_params.ok_or(RunnerError::MissingDynamicLayoutParams)?; + + CairoLayout::dynamic_instance(params) + } }; Ok(CairoRunner { program: program.clone(), @@ -215,6 +227,7 @@ impl CairoRunner { pub fn new( program: &Program, layout: LayoutName, + dynamic_layout_params: Option, proof_mode: bool, trace_enabled: bool, ) -> Result { @@ -222,11 +235,18 @@ impl CairoRunner { Self::new_v2( program, layout, + dynamic_layout_params, RunnerMode::ProofModeCanonical, trace_enabled, ) } else { - Self::new_v2(program, layout, RunnerMode::ExecutionMode, trace_enabled) + Self::new_v2( + program, + layout, + dynamic_layout_params, + RunnerMode::ExecutionMode, + trace_enabled, + ) } } @@ -826,6 +846,7 @@ impl CairoRunner { self.vm.current_step, builtin_runner.ratio().unwrap_or(1) as usize, )?; + used_units_by_builtins += used_units * multiplier; } From 7d19956781901fb05c76f7c02fa82bd907f0f9a2 Mon Sep 17 00:00:00 2001 From: Julian Gonzalez Calderon Date: Mon, 30 Sep 2024 18:14:10 -0300 Subject: [PATCH 2/2] Fix modulo builtin constraints (#1841) * Fix Zero segment location. * Fixed has_zero_segment naming * Fix prover input. * Fixed version reading when no version is supplied * Added change to changelog. * fix test_from_serializable() * fix panic_impl error * fix cairo version * Add dummy changelog * Pin wasm-bindgen * Register change in CHANGELOG * Update Cargo.lock * Remove changes from CHANGELOG * Add argument parsing for layout params file * Add dynamic support (no implement) * Add cairo_layout_params_file.example.json * Implement dynamic layout creation * Update CHANGELOG * Add cli dynamic support for cairo 1 * Make wasm compatible * Use public_memory_fraction = 4 vy default * Deserialize bool from int * Add comparison with python-vm (failing) * Rebuild .rs files in makefile * Use 8 as dynamic public_memory_fraction The same value is used in python-vm * Use None ratio for dynamic unused builtins * Add rangecheck96 to private inputs * Make dyn py files depend on params_file * Use cpu_component_step=1 by default * Fix typo in private inputs * Add range check value to air private input test * Fix zero segment location * Use zero builtin instead of None * Add debug scripts * Remove dup makefile recipes * remove outdated test * Enable ensure-no_std on test * Fix tests * Add correct test * Rename tset * Add comment * Add debugging document * Update cairo layout params file * Remove duplicated range check * Remove dup * Remove debugging and scrippts (moveed to another branch) * Add comment * Add tests * Add dynamic test to cairo-vm-cli * Add parse test * Remove compare all dynamic * Add script for comparing with dynamic layouts * Add tests to workflow * Delete logic changes They are going to be moved to another branch * Delete more logic changes * Update rust.yml * Rename compare_outputs_dynamic_layout.sh to compare_outputs_dynamic_layouts.sh * Update test script * Enforce prover constraints in add, sub, and mul * Remove debug prints * Add tests * Update CHANGELOG.md * Fix serialization * Comment failing test * Uncomment test * Fix tests * Remove operation struct and use builtin type instead * Add unit tests to modulo builtin operations * Fix security error message * Test custom serde impl * Upload mod_builtin coverage --------- Co-authored-by: Alon Titelman Co-authored-by: Yuval Goldberg Co-authored-by: Omri Eshhar --- .github/workflows/rust.yml | 30 ++ CHANGELOG.md | 3 + vm/src/air_private_input.rs | 84 ++++++ .../tests/compare_outputs_dynamic_layouts.sh | 3 + vm/src/vm/runners/builtin_runner/modulo.rs | 261 +++++++++++++----- 5 files changed, 306 insertions(+), 75 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6dde21ab95..600b0e52ca 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -600,6 +600,36 @@ jobs: key: codecov-cache-test-no_std-extensive_hints-${{ github.sha }} fail-on-cache-miss: true + - name: Fetch results for tests with stdlib (w/mod_builtin; part. 1) + uses: actions/cache/restore@v3 + with: + path: lcov-test#1-mod_builtin.info + key: codecov-cache-test#1-mod_builtin-${{ github.sha }} + fail-on-cache-miss: true + - name: Fetch results for tests with stdlib (w/mod_builtin; part. 2) + uses: actions/cache/restore@v3 + with: + path: lcov-test#2-mod_builtin.info + key: codecov-cache-test#2-mod_builtin-${{ github.sha }} + fail-on-cache-miss: true + - name: Fetch results for tests with stdlib (w/mod_builtin; part. 3) + uses: actions/cache/restore@v3 + with: + path: lcov-test#3-mod_builtin.info + key: codecov-cache-test#3-mod_builtin-${{ github.sha }} + fail-on-cache-miss: true + - name: Fetch results for tests with stdlib (w/mod_builtin; part. 4) + uses: actions/cache/restore@v3 + with: + path: lcov-test#4-mod_builtin.info + key: codecov-cache-test#4-mod_builtin-${{ github.sha }} + fail-on-cache-miss: true + - name: Fetch results for tests without stdlib (w/mod_builtin) + uses: actions/cache/restore@v3 + with: + path: lcov-no_std-mod_builtin.info + key: codecov-cache-test-no_std-mod_builtin-${{ github.sha }} + fail-on-cache-miss: true - name: Upload coverage to codecov.io uses: codecov/codecov-action@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3317000d50..1f4001b5a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ #### Upcoming Changes +* fix: [#1841](https://github.com/lambdaclass/cairo-vm/pull/1841): + * Fix modulo builtin to comply with prover constraints + * feat(BREAKING): [#1824](https://github.com/lambdaclass/cairo-vm/pull/1824): * Add support for dynamic layout * CLI change(BREAKING): The flag `cairo_layout_params_file` must be specified when using dynamic layout. diff --git a/vm/src/air_private_input.rs b/vm/src/air_private_input.rs index b76575eaff..421c1b3fa0 100644 --- a/vm/src/air_private_input.rs +++ b/vm/src/air_private_input.rs @@ -126,6 +126,8 @@ pub struct ModInputInstance { pub values_ptr: usize, pub offsets_ptr: usize, pub n: usize, + #[serde(deserialize_with = "mod_input_instance_batch_serde::deserialize")] + #[serde(serialize_with = "mod_input_instance_batch_serde::serialize")] pub batch: BTreeMap, } @@ -205,6 +207,88 @@ impl AirPrivateInputSerializable { } } +mod mod_input_instance_batch_serde { + use super::*; + + use serde::{Deserializer, Serializer}; + + pub(crate) fn serialize( + value: &BTreeMap, + s: S, + ) -> Result { + let value = value.iter().map(|v| v.1).collect::>(); + + value.serialize(s) + } + + pub(crate) fn deserialize<'de, D: Deserializer<'de>>( + d: D, + ) -> Result, D::Error> { + let value = Vec::::deserialize(d)?; + + Ok(value.into_iter().enumerate().collect()) + } + + #[cfg(feature = "std")] + #[test] + fn test_serde() { + let input_value = vec![ + ( + 0, + ModInputMemoryVars { + a_offset: 5, + b_offset: 5, + c_offset: 5, + a0: Felt252::from(5u32), + a1: Felt252::from(5u32), + a2: Felt252::from(5u32), + a3: Felt252::from(5u32), + b0: Felt252::from(5u32), + b1: Felt252::from(5u32), + b2: Felt252::from(5u32), + b3: Felt252::from(5u32), + c0: Felt252::from(5u32), + c1: Felt252::from(5u32), + c2: Felt252::from(5u32), + c3: Felt252::from(5u32), + }, + ), + ( + 1, + ModInputMemoryVars { + a_offset: 7, + b_offset: 7, + c_offset: 7, + a0: Felt252::from(7u32), + a1: Felt252::from(7u32), + a2: Felt252::from(7u32), + a3: Felt252::from(7u32), + b0: Felt252::from(7u32), + b1: Felt252::from(7u32), + b2: Felt252::from(7u32), + b3: Felt252::from(7u32), + c0: Felt252::from(7u32), + c1: Felt252::from(7u32), + c2: Felt252::from(7u32), + c3: Felt252::from(7u32), + }, + ), + ] + .into_iter() + .collect::>(); + + let bytes = Vec::new(); + let mut serializer = serde_json::Serializer::new(bytes); + serialize(&input_value, &mut serializer).unwrap(); + let bytes = serializer.into_inner(); + + let mut deserializer = serde_json::Deserializer::from_slice(&bytes); + let output_value = deserialize(&mut deserializer).unwrap(); + + assert_eq!(input_value, output_value); + } +} + #[cfg(test)] mod tests { use crate::types::layout_name::LayoutName; diff --git a/vm/src/tests/compare_outputs_dynamic_layouts.sh b/vm/src/tests/compare_outputs_dynamic_layouts.sh index 7077edaa44..2bc512e3a1 100755 --- a/vm/src/tests/compare_outputs_dynamic_layouts.sh +++ b/vm/src/tests/compare_outputs_dynamic_layouts.sh @@ -82,6 +82,9 @@ CASES=( "cairo_programs/proof_programs/sha256.json;double_all_cairo" "cairo_programs/proof_programs/keccak.json;all_cairo" "cairo_programs/proof_programs/keccak.json;double_all_cairo" + "cairo_programs/mod_builtin_feature/proof/mod_builtin.json;all_cairo" + "cairo_programs/mod_builtin_feature/proof/mod_builtin_failure.json;all_cairo" + "cairo_programs/mod_builtin_feature/proof/apply_poly.json;all_cairo" ) passed_tests=0 diff --git a/vm/src/vm/runners/builtin_runner/modulo.rs b/vm/src/vm/runners/builtin_runner/modulo.rs index 572ccf1897..0f5bb7719b 100644 --- a/vm/src/vm/runners/builtin_runner/modulo.rs +++ b/vm/src/vm/runners/builtin_runner/modulo.rs @@ -21,7 +21,7 @@ use crate::{ }, Felt252, }; -use core::{fmt::Display, ops::Shl}; +use core::ops::Shl; use num_bigint::BigUint; use num_integer::div_ceil; use num_integer::Integer; @@ -47,6 +47,7 @@ pub struct ModBuiltinRunner { // Precomputed powers used for reading and writing values that are represented as n_words words of word_bit_len bits each. shift: BigUint, shift_powers: [BigUint; N_WORDS], + k_bound: BigUint, } #[derive(Debug, Clone)] @@ -55,21 +56,11 @@ pub enum ModBuiltinType { Add, } -#[derive(Debug)] -pub enum Operation { - Mul, - Add, - Sub, - DivMod(BigUint), -} - -impl Display for Operation { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +impl ModBuiltinType { + pub(crate) fn operation_string(&self) -> &'static str { match self { - Operation::Mul => "*".fmt(f), - Operation::Add => "+".fmt(f), - Operation::Sub => "-".fmt(f), - Operation::DivMod(_) => "/".fmt(f), + ModBuiltinType::Mul => "*", + ModBuiltinType::Add => "+", } } } @@ -85,17 +76,28 @@ struct Inputs { impl ModBuiltinRunner { pub(crate) fn new_add_mod(instance_def: &ModInstanceDef, included: bool) -> Self { - Self::new(instance_def.clone(), included, ModBuiltinType::Add) + Self::new( + instance_def.clone(), + included, + ModBuiltinType::Add, + Some(2u32.into()), + ) } pub(crate) fn new_mul_mod(instance_def: &ModInstanceDef, included: bool) -> Self { - Self::new(instance_def.clone(), included, ModBuiltinType::Mul) + Self::new(instance_def.clone(), included, ModBuiltinType::Mul, None) } - fn new(instance_def: ModInstanceDef, included: bool, builtin_type: ModBuiltinType) -> Self { + fn new( + instance_def: ModInstanceDef, + included: bool, + builtin_type: ModBuiltinType, + k_bound: Option, + ) -> Self { let shift = BigUint::one().shl(instance_def.word_bit_len); let shift_powers = core::array::from_fn(|i| shift.pow(i as u32)); let zero_segment_size = core::cmp::max(N_WORDS, instance_def.batch_size * 3); + let int_lim = BigUint::from(2_u32).pow(N_WORDS as u32 * instance_def.word_bit_len); Self { builtin_type, base: 0, @@ -106,6 +108,7 @@ impl ModBuiltinRunner { zero_segment_size, shift, shift_powers, + k_bound: k_bound.unwrap_or(int_lim), } } @@ -435,13 +438,13 @@ impl ModBuiltinRunner { // Fills a value in the values table, if exactly one value is missing. // Returns true on success or if all values are already known. + // + // The builtin type (add or mul) determines which operation to perform fn fill_value( &self, memory: &mut Memory, inputs: &Inputs, index: usize, - op: &Operation, - inv_op: &Operation, ) -> Result { let mut addresses = Vec::new(); let mut values = Vec::new(); @@ -458,19 +461,19 @@ impl ModBuiltinRunner { match (a, b, c) { // Deduce c from a and b and write it to memory. (Some(a), Some(b), None) => { - let value = apply_op(a, b, op)?.mod_floor(&inputs.p); + let value = self.apply_operation(a, b, &inputs.p)?; self.write_n_words_value(memory, addresses[2], value)?; Ok(true) } // Deduce b from a and c and write it to memory. (Some(a), None, Some(c)) => { - let value = apply_op(c, a, inv_op)?.mod_floor(&inputs.p); + let value = self.deduce_operand(a, c, &inputs.p)?; self.write_n_words_value(memory, addresses[1], value)?; Ok(true) } // Deduce a from b and c and write it to memory. (None, Some(b), Some(c)) => { - let value = apply_op(c, b, inv_op)?.mod_floor(&inputs.p); + let value = self.deduce_operand(b, c, &inputs.p)?; self.write_n_words_value(memory, addresses[0], value)?; Ok(true) } @@ -539,44 +542,33 @@ impl ModBuiltinRunner { Default::default() }; - // Get one of the builtin runners - the rest of this function doesn't depend on batch_size. - let mod_runner = if let Some((_, add_mod, _)) = add_mod { - add_mod - } else { - mul_mod.unwrap().1 - }; // Fill the values table. let mut add_mod_index = 0; let mut mul_mod_index = 0; - // Create operation here to avoid cloning p in the loop - let div_operation = Operation::DivMod(mul_mod_inputs.p.clone()); + while add_mod_index < add_mod_n || mul_mod_index < mul_mod_n { - if add_mod_index < add_mod_n - && mod_runner.fill_value( - memory, - &add_mod_inputs, - add_mod_index, - &Operation::Add, - &Operation::Sub, - )? - { - add_mod_index += 1; - } else if mul_mod_index < mul_mod_n - && mod_runner.fill_value( - memory, - &mul_mod_inputs, - mul_mod_index, - &Operation::Mul, - &div_operation, - )? - { - mul_mod_index += 1; - } else { - return Err(RunnerError::FillMemoryCoudNotFillTable( - add_mod_index, - mul_mod_index, - )); + if add_mod_index < add_mod_n { + if let Some((_, add_mod_runner, _)) = add_mod { + if add_mod_runner.fill_value(memory, &add_mod_inputs, add_mod_index)? { + add_mod_index += 1; + continue; + } + } } + + if mul_mod_index < mul_mod_n { + if let Some((_, mul_mod_runner, _)) = mul_mod { + if mul_mod_runner.fill_value(memory, &mul_mod_inputs, mul_mod_index)? { + mul_mod_index += 1; + } + continue; + } + } + + return Err(RunnerError::FillMemoryCoudNotFillTable( + add_mod_index, + mul_mod_index, + )); } Ok(()) } @@ -625,14 +617,11 @@ impl ModBuiltinRunner { inputs.offsets_ptr, index_in_batch, )?; - let op = match self.builtin_type { - ModBuiltinType::Add => Operation::Add, - ModBuiltinType::Mul => Operation::Mul, - }; - let a_op_b = apply_op(&a, &b, &op)?.mod_floor(&inputs.p); - if a_op_b != c.mod_floor(&inputs.p) { + let a_op_b = self.apply_operation(&a, &b, &inputs.p)?; + if a_op_b.mod_floor(&inputs.p) != c.mod_floor(&inputs.p) { // Build error string let p = inputs.p; + let op = self.builtin_type.operation_string(); let error_string = format!("Expected a {op} b == c (mod p). Got: instance={instance}, batch={index_in_batch}, p={p}, a={a}, b={b}, c={c}."); return Err(RunnerError::ModBuiltinSecurityCheck(Box::new(( self.name(), @@ -667,23 +656,145 @@ impl ModBuiltinRunner { self.shift_powers = core::array::from_fn(|i| self.shift.pow(i as u32)); self.zero_segment_size = core::cmp::max(N_WORDS, batch_size * 3); } -} -fn apply_op(lhs: &BigUint, rhs: &BigUint, op: &Operation) -> Result { - Ok(match op { - Operation::Mul => lhs * rhs, - Operation::Add => lhs + rhs, - Operation::Sub => lhs - rhs, - Operation::DivMod(ref p) => div_mod_unsigned(lhs, rhs, p)?, - }) + // Calculates the result of `lhs OP rhs` + // + // The builtin type (add or mul) determines the OP + pub(crate) fn apply_operation( + &self, + lhs: &BigUint, + rhs: &BigUint, + prime: &BigUint, + ) -> Result { + let full_value = match self.builtin_type { + ModBuiltinType::Mul => lhs * rhs, + ModBuiltinType::Add => lhs + rhs, + }; + + let value = if full_value < &self.k_bound * prime { + full_value.mod_floor(prime) + } else { + full_value - (&self.k_bound - 1u32) * prime + }; + + Ok(value) + } + + // Given `known OP unknown = result (mod p)`, it deduces `unknown` + // + // The builtin type (add or mul) determines the OP + pub(crate) fn deduce_operand( + &self, + known: &BigUint, + result: &BigUint, + prime: &BigUint, + ) -> Result { + let value = match self.builtin_type { + ModBuiltinType::Add => { + if known <= result { + result - known + } else { + result + prime - known + } + } + ModBuiltinType::Mul => div_mod_unsigned(result, known, prime)?, + }; + Ok(value) + } } #[cfg(test)] mod tests { + use super::*; + + #[test] + fn apply_operation_add() { + let builtin = ModBuiltinRunner::new_add_mod(&ModInstanceDef::new(Some(8), 8, 8), true); + + assert_eq!( + builtin + .apply_operation( + &BigUint::from(2u32), + &BigUint::from(3u32), + &BigUint::from(7u32) + ) + .unwrap(), + BigUint::from(5u32) + ); + + assert_eq!( + builtin + .apply_operation( + &BigUint::from(5u32), + &BigUint::from(5u32), + &BigUint::from(5u32) + ) + .unwrap(), + BigUint::from(5u32) + ); + } + + #[test] + fn apply_operation_mul() { + let builtin = ModBuiltinRunner::new_mul_mod(&ModInstanceDef::new(Some(8), 8, 8), true); + + assert_eq!( + builtin + .apply_operation( + &BigUint::from(2u32), + &BigUint::from(3u32), + &BigUint::from(7u32) + ) + .unwrap(), + BigUint::from(6u32) + ); + } + + #[test] + fn deduce_operand_add() { + let builtin = ModBuiltinRunner::new_add_mod(&ModInstanceDef::new(Some(8), 8, 8), true); + + assert_eq!( + builtin + .deduce_operand( + &BigUint::from(2u32), + &BigUint::from(5u32), + &BigUint::from(7u32) + ) + .unwrap(), + BigUint::from(3u32) + ); + assert_eq!( + builtin + .deduce_operand( + &BigUint::from(5u32), + &BigUint::from(2u32), + &BigUint::from(7u32) + ) + .unwrap(), + BigUint::from(4u32) + ); + } + + #[test] + fn deduce_operand_mul() { + let builtin = ModBuiltinRunner::new_mul_mod(&ModInstanceDef::new(Some(8), 8, 8), true); + + assert_eq!( + builtin + .deduce_operand( + &BigUint::from(2u32), + &BigUint::from(1u32), + &BigUint::from(7u32) + ) + .unwrap(), + BigUint::from(4u32) + ); + } + #[test] #[cfg(feature = "mod_builtin")] fn test_air_private_input_all_cairo() { - use super::*; use crate::{ air_private_input::{ModInput, ModInputInstance, ModInputMemoryVars, PrivateInput}, hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, @@ -735,8 +846,8 @@ mod tests { a2: Felt252::ZERO, a3: Felt252::ZERO, b_offset: 12, - b0: Felt252::ZERO, - b1: Felt252::ZERO, + b0: Felt252::ONE, + b1: Felt252::ONE, b2: Felt252::ZERO, b3: Felt252::ZERO, c_offset: 4, @@ -798,8 +909,8 @@ mod tests { 0, ModInputMemoryVars { a_offset: 12, - a0: Felt252::ZERO, - a1: Felt252::ZERO, + a0: Felt252::ONE, + a1: Felt252::ONE, a2: Felt252::ZERO, a3: Felt252::ZERO, b_offset: 8,