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; }