cairo-vm 2.0.1

Blazing fast Cairo interpreter
Documentation
#![cfg(feature = "extensive_hints")]
/* 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 crate::Felt252;
use num_traits::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<usize> {
        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<dyn core::any::Any>,
        //Constant values extracted from the program specification.
        _constants: &HashMap<String, Felt252>,
    ) -> 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<dyn core::any::Any>,
        //Constant values extracted from the program specification.
        constants: &HashMap<String, Felt252>,
    ) -> 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::<HintProcessorData>()
            .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<String, HintReference>,
    ap_tracking: &ApTracking,
) -> Result<HintExtension, HintError> {
    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<String, HintReference>,
    ap_tracking: &ApTracking,
) -> Result<HintExtension, HintError> {
    // 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_hex("0x23180acc053dfb2dbc82a0da33515906d37498b42f34ee4ed308f9d5fb51b6c")
            .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,
        Felt252::from_dec_str("138277649577220228665140075").unwrap(),
    )?;
    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_hex("0x480680017fff8000").unwrap().into(),
        Felt252::from_hex("0xe").unwrap().into(),
        Felt252::from_hex("0x208b7fff7fff7ffe").unwrap().into(),
        Felt252::from_hex("0x40780017fff7fff").unwrap().into(),
        Felt252::from_hex("0x1").unwrap().into(),
        Felt252::from_hex("0x4003800080007ffc").unwrap().into(),
        Felt252::from_hex("0x4826800180008000").unwrap().into(),
        Felt252::from_hex("0x1").unwrap().into(),
        Felt252::from_hex("0x480a7ffd7fff8000").unwrap().into(),
        Felt252::from_hex("0x4828800080007ffe").unwrap().into(),
        Felt252::from_hex("0x480a80007fff8000").unwrap().into(),
        Felt252::from_hex("0x208b7fff7fff7ffe").unwrap().into(),
        Felt252::from_hex("0x402b7ffd7ffc7ffd").unwrap().into(),
        Felt252::from_hex("0x1104800180018000").unwrap().into(),
        Felt252::from_hex("0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffff4")
            .unwrap()
            .into(),
        Felt252::from_hex("0x480280017ffb8000").unwrap().into(),
        Felt252::from_hex("0x1104800180018000").unwrap().into(),
        Felt252::from_hex("0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffff4")
            .unwrap()
            .into(),
        Felt252::from_hex("0x480280007ffb8000").unwrap().into(),
        Felt252::from_hex("0x48127ffc7fff8000").unwrap().into(),
        Felt252::from_hex("0x48127ffc7fff8000").unwrap().into(),
        Felt252::from_hex("0x48127ffc7fff8000").unwrap().into(),
        Felt252::from_hex("0x208b7fff7fff7ffe").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<String, HintReference>,
    ap_tracking: &ApTracking,
) -> Result<HintExtension, HintError> {
    // 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<HintExtension, HintError> {
    #[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
            }
        ])
    );
}