From b657576ae5d21b5ef86dbddc014db86c5172d0bb Mon Sep 17 00:00:00 2001 From: fmoletta <99273364+fmoletta@users.noreply.github.com> Date: Wed, 29 Nov 2023 18:01:25 +0200 Subject: [PATCH] Add a test that runs a cairo 0 contract from a cairo program (#1494) * Usa a Hashmap for HintRanges * Add execute_and_mutate_hints * comments * Fix hashmap ranges-related stuff * Add execute_hint_extensive * Process * Add error handling * Add comments & remove previous impl * Use Box to reduce output size * Fix tests * Fix tests * Fix bug * Revert hook changes + fmt * Uncomment code * revert hook-related vchanges' ' ' * Add comments * Remove old comments from proxy era * Revert "Use Box to reduce output size" This reverts commit fc76a36c9129829b774af37c5da928bac16b73c6. * Add back RelocataedTrace struct * Move relocated trace back to runner * Remove trace relocation from vm * Fix test * Update tests * clippy * clippy * Remove dbg print + add changelog entry * Add test check * Add test check * Simplify output type of execute_hint_extensive` ` * Add missing wasm import * Only use instruction cache when running from program segment * Add test * Update test * Add unit tests * Fmt * Add changelog entry * Fix pr num * Fix tests after merge * Initial progress * Complete skeleton * Add bytecode * Add hints * Use a simpler hint for test * fmt * Add trace check * Add comment explaining file purpose * Simplify code comments * Simplify code comments * Make relocate_trace public * List breaking changes * List pr purpose in changelog * Add disclaimers for execute_hint & execute_hint_extensive * Start changelog * List functionality changes in changelog * fix names * Add no-std import * no-std * no-std --------- Co-authored-by: Pedro Fontana --- CHANGELOG.md | 13 +- .../starknet_os_deprecated_cc.cairo | 32 ++ vm/src/tests/mod.rs | 2 + ...un_deprecated_contract_class_simplified.rs | 419 ++++++++++++++++++ 4 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 cairo_programs/noretrocompat/starknet_os_deprecated_cc.cairo create mode 100644 vm/src/tests/run_deprecated_contract_class_simplified.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b873eb5471..ddde24eec7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ * feat: Allow running instructions from pcs outside the program segement [#1493](https://github.com/lambdaclass/cairo-vm/pull/14923) +* BREAKING: Partially Revert `Optimize trace relocation #906` [#1492](https://github.com/lambdaclass/cairo-vm/pull/1492) + + * Remove methods `VirtualMachine::get_relocated_trace`& `VirtualMachine::relocate_trace`. + * Add `relocated_trace` field & `relocate_trace` method to `CairoRunner`. + * Swap `TraceEntry` for `RelocatedTraceEntry` type in `write_encoded_trace` & `PublicInput::new` signatures. + * Now takes into account the program counter's segment index when building the execution trace instead of assuming it to be 0. * feat: Add HintProcessor::execute_hint_extensive + refactor hint_ranges [#1491](https://github.com/lambdaclass/cairo-vm/pull/1491) * Add trait method `HintProcessorLogic::execute_hint_extensive`: @@ -14,13 +20,6 @@ * `pub fn step_hint(&mut self, hint_executor: &mut dyn HintProcessor, exec_scopes: &mut ExecutionScopes, hint_datas: &mut Vec>, constants: &HashMap) -> Result<(), VirtualMachineError>` -> `pub fn step_hint(&mut self, hint_processor: &mut dyn HintProcessor, exec_scopes: &mut ExecutionScopes, hint_datas: &mut Vec>, hint_ranges: &mut HashMap, constants: &HashMap) -> Result<(), VirtualMachineError>` * `pub fn step(&mut self, hint_executor: &mut dyn HintProcessor, exec_scopes: &mut ExecutionScopes, hint_data: &[Box], constants: &HashMap) -> Result<(), VirtualMachineError>` -> `pub fn step(&mut self, hint_processor: &mut dyn HintProcessor, exec_scopes: &mut ExecutionScopes, hint_datas: &mut Vec>, hint_ranges: &mut HashMap, constants: &HashMap) -> Result<(), VirtualMachineError>` -* BREAKING: Partially Revert `Optimize trace relocation #906` [#1492](https://github.com/lambdaclass/cairo-vm/pull/1492) - - * Remove methods `VirtualMachine::get_relocated_trace`& `VirtualMachine::relocate_trace`. - * Add `relocated_trace` field & `relocate_trace` method to `CairoRunner`. - * Swap `TraceEntry` for `RelocatedTraceEntry` type in `write_encoded_trace` & `PublicInput::new` signatures. - * Now takes into account the program counter's segment index when building the execution trace instead of assuming it to be 0. - * feat: add debugging capabilities behind `print` feature flag. [#1476](https://github.com/lambdaclass/cairo-vm/pull/1476) * feat: add `cairo_run_program` function that takes a `Program` as an arg. [#1496](https://github.com/lambdaclass/cairo-vm/pull/1496) diff --git a/cairo_programs/noretrocompat/starknet_os_deprecated_cc.cairo b/cairo_programs/noretrocompat/starknet_os_deprecated_cc.cairo new file mode 100644 index 0000000000..b5258044e1 --- /dev/null +++ b/cairo_programs/noretrocompat/starknet_os_deprecated_cc.cairo @@ -0,0 +1,32 @@ +from starkware.starknet.core.os.contract_class.deprecated_compiled_class import ( + DeprecatedCompiledClass, + DeprecatedCompiledClassFact +) +from starkware.cairo.common.alloc import alloc + +func main() { + alloc_locals; + local compiled_class_facts: DeprecatedCompiledClassFact*; + %{ ids.compiled_class_facts = segments.add() %} + let compiled_class_fact = compiled_class_facts; + let compiled_class = compiled_class_fact.compiled_class; + %{ + from starkware.starknet.services.api.contract_class.contract_class import DeprecatedCompiledClass + from starkware.starknet.core.os.contract_class.deprecated_class_hash import ( + get_deprecated_contract_class_struct, + ) + with open("test_contract.json", "r") as f: + compiled_class = DeprecatedCompiledClass.loads(f.read()) + + cairo_contract = get_deprecated_contract_class_struct( + identifiers=ids._context.identifiers, contract_class=compiled_class) + ids.compiled_class = segments.gen_arg(cairo_contract) + %} + local compiled_class: DeprecatedCompiledClass* = compiled_class; + local destination: felt* = compiled_class.bytecode_ptr; + %{ + vm_load_program(compiled_class.program, ids.compiled_class.bytecode_ptr) + %} + call abs destination; + return (); +} diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index 3e408ba6af..cd0b18546c 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -32,6 +32,8 @@ use wasm_bindgen_test::*; use alloc::{string::String, vec::Vec}; mod bitwise_test; +#[cfg(test)] +mod run_deprecated_contract_class_simplified; #[cfg(feature = "cairo-1-hints")] mod cairo_1_run_from_entrypoint_tests; diff --git a/vm/src/tests/run_deprecated_contract_class_simplified.rs b/vm/src/tests/run_deprecated_contract_class_simplified.rs new file mode 100644 index 0000000000..7b46ae1191 --- /dev/null +++ b/vm/src/tests/run_deprecated_contract_class_simplified.rs @@ -0,0 +1,419 @@ +/* This file contains a test that runs the program: cairo_programs/starknet_os_deprecated_cc.cairo + For testsing purposes, the contract ran by this program is hardcoded, with values taken from compiling: + + %lang starknet + + @view + func get_number() -> (number: felt) { + let number = 14; + %{print("hello world")%} + return (number=number); + } + + The purpose of this test is to check the functionality of the HintProcessor::execute_hint_extensive functionality + And to show a very simplified example on how it can be used to achieve the `vm_load_data` functionality used by starknet os programs +*/ +use crate::stdlib::{collections::HashMap, prelude::*}; + +use felt::{felt_str, Felt252}; +use num_traits::{Num, One, Zero}; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; + +use crate::{ + cairo_run::{cairo_run, CairoRunConfig}, + hint_processor::{ + builtin_hint_processor::{ + builtin_hint_processor_definition::{BuiltinHintProcessor, HintProcessorData}, + hint_utils::{get_ptr_from_var_name, insert_value_from_var_name}, + }, + hint_processor_definition::{ + HintExtension, HintProcessor, HintProcessorLogic, HintReference, + }, + }, + serde::deserialize_program::ApTracking, + vm::{ + errors::hint_errors::HintError, + runners::cairo_runner::{ResourceTracker, RunResources}, + trace::trace_entry::RelocatedTraceEntry, + vm_core::VirtualMachine, + }, +}; + +struct SimplifiedOsHintProcessor { + builtin_hint_processor: BuiltinHintProcessor, + run_resources: RunResources, +} + +impl ResourceTracker for SimplifiedOsHintProcessor { + fn consumed(&self) -> bool { + self.run_resources.consumed() + } + + fn consume_step(&mut self) { + self.run_resources.consume_step() + } + + fn get_n_steps(&self) -> Option { + self.run_resources.get_n_steps() + } + + fn run_resources(&self) -> &RunResources { + &self.run_resources + } +} + +impl Default for SimplifiedOsHintProcessor { + fn default() -> Self { + Self { + builtin_hint_processor: BuiltinHintProcessor::new_empty(), + run_resources: Default::default(), + } + } +} + +impl HintProcessorLogic for SimplifiedOsHintProcessor { + fn execute_hint( + &mut self, + _vm: &mut crate::vm::vm_core::VirtualMachine, + _exec_scopes: &mut crate::types::exec_scope::ExecutionScopes, + //Data structure that can be downcasted to the structure generated by compile_hint + _hint_data: &Box, + //Constant values extracted from the program specification. + _constants: &HashMap, + ) -> Result<(), crate::vm::errors::hint_errors::HintError> { + // Empty impl as we are using `execute_hint_extensive` instead for this case + Ok(()) + } + + fn execute_hint_extensive( + &mut self, + vm: &mut crate::vm::vm_core::VirtualMachine, + exec_scopes: &mut crate::types::exec_scope::ExecutionScopes, + //Data structure that can be downcasted to the structure generated by compile_hint + hint_data: &Box, + //Constant values extracted from the program specification. + constants: &HashMap, + ) -> Result< + crate::hint_processor::hint_processor_definition::HintExtension, + crate::vm::errors::hint_errors::HintError, + > { + // First attempt to execute with builtin hint processor + match self.builtin_hint_processor.execute_hint_extensive( + vm, + exec_scopes, + hint_data, + constants, + ) { + Err(HintError::UnknownHint(_)) => {} + res => return res, + } + // Execute os-specific hints + let hint_data = hint_data + .downcast_ref::() + .ok_or(HintError::WrongHintData)?; + match &*hint_data.code { + ALLOC_FACTS => alloc_facts(vm, &hint_data.ids_data, &hint_data.ap_tracking), + COMPILE_CLASS => compile_class(vm, &hint_data.ids_data, &hint_data.ap_tracking), + VM_LOAD_PROGRAM => { + vm_load_program(self, vm, &hint_data.ids_data, &hint_data.ap_tracking) + } + HELLO_WORLD => hello_world(), + code => Err(HintError::UnknownHint(code.to_string().into_boxed_str())), + } + } +} + +// Hints & Hint impls +const ALLOC_FACTS: &str = "ids.compiled_class_facts = segments.add()"; +pub fn alloc_facts( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result { + insert_value_from_var_name( + "compiled_class_facts", + vm.add_memory_segment(), + vm, + ids_data, + ap_tracking, + )?; + Ok(HintExtension::default()) +} +const COMPILE_CLASS: &str = "from starkware.starknet.services.api.contract_class.contract_class import DeprecatedCompiledClass\nfrom starkware.starknet.core.os.contract_class.deprecated_class_hash import (\n get_deprecated_contract_class_struct,\n)\nwith open(\"test_contract.json\", \"r\") as f:\n compiled_class = DeprecatedCompiledClass.loads(f.read())\n \ncairo_contract = get_deprecated_contract_class_struct(\n identifiers=ids._context.identifiers, contract_class=compiled_class)\nids.compiled_class = segments.gen_arg(cairo_contract)"; +pub fn compile_class( + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result { + // We wil use a hardcoded contract to avoid importing starknet-related code for this test + // What this hint does is load the "test_contract.json" compiled contract into the `ids.compiled_class` variable of type *DeprecatedCompiledClass + // First we need to allocate the struct + let compiled_class_ptr = vm.add_memory_segment(); + insert_value_from_var_name( + "compiled_class", + compiled_class_ptr, + vm, + ids_data, + ap_tracking, + )?; + // Now we can fill each struct field with our hardcoded values + let mut ptr = compiled_class_ptr; + // For this test's purpose we will be using the following cairo 0 contract: + /* + %lang starknet + + @view + func get_number() -> (number: felt) { + let number = 14; + %{print("hello world")%} + return (number=number); + } + */ + + // struct DeprecatedCompiledClass { + // compiled_class_version: felt, + vm.insert_value(ptr, Felt252::default())?; // Not relevant + ptr.offset += 1; + // n_external_functions: felt, + vm.insert_value(ptr, Felt252::one())?; // Only one external entrypoint + ptr.offset += 1; + // external_functions: DeprecatedContractEntryPoint*, + let mut entrypoints_ptr = vm.add_memory_segment(); + // struct DeprecatedContractEntryPoint { + // selector: felt, + let selector = Felt252::from_str_radix( + "23180acc053dfb2dbc82a0da33515906d37498b42f34ee4ed308f9d5fb51b6c", + 16, + ) + .unwrap(); + vm.insert_value(entrypoints_ptr, selector)?; + entrypoints_ptr.offset += 1; + // offset: felt, + let offset = Felt252::from(12); + vm.insert_value(entrypoints_ptr, offset)?; + // } + vm.insert_value(ptr, entrypoints_ptr)?; // Only one external entrypoint + ptr.offset += 1; + // n_l1_handlers: felt, + vm.insert_value(ptr, Felt252::zero())?; + ptr.offset += 1; + // l1_handlers: DeprecatedContractEntryPoint*, + let l1_handler_entrypoints_ptr = vm.add_memory_segment(); + vm.insert_value(ptr, l1_handler_entrypoints_ptr)?; + ptr.offset += 1; + // n_constructors: felt, + vm.insert_value(ptr, Felt252::zero())?; + ptr.offset += 1; + // constructors: DeprecatedContractEntryPoint*, + let constructor_entrypoints_ptr = vm.add_memory_segment(); + vm.insert_value(ptr, constructor_entrypoints_ptr)?; + ptr.offset += 1; + // n_builtins: felt, + vm.insert_value(ptr, Felt252::one())?; + ptr.offset += 1; + // builtin_list: felt*, + let builtins_ptr = vm.add_memory_segment(); + // One builtin: range_check = 138277649577220228665140075 + vm.insert_value(builtins_ptr, felt_str!("138277649577220228665140075"))?; + vm.insert_value(ptr, builtins_ptr)?; + ptr.offset += 1; + // hinted_class_hash: felt, + vm.insert_value(ptr, Felt252::zero())?; + ptr.offset += 1; + // bytecode_length: felt, + let byte_code = vec![ + Felt252::from_str_radix("480680017fff8000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("e", 16).unwrap().into(), + Felt252::from_str_radix("208b7fff7fff7ffe", 16) + .unwrap() + .into(), + Felt252::from_str_radix("40780017fff7fff", 16) + .unwrap() + .into(), + Felt252::from_str_radix("1", 16).unwrap().into(), + Felt252::from_str_radix("4003800080007ffc", 16) + .unwrap() + .into(), + Felt252::from_str_radix("4826800180008000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("1", 16).unwrap().into(), + Felt252::from_str_radix("480a7ffd7fff8000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("4828800080007ffe", 16) + .unwrap() + .into(), + Felt252::from_str_radix("480a80007fff8000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("208b7fff7fff7ffe", 16) + .unwrap() + .into(), + Felt252::from_str_radix("402b7ffd7ffc7ffd", 16) + .unwrap() + .into(), + Felt252::from_str_radix("1104800180018000", 16) + .unwrap() + .into(), + Felt252::from_str_radix( + "800000000000010fffffffffffffffffffffffffffffffffffffffffffffff4", + 16, + ) + .unwrap() + .into(), + Felt252::from_str_radix("480280017ffb8000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("1104800180018000", 16) + .unwrap() + .into(), + Felt252::from_str_radix( + "800000000000010fffffffffffffffffffffffffffffffffffffffffffffff4", + 16, + ) + .unwrap() + .into(), + Felt252::from_str_radix("480280007ffb8000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("48127ffc7fff8000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("48127ffc7fff8000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("48127ffc7fff8000", 16) + .unwrap() + .into(), + Felt252::from_str_radix("208b7fff7fff7ffe", 16) + .unwrap() + .into(), + ]; + vm.insert_value(ptr, Felt252::from(byte_code.len()))?; + ptr.offset += 1; + // bytecode_ptr: felt*, + let byte_code_ptr = vm.add_memory_segment(); + vm.load_data(byte_code_ptr, &byte_code)?; + vm.insert_value(ptr, byte_code_ptr)?; + + Ok(HintExtension::default()) +} + +const VM_LOAD_PROGRAM: &str = + "vm_load_program(compiled_class.program, ids.compiled_class.bytecode_ptr)"; +pub fn vm_load_program( + hint_processor: &dyn HintProcessor, + vm: &mut VirtualMachine, + ids_data: &HashMap, + ap_tracking: &ApTracking, +) -> Result { + // We will be hardcoding the hint-related values instead of taking them from the compiled contract + // The contract has the following hint: + /* + "0": [ + { + "accessible_scopes": [ + "__main__", + "__main__", + "__main__.get_number" + ], + "code": "print(\"hello world\")", + "flow_tracking_data": { + "ap_tracking": { + "group": 0, + "offset": 0 + }, + "reference_ids": {} + } + } + ], + */ + let hint_code = "print(\"hello world\")"; + let hint_ap_tracking_data = ApTracking::default(); + let reference_ids = HashMap::default(); + let references = vec![]; + // Compile the hint + let compiled_hint = hint_processor.compile_hint( + hint_code, + &hint_ap_tracking_data, + &reference_ids, + &references, + )?; + // Create the hint extension + // As the hint from the compiled constract has offset 0, the hint pc will be equal to the loaded contract's program base: + // This ptr can be found at ids.compiled_class.bytecode_ptr + let compiled_class = get_ptr_from_var_name("compiled_class", vm, ids_data, ap_tracking)?; + // ids.compiled_class.bytecode_ptr = [ids.compiled_class + 11] + let byte_code_ptr = vm.get_relocatable((compiled_class + 11)?)?; + let hint_extension = HashMap::from([(byte_code_ptr, vec![compiled_hint])]); + Ok(hint_extension) +} + +const HELLO_WORLD: &str = "print(\"hello world\")"; +pub fn hello_world() -> Result { + #[cfg(feature = "std")] + println!("hello world"); + Ok(HintExtension::default()) +} + +#[test] +#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] +fn run_deprecated_cc() { + let mut hint_processor = SimplifiedOsHintProcessor::default(); + let program_content = + include_bytes!("../../../cairo_programs/noretrocompat/starknet_os_deprecated_cc.json"); + let (runner, _) = cairo_run( + program_content, + &CairoRunConfig { + trace_enabled: true, + ..Default::default() + }, + &mut hint_processor, + ) + .unwrap(); + // Check trace against cairo-lang vm run + assert_eq!( + runner.relocated_trace, + Some(vec![ + RelocatedTraceEntry { + pc: 4, + ap: 12, + fp: 12 + }, + RelocatedTraceEntry { + pc: 6, + ap: 15, + fp: 12 + }, + RelocatedTraceEntry { + pc: 7, + ap: 15, + fp: 12 + }, + RelocatedTraceEntry { + pc: 8, + ap: 15, + fp: 12 + }, + RelocatedTraceEntry { + pc: 35, + ap: 17, + fp: 17 + }, + RelocatedTraceEntry { + pc: 37, + ap: 18, + fp: 17 + }, + RelocatedTraceEntry { + pc: 9, + ap: 18, + fp: 12 + } + ]) + ); +}