diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index f3443d5f68..cf4a54a96d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-20.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: rustfmt, clippy - uses: actions/checkout@v3 diff --git a/.github/workflows/cairo_1_programs.yml b/.github/workflows/cairo_1_programs.yml index d8a4c1f3f4..c2872a03b4 100644 --- a/.github/workflows/cairo_1_programs.yml +++ b/.github/workflows/cairo_1_programs.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up Cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/.github/workflows/fresh_run.yml b/.github/workflows/fresh_run.yml index 4660f1f8a2..de76902a24 100644 --- a/.github/workflows/fresh_run.yml +++ b/.github/workflows/fresh_run.yml @@ -35,7 +35,7 @@ jobs: uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Install Pyenv uses: "gabrielfalcao/pyenv-action@v13" diff --git a/.github/workflows/hint_accountant.yml b/.github/workflows/hint_accountant.yml index 4a3a1e023f..3bd9ca4ff0 100644 --- a/.github/workflows/hint_accountant.yml +++ b/.github/workflows/hint_accountant.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up Cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/.github/workflows/hyperfine.yml b/.github/workflows/hyperfine.yml index 87bf26f3e3..6fb098b6a6 100644 --- a/.github/workflows/hyperfine.yml +++ b/.github/workflows/hyperfine.yml @@ -74,7 +74,7 @@ jobs: - name: Install Rust if: ${{ steps.cache.outputs.cache-hit != 'true' }} - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Checkout if: ${{ steps.cache.outputs.cache-hit != 'true' }} diff --git a/.github/workflows/iai_main.yml b/.github/workflows/iai_main.yml index e1217085ba..f61eef7e3e 100644 --- a/.github/workflows/iai_main.yml +++ b/.github/workflows/iai_main.yml @@ -11,7 +11,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Python3 Build diff --git a/.github/workflows/iai_pr.yml b/.github/workflows/iai_pr.yml index db446adeb9..afb9e3a97c 100644 --- a/.github/workflows/iai_pr.yml +++ b/.github/workflows/iai_pr.yml @@ -23,7 +23,7 @@ jobs: - name: Install Rust if: ${{ steps.cache-iai-results.outputs.cache-hit != 'true' }} - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache if: ${{ steps.cache-iai-results.outputs.cache-hit != 'true' }} uses: Swatinem/rust-cache@v2 @@ -51,7 +51,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Python3 Build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f8f5c9b81a..a06efe8806 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,15 +13,7 @@ jobs: - name: Checkout sources uses: actions/checkout@v2 - name: Install stable toolchain - uses: dtolnay/rust-toolchain@1.70.0 - - name: Publish crate cairo-felt - env: - CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: cargo publish --token ${CRATES_TOKEN} --all-features --manifest-path ./felt/Cargo.toml - # FIXME: there should be a better way to make sure the index in crates.io is updated before publishing - # cairo-vm but right now the step throws timeout and fails. - - name: wait for index in crates.io - run: sleep 300 + uses: dtolnay/rust-toolchain@1.74.1 - name: Publish crate cairo-vm env: CRATES_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 32a20befbd..8b34694243 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: rustfmt, clippy - name: Set up cargo cache @@ -159,7 +159,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: wasm32-unknown-unknown @@ -220,7 +220,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 with: components: llvm-tools-preview - name: Set up cargo cache @@ -281,7 +281,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Rust - uses: dtolnay/rust-toolchain@1.70.0 + uses: dtolnay/rust-toolchain@1.74.1 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 - name: Checkout diff --git a/README.md b/README.md index 52c2dd4043..db368a35db 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ It's Turing-complete and it was created by [Starkware](https://starkware.co/) as These are needed in order to compile and use the project. -- [Rust 1.70.0 or newer](https://www.rust-lang.org/tools/install) +- [Rust 1.74.1 or newer](https://www.rust-lang.org/tools/install) - Cargo #### Optional diff --git a/cairo1-run/src/cairo_run.rs b/cairo1-run/src/cairo_run.rs new file mode 100644 index 0000000000..5dbda6c672 --- /dev/null +++ b/cairo1-run/src/cairo_run.rs @@ -0,0 +1,766 @@ +use cairo_lang_casm::{casm, casm_extend, hints::Hint, instructions::Instruction}; +use cairo_lang_sierra::{ + extensions::{ + bitwise::BitwiseType, + core::{CoreLibfunc, CoreType}, + ec::EcOpType, + gas::{CostTokenType, GasBuiltinType}, + pedersen::PedersenType, + poseidon::PoseidonType, + range_check::RangeCheckType, + segment_arena::SegmentArenaType, + starknet::syscalls::SystemType, + ConcreteType, NamedType, + }, + ids::ConcreteTypeId, + program::{Function, Program as SierraProgram}, + program_registry::ProgramRegistry, +}; +use cairo_lang_sierra_ap_change::calc_ap_changes; +use cairo_lang_sierra_gas::gas_info::GasInfo; +use cairo_lang_sierra_to_casm::{ + compiler::CairoProgram, + metadata::{calc_metadata, Metadata, MetadataComputationConfig, MetadataError}, +}; +use cairo_lang_sierra_type_size::get_type_size_map; +use cairo_lang_utils::unordered_hash_map::UnorderedHashMap; +use cairo_vm::{ + hint_processor::cairo_1_hint_processor::hint_processor::Cairo1HintProcessor, + serde::deserialize_program::{ + ApTracking, BuiltinName, FlowTrackingData, HintParams, ReferenceManager, + }, + types::{program::Program, relocatable::MaybeRelocatable}, + vm::{ + errors::{runner_errors::RunnerError, vm_errors::VirtualMachineError}, + runners::{ + builtin_runner::{ + BITWISE_BUILTIN_NAME, EC_OP_BUILTIN_NAME, HASH_BUILTIN_NAME, OUTPUT_BUILTIN_NAME, + POSEIDON_BUILTIN_NAME, RANGE_CHECK_BUILTIN_NAME, SIGNATURE_BUILTIN_NAME, + }, + cairo_runner::{CairoRunner, RunResources, RunnerMode}, + }, + vm_core::VirtualMachine, + }, + Felt252, +}; +use itertools::chain; +use std::collections::HashMap; + +use crate::{Error, FuncArg}; + +#[derive(Debug)] +pub struct Cairo1RunConfig<'a> { + pub args: &'a [FuncArg], + pub trace_enabled: bool, + pub relocate_mem: bool, + pub layout: &'a str, + pub proof_mode: bool, + // Should be true if either air_public_input or cairo_pie_output are needed + // Sets builtins stop_ptr by calling `final_stack` on each builtin + pub finalize_builtins: bool, +} + +impl Default for Cairo1RunConfig<'_> { + fn default() -> Self { + Self { + args: Default::default(), + trace_enabled: false, + relocate_mem: false, + layout: "plain", + proof_mode: false, + finalize_builtins: false, + } + } +} + +// Runs a Cairo 1 program +// Returns the runner & VM after execution + the return values +pub fn cairo_run_program( + sierra_program: &SierraProgram, + cairo_run_config: Cairo1RunConfig, +) -> Result<(CairoRunner, VirtualMachine, Vec), Error> { + let metadata = create_metadata(sierra_program, Some(Default::default()))?; + let sierra_program_registry = ProgramRegistry::::new(sierra_program)?; + let type_sizes = + get_type_size_map(sierra_program, &sierra_program_registry).unwrap_or_default(); + let casm_program = + cairo_lang_sierra_to_casm::compiler::compile(sierra_program, &metadata, true)?; + + let main_func = find_function(sierra_program, "::main")?; + + let initial_gas = 9999999999999_usize; + + // Modified entry code to be compatible with custom cairo1 Proof Mode. + // This adds code that's needed for dictionaries, adjusts ap for builtin pointers, adds initial gas for the gas builtin if needed, and sets up other necessary code for cairo1 + let (entry_code, builtins) = create_entry_code( + &sierra_program_registry, + &casm_program, + &type_sizes, + main_func, + initial_gas, + cairo_run_config.proof_mode, + cairo_run_config.args, + )?; + + // Fetch return type data + let return_type_id = main_func + .signature + .ret_types + .last() + .ok_or(Error::NoRetTypesInSignature)?; + let return_type_size = type_sizes + .get(return_type_id) + .cloned() + .ok_or_else(|| Error::NoTypeSizeForId(return_type_id.clone()))?; + + // This footer is used by lib funcs + let libfunc_footer = create_code_footer(); + + // Header used to initiate the infinite loop after executing the program + // Also appends return values to output segment + let proof_mode_header = if cairo_run_config.proof_mode { + create_proof_mode_header(builtins.len() as i16, return_type_size) + } else { + casm! {}.instructions + }; + + // This is the program we are actually running/proving + // With (embedded proof mode), cairo1 header and the libfunc footer + let instructions = chain!( + proof_mode_header.iter(), + entry_code.iter(), + casm_program.instructions.iter(), + libfunc_footer.iter(), + ); + + let (processor_hints, program_hints) = build_hints_vec(instructions.clone()); + + let mut hint_processor = Cairo1HintProcessor::new(&processor_hints, RunResources::default()); + + let data: Vec = instructions + .flat_map(|inst| inst.assemble().encode()) + .map(|x| Felt252::from(&x)) + .map(MaybeRelocatable::from) + .collect(); + + let data_len = data.len(); + + let program = if cairo_run_config.proof_mode { + Program::new_for_proof( + builtins, + data, + 0, + // Proof mode is on top + // jmp rel 0 is on PC == 2 + 2, + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )? + } else { + Program::new( + builtins, + data, + Some(0), + program_hints, + ReferenceManager { + references: Vec::new(), + }, + HashMap::new(), + vec![], + None, + )? + }; + + let runner_mode = if cairo_run_config.proof_mode { + RunnerMode::ProofModeCairo1 + } else { + RunnerMode::ExecutionMode + }; + + let mut runner = CairoRunner::new_v2(&program, cairo_run_config.layout, runner_mode)?; + let mut vm = VirtualMachine::new(cairo_run_config.trace_enabled); + let end = runner.initialize(&mut vm, cairo_run_config.proof_mode)?; + + additional_initialization(&mut vm, data_len)?; + + // Run it until the end / infinite loop in proof_mode + runner.run_until_pc(end, &mut vm, &mut hint_processor)?; + if cairo_run_config.proof_mode { + // As we will be inserting the return values into the output segment after running the main program (right before the infinite loop) the computed size for the output builtin will be 0 + // We need to manually set the segment size for the output builtin's segment so memory hole counting doesn't fail due to having a higher accessed address count than the segment's size + vm.segments + .segment_sizes + .insert(2, return_type_size as usize); + } + runner.end_run(false, false, &mut vm, &mut hint_processor)?; + + // Fetch return values + let return_values = fetch_return_values(return_type_size, return_type_id, &vm)?; + + // Set stop pointers for builtins so we can obtain the air public input + if cairo_run_config.finalize_builtins { + finalize_builtins( + cairo_run_config.proof_mode, + &main_func.signature.ret_types, + &type_sizes, + &mut vm, + )?; + + // Build execution public memory + if cairo_run_config.proof_mode { + // As the output builtin is not used by the program we need to compute it's stop ptr manually + vm.set_output_stop_ptr_offset(return_type_size as usize); + + runner.finalize_segments(&mut vm)?; + } + } + + runner.relocate(&mut vm, true)?; + + Ok((runner, vm, return_values)) +} + +fn additional_initialization(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Error> { + // Create the builtin cost segment + let builtin_cost_segment = vm.add_memory_segment(); + for token_type in CostTokenType::iter_precost() { + vm.insert_value( + (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)) + .map_err(VirtualMachineError::Math)?, + Felt252::default(), + )? + } + // Put a pointer to the builtin cost segment at the end of the program (after the + // additional `ret` statement). + vm.insert_value( + (vm.get_pc() + data_len).map_err(VirtualMachineError::Math)?, + builtin_cost_segment, + )?; + + Ok(()) +} + +#[allow(clippy::type_complexity)] +fn build_hints_vec<'b>( + instructions: impl Iterator, +) -> (Vec<(usize, Vec)>, HashMap>) { + let mut hints: Vec<(usize, Vec)> = Vec::new(); + let mut program_hints: HashMap> = HashMap::new(); + + let mut hint_offset = 0; + + for instruction in instructions { + if !instruction.hints.is_empty() { + hints.push((hint_offset, instruction.hints.clone())); + program_hints.insert( + hint_offset, + vec![HintParams { + code: hint_offset.to_string(), + accessible_scopes: Vec::new(), + flow_tracking_data: FlowTrackingData { + ap_tracking: ApTracking::default(), + reference_ids: HashMap::new(), + }, + }], + ); + } + hint_offset += instruction.body.op_size(); + } + (hints, program_hints) +} + +/// Finds first function ending with `name_suffix`. +fn find_function<'a>( + sierra_program: &'a SierraProgram, + name_suffix: &'a str, +) -> Result<&'a Function, RunnerError> { + sierra_program + .funcs + .iter() + .find(|f| { + if let Some(name) = &f.id.debug_name { + name.ends_with(name_suffix) + } else { + false + } + }) + .ok_or_else(|| RunnerError::MissingMain) +} + +/// Creates a list of instructions that will be appended to the program's bytecode. +fn create_code_footer() -> Vec { + casm! { + // Add a `ret` instruction used in libfuncs that retrieve the current value of the `fp` + // and `pc` registers. + ret; + } + .instructions +} + +// Create proof_mode specific instructions +// Including the "canonical" proof mode instructions (the ones added by the compiler in cairo 0) +// wich call the firt program instruction and then initiate an infinite loop. +// And also appending the return values to the output builtin's memory segment +fn create_proof_mode_header(builtin_count: i16, return_type_size: i16) -> Vec { + // As the output builtin is not used by cairo 1 (we forced it for this purpose), it's segment is always empty + // so we can start writing values directly from it's base, which is located relative to the fp before the other builtin's bases + let output_fp_offset: i16 = -(builtin_count + 2); // The 2 here represents the return_fp & end segments + + // The pc offset where the original program should start + // Without this header it should start at 0, but we add 2 for each call and jump instruction (as both of them use immediate values) + // and also 1 for each instruction added to copy each return value into the output segment + let program_start_offset: i16 = 4 + return_type_size; + + let mut ctx = casm! {}; + casm_extend! {ctx, + call rel program_start_offset; // Begin program execution by calling the first instruction in the original program + }; + // Append each return value to the output segment + for (i, j) in (1..return_type_size + 1).rev().enumerate() { + casm_extend! {ctx, + // [ap -j] is where each return value is located in memory + // [[fp + output_fp_offet] + 0] is the base of the output segment + [ap - j] = [[fp + output_fp_offset] + i as i16]; + }; + } + casm_extend! {ctx, + jmp rel 0; // Infinite loop + }; + ctx.instructions +} + +/// Returns the instructions to add to the beginning of the code to successfully call the main +/// function, as well as the builtins required to execute the program. +fn create_entry_code( + sierra_program_registry: &ProgramRegistry, + casm_program: &CairoProgram, + type_sizes: &UnorderedHashMap, + func: &Function, + initial_gas: usize, + proof_mode: bool, + args: &[FuncArg], +) -> Result<(Vec, Vec), Error> { + let mut ctx = casm! {}; + // The builtins in the formatting expected by the runner. + let (builtins, builtin_offset) = get_function_builtins(func, proof_mode); + + // Load all vecs to memory. + // Load all array args content to memory. + let mut array_args_data = vec![]; + let mut ap_offset: i16 = 0; + for arg in args { + let FuncArg::Array(values) = arg else { + continue; + }; + array_args_data.push(ap_offset); + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + for (i, v) in values.iter().enumerate() { + let arr_at = (i + 1) as i16; + casm_extend! {ctx, + [ap + 0] = (v.to_bigint()); + [ap + 0] = [[ap - arr_at] + (i as i16)], ap++; + }; + } + ap_offset += (1 + values.len()) as i16; + } + let mut array_args_data_iter = array_args_data.iter(); + let after_arrays_data_offset = ap_offset; + let mut arg_iter = args.iter().enumerate(); + let mut param_index = 0; + let mut expected_arguments_size = 0; + if func.signature.param_types.iter().any(|ty| { + get_info(sierra_program_registry, ty) + .map(|x| x.long_id.generic_id == SegmentArenaType::ID) + .unwrap_or_default() + }) { + casm_extend! {ctx, + // SegmentArena segment. + %{ memory[ap + 0] = segments.add() %} + // Infos segment. + %{ memory[ap + 1] = segments.add() %} + ap += 2; + [ap + 0] = 0, ap++; + // Write Infos segment, n_constructed (0), and n_destructed (0) to the segment. + [ap - 2] = [[ap - 3]]; + [ap - 1] = [[ap - 3] + 1]; + [ap - 1] = [[ap - 3] + 2]; + } + ap_offset += 3; + } + for ty in func.signature.param_types.iter() { + let info = get_info(sierra_program_registry, ty) + .ok_or_else(|| Error::NoInfoForType(ty.clone()))?; + let generic_ty = &info.long_id.generic_id; + if let Some(offset) = builtin_offset.get(generic_ty) { + let mut offset = *offset; + if proof_mode { + // Everything is off by 2 due to the proof mode header + offset += 2; + } + casm_extend! {ctx, + [ap + 0] = [fp - offset], ap++; + } + ap_offset += 1; + } else if generic_ty == &SystemType::ID { + casm_extend! {ctx, + %{ memory[ap + 0] = segments.add() %} + ap += 1; + } + ap_offset += 1; + } else if generic_ty == &GasBuiltinType::ID { + casm_extend! {ctx, + [ap + 0] = initial_gas, ap++; + } + ap_offset += 1; + } else if generic_ty == &SegmentArenaType::ID { + let offset = -ap_offset + after_arrays_data_offset; + casm_extend! {ctx, + [ap + 0] = [ap + offset] + 3, ap++; + } + ap_offset += 1; + } else { + let ty_size = type_sizes[ty]; + let param_ap_offset_end = ap_offset + ty_size; + expected_arguments_size += ty_size; + while ap_offset < param_ap_offset_end { + let Some((arg_index, arg)) = arg_iter.next() else { + break; + }; + match arg { + FuncArg::Single(value) => { + casm_extend! {ctx, + [ap + 0] = (value.to_bigint()), ap++; + } + ap_offset += 1; + } + FuncArg::Array(values) => { + let offset = -ap_offset + array_args_data_iter.next().unwrap(); + casm_extend! {ctx, + [ap + 0] = [ap + (offset)], ap++; + [ap + 0] = [ap - 1] + (values.len()), ap++; + } + ap_offset += 2; + if ap_offset > param_ap_offset_end { + return Err(Error::ArgumentUnaligned { + param_index, + arg_index, + }); + } + } + } + } + param_index += 1; + }; + } + let actual_args_size = args + .iter() + .map(|arg| match arg { + FuncArg::Single(_) => 1, + FuncArg::Array(_) => 2, + }) + .sum::(); + if expected_arguments_size != actual_args_size { + return Err(Error::ArgumentsSizeMismatch { + expected: expected_arguments_size, + actual: actual_args_size, + }); + } + + let before_final_call = ctx.current_code_offset; + let final_call_size = 3; + let offset = final_call_size + + casm_program.debug_info.sierra_statement_info[func.entry_point.0].code_offset; + + casm_extend! {ctx, + call rel offset; + ret; + } + assert_eq!(before_final_call + final_call_size, ctx.current_code_offset); + + Ok((ctx.instructions, builtins)) +} + +fn get_info<'a>( + sierra_program_registry: &'a ProgramRegistry, + ty: &'a cairo_lang_sierra::ids::ConcreteTypeId, +) -> Option<&'a cairo_lang_sierra::extensions::types::TypeInfo> { + sierra_program_registry + .get_type(ty) + .ok() + .map(|ctc| ctc.info()) +} + +/// Creates the metadata required for a Sierra program lowering to casm. +fn create_metadata( + sierra_program: &cairo_lang_sierra::program::Program, + metadata_config: Option, +) -> Result { + if let Some(metadata_config) = metadata_config { + calc_metadata(sierra_program, metadata_config).map_err(|err| match err { + MetadataError::ApChangeError(_) => VirtualMachineError::Unexpected, + MetadataError::CostError(_) => VirtualMachineError::Unexpected, + }) + } else { + Ok(Metadata { + ap_change_info: calc_ap_changes(sierra_program, |_, _| 0) + .map_err(|_| VirtualMachineError::Unexpected)?, + gas_info: GasInfo { + variable_values: Default::default(), + function_costs: Default::default(), + }, + }) + } +} + +/// Type representing the Output builtin. +#[derive(Default)] +pub struct OutputType {} +impl cairo_lang_sierra::extensions::NoGenericArgsGenericType for OutputType { + const ID: cairo_lang_sierra::ids::GenericTypeId = + cairo_lang_sierra::ids::GenericTypeId::new_inline("Output"); + const STORABLE: bool = true; + const DUPLICATABLE: bool = false; + const DROPPABLE: bool = false; + const ZERO_SIZED: bool = false; +} + +fn get_function_builtins( + func: &Function, + proof_mode: bool, +) -> ( + Vec, + HashMap, +) { + let entry_params = &func.signature.param_types; + let mut builtins = Vec::new(); + let mut builtin_offset: HashMap = HashMap::new(); + let mut current_offset = 3; + // Fetch builtins from the entry_params in the standard order + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Poseidon".into())) + { + builtins.push(BuiltinName::poseidon); + builtin_offset.insert(PoseidonType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("EcOp".into())) + { + builtins.push(BuiltinName::ec_op); + builtin_offset.insert(EcOpType::ID, current_offset); + current_offset += 1 + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Bitwise".into())) + { + builtins.push(BuiltinName::bitwise); + builtin_offset.insert(BitwiseType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("RangeCheck".into())) + { + builtins.push(BuiltinName::range_check); + builtin_offset.insert(RangeCheckType::ID, current_offset); + current_offset += 1; + } + if entry_params + .iter() + .any(|ti| ti.debug_name == Some("Pedersen".into())) + { + builtins.push(BuiltinName::pedersen); + builtin_offset.insert(PedersenType::ID, current_offset); + current_offset += 1; + } + // Force an output builtin so that we can write the program output into it's segment + if proof_mode { + builtins.push(BuiltinName::output); + builtin_offset.insert(OutputType::ID, current_offset); + } + builtins.reverse(); + (builtins, builtin_offset) +} + +fn fetch_return_values( + return_type_size: i16, + return_type_id: &ConcreteTypeId, + vm: &VirtualMachine, +) -> Result, Error> { + let mut return_values = vm.get_return_values(return_type_size as usize)?; + // Check if this result is a Panic result + if return_type_id + .debug_name + .as_ref() + .ok_or_else(|| Error::TypeIdNoDebugName(return_type_id.clone()))? + .starts_with("core::panics::PanicResult::") + { + // Check the failure flag (aka first return value) + if return_values.first() != Some(&MaybeRelocatable::from(0)) { + // In case of failure, extract the error from the return values (aka last two values) + let panic_data_end = return_values + .last() + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data_start = return_values + .get(return_values.len() - 2) + .ok_or(Error::FailedToExtractReturnValues)? + .get_relocatable() + .ok_or(Error::FailedToExtractReturnValues)?; + let panic_data = vm.get_integer_range( + panic_data_start, + (panic_data_end - panic_data_start).map_err(VirtualMachineError::Math)?, + )?; + return Err(Error::RunPanic( + panic_data.iter().map(|c| *c.as_ref()).collect(), + )); + } else { + if return_values.len() < 3 { + return Err(Error::FailedToExtractReturnValues); + } + return_values = return_values[2..].to_vec() + } + } + Ok(return_values) +} + +// Calculates builtins' final_stack setting each stop_ptr +// Calling this function is a must if either air_public_input or cairo_pie are needed +fn finalize_builtins( + proof_mode: bool, + main_ret_types: &[ConcreteTypeId], + type_sizes: &UnorderedHashMap, + vm: &mut VirtualMachine, +) -> Result<(), Error> { + // Set stop pointers for builtins so we can obtain the air public input + // Cairo 1 programs have other return values aside from the used builtin's final pointers, so we need to hand-pick them + let ret_types_sizes = main_ret_types + .iter() + .map(|id| type_sizes.get(id).cloned().unwrap_or_default()); + let ret_types_and_sizes = main_ret_types.iter().zip(ret_types_sizes.clone()); + + let full_ret_types_size: i16 = ret_types_sizes.sum(); + let mut stack_pointer = (vm.get_ap() - (full_ret_types_size as usize).saturating_sub(1)) + .map_err(VirtualMachineError::Math)?; + + // Calculate the stack_ptr for each return builtin in the return values + let mut builtin_name_to_stack_pointer = HashMap::new(); + for (id, size) in ret_types_and_sizes { + if let Some(ref name) = id.debug_name { + let builtin_name = match &*name.to_string() { + "RangeCheck" => RANGE_CHECK_BUILTIN_NAME, + "Poseidon" => POSEIDON_BUILTIN_NAME, + "EcOp" => EC_OP_BUILTIN_NAME, + "Bitwise" => BITWISE_BUILTIN_NAME, + "Pedersen" => HASH_BUILTIN_NAME, + "Output" => OUTPUT_BUILTIN_NAME, + "Ecdsa" => SIGNATURE_BUILTIN_NAME, + _ => { + stack_pointer.offset += size as usize; + continue; + } + }; + builtin_name_to_stack_pointer.insert(builtin_name, stack_pointer); + } + stack_pointer.offset += size as usize; + } + + // Set stop pointer for each builtin + vm.builtins_final_stack_from_stack_pointer_dict(&builtin_name_to_stack_pointer, proof_mode)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + use cairo_lang_compiler::{compile_cairo_project_at_path, CompilerConfig}; + use cairo_vm::types::relocatable::Relocatable; + use rstest::rstest; + + fn compile_to_sierra(filename: &str) -> SierraProgram { + let compiler_config = CompilerConfig { + replace_ids: true, + ..CompilerConfig::default() + }; + + compile_cairo_project_at_path(Path::new(filename), compiler_config).unwrap() + } + + fn main_hash_panic_result(sierra_program: &SierraProgram) -> bool { + let main_func = find_function(sierra_program, "::main").unwrap(); + main_func + .signature + .ret_types + .last() + .and_then(|rt| { + rt.debug_name + .as_ref() + .map(|n| n.as_ref().starts_with("core::panics::PanicResult::")) + }) + .unwrap_or_default() + } + + #[rstest] + #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] + #[case("../cairo_programs/cairo-1-programs/array_get.cairo")] + #[case("../cairo_programs/cairo-1-programs/dictionaries.cairo")] + #[case("../cairo_programs/cairo-1-programs/enum_flow.cairo")] + #[case("../cairo_programs/cairo-1-programs/enum_match.cairo")] + #[case("../cairo_programs/cairo-1-programs/factorial.cairo")] + #[case("../cairo_programs/cairo-1-programs/fibonacci.cairo")] + #[case("../cairo_programs/cairo-1-programs/hello.cairo")] + #[case("../cairo_programs/cairo-1-programs/pedersen_example.cairo")] + #[case("../cairo_programs/cairo-1-programs/poseidon.cairo")] + #[case("../cairo_programs/cairo-1-programs/print.cairo")] + #[case("../cairo_programs/cairo-1-programs/array_append.cairo")] + #[case("../cairo_programs/cairo-1-programs/recursion.cairo")] + #[case("../cairo_programs/cairo-1-programs/sample.cairo")] + #[case("../cairo_programs/cairo-1-programs/simple_struct.cairo")] + #[case("../cairo_programs/cairo-1-programs/simple.cairo")] + #[case("../cairo_programs/cairo-1-programs/struct_span_return.cairo")] + fn check_append_ret_values_to_output_segment(#[case] filename: &str) { + // Compile to sierra + let sierra_program = compile_to_sierra(filename); + // Set proof_mode + let cairo_run_config = Cairo1RunConfig { + proof_mode: true, + layout: "all_cairo", + ..Default::default() + }; + // Run program + let (_, vm, return_values) = cairo_run_program(&sierra_program, cairo_run_config).unwrap(); + // When the return type is a PanicResult, we remove the panic wrapper when returning the ret values + // And handle the panics returning an error, so we need to add it here + let return_values = if main_hash_panic_result(&sierra_program) { + let mut rv = vec![Felt252::ZERO.into(), Felt252::ZERO.into()]; + rv.extend_from_slice(&return_values); + rv + } else { + return_values + }; + // Check that the output segment contains the return values + // The output builtin will always be the first builtin, so we know it's segment is 2 + let output_builtin_segment = vm + .get_continuous_range((2, 0).into(), return_values.len()) + .unwrap(); + assert_eq!(output_builtin_segment, return_values, "{}", filename); + // Just for consistency, we will check that there are no values in the output segment after the return values + assert!(vm + .get_maybe(&Relocatable::from((2_isize, return_values.len()))) + .is_none()); + } +} diff --git a/rust-toolchain b/rust-toolchain index 2d24a1e077..4da0ec7b8e 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,4 +1,4 @@ [toolchain] -channel = "1.70.0" +channel = "1.74.1" components = ["rustfmt", "clippy"] profile = "minimal" diff --git a/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs b/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs index d9d7a56600..9ea30fb6e3 100644 --- a/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs +++ b/vm/src/hint_processor/builtin_hint_processor/find_element_hint.rs @@ -162,7 +162,7 @@ mod tests { vm.segments.add(); } - let addresses = vec![ + let addresses = [ Relocatable::from((1, 0)), Relocatable::from((1, 1)), Relocatable::from((1, 2)), diff --git a/vm/src/serde/deserialize_utils.rs b/vm/src/serde/deserialize_utils.rs index 5a90ec863d..53bcce78ca 100644 --- a/vm/src/serde/deserialize_utils.rs +++ b/vm/src/serde/deserialize_utils.rs @@ -230,7 +230,7 @@ fn take_until_unbalanced( .ok_or_else(|| Err::Error(Error::from_error_kind(i, ErrorKind::TakeUntil)))? .chars(); match it.next().unwrap_or_default() { - c if c == '\\' => { + '\\' => { // Skip the escape char `\`. index += '\\'.len_utf8(); // Skip also the following char. diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index cecacbad8d..f66a602fb0 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -68,7 +68,7 @@ pub(self) fn run_program_with_error(data: &[u8], error: &str) { run_program(data, Some("all_cairo"), None, Some(error), None) } -pub(self) fn run_program( +fn run_program( data: &[u8], layout: Option<&str>, trace: Option<&[(usize, usize, usize)]>, @@ -109,7 +109,7 @@ pub(self) fn run_program( #[cfg(feature = "cairo-1-hints")] // Runs a contract entrypoint with given arguments and checks its return values // Doesn't use a syscall_handler -pub(self) fn run_cairo_1_entrypoint( +fn run_cairo_1_entrypoint( program_content: &[u8], entrypoint_offset: usize, args: &[MaybeRelocatable], @@ -215,7 +215,7 @@ pub(self) fn run_cairo_1_entrypoint( #[cfg(feature = "cairo-1-hints")] /// Equals to fn run_cairo_1_entrypoint /// But with run_resources as an input -pub(self) fn run_cairo_1_entrypoint_with_run_resources( +fn run_cairo_1_entrypoint_with_run_resources( contract_class: CasmContractClass, entrypoint_offset: usize, hint_processor: &mut Cairo1HintProcessor, diff --git a/vm/src/vm/runners/cairo_pie.rs b/vm/src/vm/runners/cairo_pie.rs index 60a0fa147e..2862a0fdbb 100644 --- a/vm/src/vm/runners/cairo_pie.rs +++ b/vm/src/vm/runners/cairo_pie.rs @@ -147,6 +147,7 @@ mod serde_impl { seq_serializer.end() } + #[allow(clippy::format_collect)] pub fn serialize_memory( values: &[((usize, usize), MaybeRelocatable)], serializer: S,