diff --git a/.github/workflows/compare_memory_devnet.yml b/.github/workflows/compare_memory_devnet.yml new file mode 100644 index 00000000..baa968fc --- /dev/null +++ b/.github/workflows/compare_memory_devnet.yml @@ -0,0 +1,36 @@ +name: compare_devnet_memory + +on: + push: + branches: [ main ] + pull_request: + branches: [ '*' ] + +env: + CARGO_TERM_COLOR: always +jobs: + compare_memory_devnet: + runs-on: ubuntu-20.04 + steps: + - name: Install Rust 1.61.0 + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.61.0 + override: true + components: rustfmt, clippy + - uses: actions/checkout@v3 + - name: Python3 Build + uses: actions/setup-python@v4 + with: + python-version: '3.9' + - name: Checkout + uses: actions/checkout@v3 + - name: Checkout starknet-devnet + uses: actions/checkout@v3 + with: + repository: Shard-Labs/starknet-devnet + path: starknet-devnet + - name: Install test dependencies + run: pip install ecdsa fastecdsa sympy cairo-lang==0.9.1 maturin + - name: Run devnet tests & compare memory outputs + run: make compare_memory_devnet_ci diff --git a/Makefile b/Makefile index ff86e69a..b0b67c2c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: deps deps-macos deps-default-version build run check test clippy clean run-python-test full-test run-comparer-tracer compare_trace_memory compare_trace compare_memory +.PHONY: deps deps-macos deps-default-version build run check test clippy clean run-python-test full-test run-comparer-tracer compare_trace_memory compare_trace compare_memory compare_memory_devnet compare_memory_devnet_ci TEST_DIR=cairo_programs TEST_FILES:=$(wildcard $(TEST_DIR)/*.cairo) @@ -78,6 +78,8 @@ clean: rm -f $(BAD_TEST_DIR)/*.memory rm -f $(BAD_TEST_DIR)/*.trace rm -rf cairo-rs-py-env + rm -rf starknet-devnet + rm -rf scripts/memory_comparator/cairo* run-python-test: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) PYENV_VERSION=pypy3.7-7.3.9 . cairo-rs-py-env/bin/activate && \ @@ -104,4 +106,50 @@ compare_trace: $(CAIRO_RS_TRACE) $(CAIRO_TRACE) compare_memory: $(CAIRO_RS_MEM) $(CAIRO_MEM) cd tests; ./compare_vm_state.sh memory - +compare_memory_devnet: +# Set up the virtual envs + scripts/memory_comparator/build_envs.sh +# Clone the starknet-devnet from github + git clone git@github.com:Shard-Labs/starknet-devnet.git +# Set up the starknet-devnet in each env +# cairo-rs-py + . scripts/memory_comparator/cairo-rs-py/bin/activate && \ + pip install starknet-devnet && \ + cd starknet-devnet; scripts/install_dev_tools.sh +# cairo-lang + . scripts/memory_comparator/cairo-lang/bin/activate && \ + pip install starknet-devnet && \ + cd starknet-devnet; scripts/install_dev_tools.sh +# Create the folder where we will store the memory outputs + cd starknet-devnet; mkdir memory_files +# Compile test files + . scripts/memory_comparator/cairo-lang/bin/activate && \ + cd starknet-devnet; scripts/compile_contracts.sh +# Patch both envs + patch --directory scripts/memory_comparator/cairo-rs-py/lib/python3.9/site-packages/ --strip 2 < scripts/memory_comparator/output-memory-cairo-rs-py.patch + patch --directory scripts/memory_comparator/cairo-lang/lib/python3.9/site-packages/ --strip 2 < scripts/memory_comparator/output-memory-cairo-lang.patch +# Run each test one by one in each env and run the memory comparator + . ./scripts/memory_comparator/run_tests_compare_memory.sh + +compare_memory_devnet_ci: +# Set up the virtual envs + scripts/memory_comparator/build_envs.sh +# Set up the starknet-devnet in each env +# cairo-rs-py + . scripts/memory_comparator/cairo-rs-py/bin/activate && \ + pip install starknet-devnet && \ + cd starknet-devnet; scripts/install_dev_tools.sh +# cairo-lang + . scripts/memory_comparator/cairo-lang/bin/activate && \ + pip install starknet-devnet && \ + cd starknet-devnet; scripts/install_dev_tools.sh +# Create the folder where we will store the memory outputs + cd starknet-devnet; mkdir memory_files +# Compile test files + . scripts/memory_comparator/cairo-lang/bin/activate && \ + cd starknet-devnet; scripts/compile_contracts.sh +# Patch both envs + patch --directory scripts/memory_comparator/cairo-rs-py/lib/python3.9/site-packages/ --strip 2 < scripts/memory_comparator/output-memory-cairo-rs-py.patch + patch --directory scripts/memory_comparator/cairo-lang/lib/python3.9/site-packages/ --strip 2 < scripts/memory_comparator/output-memory-cairo-lang.patch +# Run each test one by one in each env and run the memory comparator + . ./scripts/memory_comparator/run_tests_compare_memory.sh diff --git a/scripts/memory_comparator/build_envs.sh b/scripts/memory_comparator/build_envs.sh new file mode 100755 index 00000000..81638062 --- /dev/null +++ b/scripts/memory_comparator/build_envs.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +set -e + +# This is not reaaaaally a robust way to find it, but you need to be actively +# trying to break it for this to fail :) +SCRIPT_DIR="scripts/memory_comparator" + +python3.9 -m venv --upgrade-deps ${SCRIPT_DIR}/cairo-lang ${SCRIPT_DIR}/cairo-rs-py +${SCRIPT_DIR}/cairo-lang/bin/pip install cairo-lang==0.10.1 +${SCRIPT_DIR}/cairo-rs-py/bin/pip install maturin==0.14.1 cairo-lang==0.10.1 +${SCRIPT_DIR}/cairo-rs-py/bin/maturin build --manifest-path Cargo.toml --release --strip --interpreter 3.9 --no-default-features --features extension +${SCRIPT_DIR}/cairo-rs-py/bin/pip install target/wheels/cairo_rs_py-*.whl + +${SCRIPT_DIR}/cairo-rs-py/bin/cairo-run --version +${SCRIPT_DIR}/cairo-rs-py/bin/starknet --version diff --git a/scripts/memory_comparator/memory_comparator.py b/scripts/memory_comparator/memory_comparator.py new file mode 100755 index 00000000..537f5735 --- /dev/null +++ b/scripts/memory_comparator/memory_comparator.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +import sys + +def main(): + filename1 = sys.argv[1] + filename2 = sys.argv[2] + cairo_mem = {} + cairo_rs_mem = {} + name = filename2.split("/")[-1] + with open(filename1, 'rb') as f: + cairo_raw = f.read() + assert len(cairo_raw) % 40 == 0, f'{filename1}: malformed memory file from Cairo VM' + chunks = len(cairo_raw) // 40 + for i in range(0, chunks): + chunk = cairo_raw[i*40:(i+1)*40] + k, v = int.from_bytes(chunk[:8], 'little'), int.from_bytes(chunk[8:], 'little') + assert k not in cairo_mem, f'{filename1}: address {k} has two values' + cairo_mem[k] = v + assert len(cairo_mem) * 40 == len(cairo_raw), f'{filename1}: {len(cairo_mem) * 40} != {len(cairo_raw)}' + + with open(filename2, 'rb') as f: + cairo_rs_raw = f.read() + assert len(cairo_rs_raw) % 40 == 0, f'{filename2}: malformed memory file from cairo-rs' + chunks = len(cairo_rs_raw) // 40 + for i in range(0, chunks): + chunk = cairo_rs_raw[i*40:(i+1)*40] + k, v = int.from_bytes(chunk[:8], 'little'), int.from_bytes(chunk[8:], 'little') + assert k not in cairo_rs_mem, f'{filename2}: address {k} has two values' + cairo_rs_mem[k] = v + assert len(cairo_rs_mem) * 40 == len(cairo_rs_raw), f'{filename2}: {len(cairo_rs_mem) * 40} != {len(cairo_rs_raw)}' + + assert len(cairo_mem) == len(cairo_rs_mem), f'{filename2}: len(cairo_mem)={len(cairo_mem)} len(cairo_mem)={len(cairo_rs_mem)}' + if cairo_mem != cairo_rs_mem: + print(f'Mismatch between {filename1} (Cairo) and {filename2} (cairo_rs)') + print('keys in Cairo but not cairo-rs:') + for k in cairo_mem: + if k in cairo_rs_mem: + continue + print(f'{k}:{v}') + print('keys in cairo_rs but not Cairo:') + for k in cairo_rs_mem: + if k in cairo_mem: + continue + print(f'{k}:{v}') + print('mismatched values (Cairo <-> cairo_rs)):') + for k in cairo_rs_mem: + if k not in cairo_mem: + continue + if cairo_rs_mem[k] == cairo_mem[k]: + continue + print(f'{k}:({cairo_mem[k]} <-> {cairo_rs_mem[k]})') + exit(1) + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/scripts/memory_comparator/output-memory-cairo-lang.patch b/scripts/memory_comparator/output-memory-cairo-lang.patch new file mode 100644 index 00000000..95d26179 --- /dev/null +++ b/scripts/memory_comparator/output-memory-cairo-lang.patch @@ -0,0 +1,51 @@ +diff --git a/src/starkware/starknet/business_logic/execution/execute_entry_point.py b/src/starkware/starknet/business_logic/execution/execute_entry_point.py +index 62d4290..f4a0d8b 100644 +--- a/src/starkware/starknet/business_logic/execution/execute_entry_point.py ++++ b/src/starkware/starknet/business_logic/execution/execute_entry_point.py +@@ -9,6 +9,8 @@ from starkware.cairo.lang.vm.relocatable import RelocatableValue + from starkware.cairo.lang.vm.security import SecurityError + from starkware.cairo.lang.vm.utils import ResourcesError + from starkware.cairo.lang.vm.vm_exceptions import HintException, VmException, VmExceptionBase ++from starkware.cairo.lang.vm.cairo_run import write_binary_memory ++import math + from starkware.starknet.business_logic.execution.execute_entry_point_base import ( + ExecuteEntryPointBase, + ) +@@ -274,6 +276,10 @@ class ExecuteEntryPoint(ExecuteEntryPointBase): + assert isinstance(args_ptr, RelocatableValue) # Downcast. + runner.mark_as_accessed(address=args_ptr, size=len(entry_points_args)) + ++ f = open("memory_files/execute_entry_point.memory", "wb") ++ field_bytes = math.ceil(contract_class.program.prime.bit_length() / 8) ++ runner.relocate() ++ write_binary_memory(f, runner.relocated_memory, field_bytes) + return runner, syscall_handler + + def _get_selected_entry_point( +diff --git a/src/starkware/starknet/core/os/class_hash.py b/src/starkware/starknet/core/os/class_hash.py +index 132fb21..321c63a 100644 +--- a/src/starkware/starknet/core/os/class_hash.py ++++ b/src/starkware/starknet/core/os/class_hash.py +@@ -3,9 +3,11 @@ import dataclasses + import itertools + import json + import os ++import math + from contextvars import ContextVar + from functools import lru_cache + from typing import Callable, List, Optional ++from starkware.cairo.lang.vm.cairo_run import write_binary_memory + + import cachetools + +@@ -92,6 +94,10 @@ def compute_class_hash_inner( + use_full_name=True, + verify_secure=False, + ) ++ f = open("memory_files/class_hash.memory", "wb") ++ field_bytes = math.ceil(program.prime.bit_length() / 8) ++ runner.relocate() ++ write_binary_memory(f, runner.relocated_memory, field_bytes) + _, class_hash = runner.get_return_values(2) + return class_hash + diff --git a/scripts/memory_comparator/output-memory-cairo-rs-py.patch b/scripts/memory_comparator/output-memory-cairo-rs-py.patch new file mode 100644 index 00000000..895315e2 --- /dev/null +++ b/scripts/memory_comparator/output-memory-cairo-rs-py.patch @@ -0,0 +1,516 @@ +diff --git a/src/starkware/starknet/business_logic/execution/execute_entry_point.py b/src/starkware/starknet/business_logic/execution/execute_entry_point.py +index 62d4290..719208f 100644 +--- a/src/starkware/starknet/business_logic/execution/execute_entry_point.py ++++ b/src/starkware/starknet/business_logic/execution/execute_entry_point.py +@@ -41,6 +41,7 @@ from starkware.starkware_utils.error_handling import ( + stark_assert, + wrap_with_stark_exception, + ) ++import cairo_rs_py + + logger = logging.getLogger(__name__) + +@@ -182,7 +183,8 @@ class ExecuteEntryPoint(ExecuteEntryPointBase): + + # Run the specified contract entry point with given calldata. + with wrap_with_stark_exception(code=StarknetErrorCode.SECURITY_ERROR): +- runner = CairoFunctionRunner(program=contract_class.program, layout="all") ++ runner = cairo_rs_py.CairoRunner(program=contract_class.program.dumps(), entrypoint=None, layout="all", proof_mode=False) ++ runner.initialize_function_runner() + os_context = os_utils.prepare_os_context(runner=runner) + + validate_contract_deployed(state=state, contract_address=self.contract_address) +@@ -205,24 +207,24 @@ class ExecuteEntryPoint(ExecuteEntryPointBase): + os_context, + len(self.calldata), + # Allocate and mark the segment as read-only (to mark every input array as read-only). +- syscall_handler._allocate_segment(segments=runner.segments, data=self.calldata), ++ syscall_handler._allocate_segment(segments=runner, data=self.calldata), + ] + + try: + runner.run_from_entrypoint( + entry_point.offset, +- *entry_points_args, ++ entry_points_args, + hint_locals={ + "syscall_handler": syscall_handler, + }, +- static_locals={ +- "__find_element_max_size": 2**20, +- "__squash_dict_max_size": 2**20, +- "__keccak_max_size": 2**20, +- "__usort_max_size": 2**20, +- "__chained_ec_op_max_len": 1000, +- }, +- run_resources=tx_execution_context.run_resources, ++ # static_locals={ ++ # "__find_element_max_size": 2**20, ++ # "__squash_dict_max_size": 2**20, ++ # "__keccak_max_size": 2**20, ++ # "__usort_max_size": 2**20, ++ # "__chained_ec_op_max_len": 1000, ++ # }, ++ # run_resources=tx_execution_context.run_resources, + verify_secure=True, + ) + except VmException as exception: +@@ -271,9 +273,10 @@ class ExecuteEntryPoint(ExecuteEntryPointBase): + + # The arguments are touched by the OS and should not be counted as holes, mark them + # as accessed. +- assert isinstance(args_ptr, RelocatableValue) # Downcast. ++ # assert isinstance(args_ptr, RelocatableValue) # Downcast. + runner.mark_as_accessed(address=args_ptr, size=len(entry_points_args)) +- ++ runner.relocate() ++ runner.write_binary_memory("memory_files/execute_entry_point.rs.memory") + return runner, syscall_handler + + def _get_selected_entry_point( +@@ -355,4 +358,4 @@ class ExecuteEntryPoint(ExecuteEntryPointBase): + raise NotImplementedError(f"Call type {self.call_type} not implemented.") + + # Extract pre-fetched contract code from carried state. +- return get_deployed_class_hash_at_address(state=state, contract_address=code_address) ++ return get_deployed_class_hash_at_address(state=state, contract_address=code_address) +\ No newline at end of file +diff --git a/src/starkware/starknet/business_logic/transaction/fee.py b/src/starkware/starknet/business_logic/transaction/fee.py +index 9acaa73..9bbb9f5 100644 +--- a/src/starkware/starknet/business_logic/transaction/fee.py ++++ b/src/starkware/starknet/business_logic/transaction/fee.py +@@ -67,9 +67,9 @@ def calculate_l1_gas_by_cairo_usage( + """ + cairo_resource_fee_weights = general_config.cairo_resource_fee_weights + cairo_resource_names = set(cairo_resource_usage.keys()) +- assert cairo_resource_names.issubset( +- cairo_resource_fee_weights.keys() +- ), "Cairo resource names must be contained in fee weights dict." ++ # assert cairo_resource_names.issubset( ++ # cairo_resource_fee_weights.keys() ++ # ), "Cairo resource names must be contained in fee weights dict." + + # Convert Cairo usage to L1 gas usage. + cairo_l1_gas_usage = max( +diff --git a/src/starkware/starknet/business_logic/utils.py b/src/starkware/starknet/business_logic/utils.py +index f63bc9f..15660fe 100644 +--- a/src/starkware/starknet/business_logic/utils.py ++++ b/src/starkware/starknet/business_logic/utils.py +@@ -48,7 +48,14 @@ def get_return_values(runner: CairoFunctionRunner) -> List[int]: + exception_types=[Exception], + ): + ret_data_size, ret_data_ptr = runner.get_return_values(2) +- values = runner.memory.get_range(ret_data_ptr, ret_data_size) ++ ++ try: ++ # CAIRO-RS VERSION ++ values = runner.get_range(ret_data_ptr, ret_data_size) ++ except: ++ # ORIGINAL VERSION ++ values = runner.memory.get_range(ret_data_ptr, ret_data_size) ++ + + stark_assert( + all(isinstance(value, int) for value in values), +diff --git a/src/starkware/starknet/core/os/class_hash.py b/src/starkware/starknet/core/os/class_hash.py +index 132fb21..3cf48a8 100644 +--- a/src/starkware/starknet/core/os/class_hash.py ++++ b/src/starkware/starknet/core/os/class_hash.py +@@ -5,9 +5,10 @@ import json + import os + from contextvars import ContextVar + from functools import lru_cache +-from typing import Callable, List, Optional ++from typing import Any, Callable, Dict, List, Optional, Tuple + + import cachetools ++import cairo_rs_py + + from starkware.cairo.common.cairo_function_runner import CairoFunctionRunner + from starkware.cairo.common.structs import CairoStructFactory, CairoStructProxy +@@ -23,6 +24,10 @@ from starkware.cairo.lang.vm.crypto import pedersen_hash + from starkware.python.utils import from_bytes + from starkware.starknet.public.abi import starknet_keccak + from starkware.starknet.services.api.contract_class import ContractClass, EntryPointType ++# Added Imports ++from starkware.cairo.lang.vm.relocatable import MaybeRelocatable, RelocatableValue ++from starkware.cairo.lang.vm.vm_exceptions import SecurityError, VmException ++from starkware.python.utils import safe_zip + + CAIRO_FILE = os.path.join(os.path.dirname(__file__), "contracts.cairo") + +@@ -77,22 +82,24 @@ def compute_class_hash_inner( + contract_class_struct = get_contract_class_struct( + identifiers=program.identifiers, contract_class=contract_class + ) +- runner = CairoFunctionRunner(program) + +- hash_builtin = HashBuiltinRunner( +- name="custom_hasher", included=True, ratio=32, hash_func=hash_func +- ) +- runner.builtin_runners["hash_builtin"] = hash_builtin +- hash_builtin.initialize_segments(runner) ++ runner = cairo_rs_py.CairoRunner(program=program.dumps(), entrypoint=None, layout="all", proof_mode=False) ++ runner.initialize_function_runner() ++ hash_ptr = runner.add_additional_hash_builtin() ++ + +- runner.run( ++ run_function_runner( ++ runner, ++ program, + "starkware.starknet.core.os.contracts.class_hash", +- hash_ptr=hash_builtin.base, ++ hash_ptr=hash_ptr, + contract_class=contract_class_struct, + use_full_name=True, + verify_secure=False, + ) + _, class_hash = runner.get_return_values(2) ++ runner.relocate() ++ runner.write_binary_memory("memory_files/class_hash.rs.memory") + return class_hash + + +@@ -194,3 +201,103 @@ def get_contract_class_struct( + bytecode_length=len(contract_class.program.data), + bytecode_ptr=contract_class.program.data, + ) ++ ++def run_function_runner( ++ runner, ++ program, ++ func_name: str, ++ *args, ++ hint_locals: Optional[Dict[str, Any]] = None, ++ static_locals: Optional[Dict[str, Any]] = None, ++ verify_secure: Optional[bool] = None, ++ trace_on_failure: bool = False, ++ apply_modulo_to_args: Optional[bool] = None, ++ use_full_name: bool = False, ++ verify_implicit_args_segment: bool = False, ++ **kwargs, ++ ) -> Tuple[Tuple[MaybeRelocatable, ...], Tuple[MaybeRelocatable, ...]]: ++ """ ++ Runs func_name(*args). ++ args are converted to Cairo-friendly ones using gen_arg. ++ ++ Returns the return values of the function, splitted into 2 tuples of implicit values and ++ explicit values. Structs will be flattened to a sequence of felts as part of the returned ++ tuple. ++ ++ Additional params: ++ verify_secure - Run verify_secure_runner to do extra verifications. ++ trace_on_failure - Run the tracer in case of failure to help debugging. ++ apply_modulo_to_args - Apply modulo operation on integer arguments. ++ use_full_name - Treat 'func_name' as a fully qualified identifier name, rather than a ++ relative one. ++ verify_implicit_args_segment - For each implicit argument, verify that the argument and the ++ return value are in the same segment. ++ """ ++ assert isinstance(program, Program) ++ entrypoint = program.get_label(func_name, full_name_lookup=use_full_name) ++ ++ #Construct Fu ++ structs_factory = CairoStructFactory.from_program(program=program) ++ func = ScopedName.from_string(scope=func_name) ++ ++ full_args_struct = structs_factory.build_func_args(func=func) ++ all_args = full_args_struct(*args, **kwargs) ++ ++ try: ++ runner.run_from_entrypoint( ++ entrypoint, ++ all_args, ++ typed_args=True, ++ hint_locals=hint_locals, ++ static_locals=static_locals, ++ verify_secure=verify_secure, ++ apply_modulo_to_args=apply_modulo_to_args, ++ ) ++ except (VmException, SecurityError, AssertionError) as ex: ++ if trace_on_failure: ++ print( ++ f"""\ ++Got {type(ex).__name__} exception during the execution of {func_name}: ++{str(ex)} ++""" ++ ) ++ #trace_runner(runner=runner) ++ raise ++ ++ # The number of implicit arguments is identical to the number of implicit return values. ++ n_implicit_ret_vals = structs_factory.get_implicit_args_length(func=func) ++ n_explicit_ret_vals = structs_factory.get_explicit_return_values_length(func=func) ++ n_ret_vals = n_explicit_ret_vals + n_implicit_ret_vals ++ implicit_retvals = tuple( ++ runner.get_range( ++ runner.get_ap() - n_ret_vals, n_implicit_ret_vals ++ ) ++ ) ++ ++ explicit_retvals = tuple( ++ runner.get_range( ++ runner.get_ap() - n_explicit_ret_vals, n_explicit_ret_vals ++ ) ++ ) ++ ++ # Verify the memory segments of the implicit arguments. ++ if verify_implicit_args_segment: ++ implicit_args = all_args[:n_implicit_ret_vals] ++ for implicit_arg, implicit_retval in safe_zip(implicit_args, implicit_retvals): ++ assert isinstance( ++ implicit_arg, RelocatableValue ++ ), f"Implicit arguments must be RelocatableValues, {implicit_arg} is not." ++ assert isinstance(implicit_retval, RelocatableValue), ( ++ f"Argument {implicit_arg} is a RelocatableValue, but the returned value " ++ f"{implicit_retval} is not." ++ ) ++ assert implicit_arg.segment_index == implicit_retval.segment_index, ( ++ f"Implicit argument {implicit_arg} is not on the same segment as the returned " ++ f"{implicit_retval}." ++ ) ++ assert implicit_retval.offset >= implicit_arg.offset, ( ++ f"The offset of the returned implicit argument {implicit_retval} is less than " ++ f"the offset of the input {implicit_arg}." ++ ) ++ ++ return implicit_retvals, explicit_retvals +diff --git a/src/starkware/starknet/core/os/os_utils.py b/src/starkware/starknet/core/os/os_utils.py +index 20bd521..0ea99f4 100644 +--- a/src/starkware/starknet/core/os/os_utils.py ++++ b/src/starkware/starknet/core/os/os_utils.py +@@ -43,18 +43,23 @@ def update_builtin_pointers( + + return return_builtins + +- + def prepare_os_context(runner: CairoFunctionRunner) -> List[MaybeRelocatable]: +- syscall_segment = runner.segments.add() +- os_context: List[MaybeRelocatable] = [syscall_segment] +- +- for builtin in runner.program.builtins: +- builtin_runner = runner.builtin_runners[f"{builtin}_builtin"] +- os_context.extend(builtin_runner.initial_stack()) ++ # CAIRO-RS VERSION ++ try: ++ syscall_segment = runner.add_segment() ++ os_context: List[MaybeRelocatable] = [syscall_segment] ++ os_context.extend(runner.get_program_builtins_initial_stack()) ++ # ORIGINAL VERSION ++ except: ++ syscall_segment = runner.segments.add() ++ os_context: List[MaybeRelocatable] = [syscall_segment] ++ ++ for builtin in runner.program.builtins: ++ builtin_runner = runner.builtin_runners[f"{builtin}_builtin"] ++ os_context.extend(builtin_runner.initial_stack()) + + return os_context + +- + def validate_and_process_os_context( + runner: CairoFunctionRunner, + syscall_handler: syscall_utils.BusinessLogicSysCallHandler, +@@ -64,14 +69,23 @@ def validate_and_process_os_context( + Validates and processes an OS context that was returned by a transaction. + Returns the syscall processor object containing the accumulated syscall information. + """ +- # The returned values are os_context, retdata_size, retdata_ptr. +- os_context_end = runner.vm.run_context.ap - 2 +- stack_ptr = os_context_end +- for builtin in runner.program.builtins[::-1]: +- builtin_runner = runner.builtin_runners[f"{builtin}_builtin"] ++ # CAIRO-RS VERSION ++ try: ++ os_context_end = runner.get_ap() - 2 ++ stack_ptr = os_context_end ++ # The returned values are os_context, retdata_size, retdata_ptr. ++ stack_ptr = runner.get_builtins_final_stack(stack_ptr) ++ # ORIGINAL VERSION ++ except: ++ os_context_end = runner.vm.run_context.ap - 2 ++ ++ stack_ptr = os_context_end + +- with wrap_with_stark_exception(code=StarknetErrorCode.SECURITY_ERROR): +- stack_ptr = builtin_runner.final_stack(runner=runner, pointer=stack_ptr) ++ for builtin in runner.program.builtins[::-1]: ++ builtin_runner = runner.builtin_runners[f"{builtin}_builtin"] ++ ++ with wrap_with_stark_exception(code=StarknetErrorCode.SECURITY_ERROR): ++ stack_ptr = builtin_runner.final_stack(runner=runner, pointer=stack_ptr) + + final_os_context_ptr = stack_ptr - 1 + assert final_os_context_ptr + len(initial_os_context) == os_context_end +@@ -81,9 +95,19 @@ def validate_and_process_os_context( + runner=runner, ptr_offset=SYSCALL_PTR_OFFSET, os_context=initial_os_context + ) + +- segment_utils.validate_segment_pointers( +- segments=runner.segments, +- segment_base_ptr=syscall_base_ptr, +- segment_stop_ptr=syscall_stop_ptr, +- ) ++ # ORIGINAL VERSION ++ try: ++ segment_utils.validate_segment_pointers( ++ segments=runner, ++ segment_base_ptr=syscall_base_ptr, ++ segment_stop_ptr=syscall_stop_ptr, ++ ) ++ # CAIRO-RS VERSION ++ except: ++ segment_utils.validate_segment_pointers( ++ segments=runner.segments, ++ segment_base_ptr=syscall_base_ptr, ++ segment_stop_ptr=syscall_stop_ptr, ++ ) ++ + syscall_handler.post_run(runner=runner, syscall_stop_ptr=syscall_stop_ptr) +diff --git a/src/starkware/starknet/core/os/segment_utils.py b/src/starkware/starknet/core/os/segment_utils.py +index 1d09414..33f5c26 100644 +--- a/src/starkware/starknet/core/os/segment_utils.py ++++ b/src/starkware/starknet/core/os/segment_utils.py +@@ -8,7 +8,7 @@ from starkware.starknet.definitions.error_codes import StarknetErrorCode + from starkware.starknet.public.abi import SYSCALL_PTR_OFFSET + from starkware.starkware_utils.error_handling import stark_assert, wrap_with_stark_exception + +- ++# LAMBDA MODIFIED + def get_os_segment_ptr_range( + runner: CairoFunctionRunner, ptr_offset: int, os_context: List[MaybeRelocatable] + ) -> Tuple[MaybeRelocatable, MaybeRelocatable]: +@@ -21,10 +21,23 @@ def get_os_segment_ptr_range( + ), f"Illegal OS ptr offset; must be one of: {allowed_offsets}." + + # The returned values are os_context, retdata_size, retdata_ptr. +- os_context_end = runner.vm.run_context.ap - 2 ++ # CAIRO-RS VERSION ++ try: ++ os_context_end = runner.get_ap() - 2 ++ except: ++ # ORIGINAL VERSION ++ os_context_end = runner.vm.run_context.ap - 2 ++ + final_os_context_ptr = os_context_end - len(os_context) + +- return os_context[ptr_offset], runner.vm_memory[final_os_context_ptr + ptr_offset] ++ # CAIRO-RS VERSION ++ try: ++ return os_context[ptr_offset], runner.get(final_os_context_ptr + ptr_offset) ++ # ORIGINAL VERSION ++ except: ++ return os_context[ptr_offset], runner.vm_memory[final_os_context_ptr + ptr_offset] ++ ++ + + + def get_os_segment_stop_ptr( +@@ -61,14 +74,19 @@ def validate_segment_pointers( + segment_base_ptr: MaybeRelocatable, + segment_stop_ptr: MaybeRelocatable, + ): +- assert isinstance(segment_base_ptr, RelocatableValue) ++ # assert isinstance(segment_base_ptr, RelocatableValue) + assert ( + segment_base_ptr.offset == 0 + ), f"Segment base pointer must be zero; got {segment_base_ptr.offset}." + +- expected_stop_ptr = segment_base_ptr + segments.get_segment_used_size( +- segment_index=segment_base_ptr.segment_index +- ) ++ # CAIRO-RS VERSION ++ try: ++ expected_stop_ptr = segment_base_ptr + segments.get_segment_used_size( ++ index=segment_base_ptr.segment_index) ++ # ORIGINAL VERSION ++ except: ++ expected_stop_ptr = segment_base_ptr + segments.get_segment_used_size( ++ segment_index=segment_base_ptr.segment_index) + + stark_assert( + expected_stop_ptr == segment_stop_ptr, +diff --git a/src/starkware/starknet/core/os/syscall_utils.py b/src/starkware/starknet/core/os/syscall_utils.py +index e44635b..c129e50 100644 +--- a/src/starkware/starknet/core/os/syscall_utils.py ++++ b/src/starkware/starknet/core/os/syscall_utils.py +@@ -458,7 +458,6 @@ class SysCallHandlerBase(ABC): + ) -> List[int]: + """ + Returns the call retdata. +- + syscall_name can be "call_contract", "delegate_call", "delegate_l1_handler", "library_call" + or "library_call_l1_handler". + """ +@@ -589,7 +588,16 @@ class BusinessLogicSysCallHandler(SysCallHandlerBase): + def _allocate_segment( + self, segments: MemorySegmentManager, data: Iterable[MaybeRelocatable] + ) -> RelocatableValue: +- segment_start = segments.add() ++ # FIXME: Here "segments" in really a Runner under the hood. ++ # May want to change the variable names. ++ ++ # CAIRO-RS VERSION ++ try: ++ segment_start = segments.add_segment() ++ # ORIGINAL VERSION ++ except: ++ segment_start = segments.add() ++ + segment_end = segments.write_arg(ptr=segment_start, arg=data) + self.read_only_segments.append((segment_start, segment_end - segment_start)) + return segment_start +@@ -631,10 +639,10 @@ class BusinessLogicSysCallHandler(SysCallHandlerBase): + args_struct_def: StructDefinition = syscall_info.syscall_request_struct.struct_definition_ + for arg, (arg_name, arg_def) in safe_zip(request, args_struct_def.members.items()): + expected_type = get_runtime_type(arg_def.cairo_type) +- assert isinstance(arg, expected_type), ( +- f"Argument {arg_name} to syscall {syscall_name} is of unexpected type. " +- f"Expected: value of type {expected_type}; got: {arg}." +- ) ++ # assert isinstance(arg, expected_type), ( ++ # f"Argument {arg_name} to syscall {syscall_name} is of unexpected type. " ++ # f"Expected: value of type {expected_type}; got: {arg}." ++ # ) + + return request + +@@ -902,10 +910,20 @@ class BusinessLogicSysCallHandler(SysCallHandlerBase): + Validates that there were no out of bounds writes to read-only segments and marks + them as accessed. + """ +- segments = runner.segments ++ # ORIGINAL VERSION ++ try: ++ segments = runner.segments ++ # CAIRO-RS VERSION ++ except: ++ segments = runner + + for segment_ptr, segment_size in self.read_only_segments: +- used_size = segments.get_segment_used_size(segment_index=segment_ptr.segment_index) ++ # CAIRO-RS VERSION ++ try: ++ used_size = segments.get_segment_used_size(index=segment_ptr.segment_index) ++ # ORIGINAL VERSION ++ except: ++ used_size = segments.get_segment_used_size(segment_index=segment_ptr.segment_index) + stark_assert( + used_size == segment_size, + code=StarknetErrorCode.SECURITY_ERROR, +@@ -1041,7 +1059,6 @@ class OsSysCallHandler(SysCallHandlerBase): + def start_tx(self, tx_info_ptr: RelocatableValue): + """ + Called when starting the execution of a transaction. +- + 'tx_info_ptr' is a pointer to the TxInfo struct corresponding to said transaction. + """ + assert self.tx_info_ptr is None +@@ -1089,4 +1106,4 @@ class OsSysCallHandler(SysCallHandlerBase): + Called when skipping the execution of a transaction. + It replaces a call to start_tx and end_tx. + """ +- next(self.tx_execution_info_iterator) ++ next(self.tx_execution_info_iterator) +\ No newline at end of file diff --git a/scripts/memory_comparator/run_tests_compare_memory.sh b/scripts/memory_comparator/run_tests_compare_memory.sh new file mode 100644 index 00000000..ddc9f627 --- /dev/null +++ b/scripts/memory_comparator/run_tests_compare_memory.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env sh +# Please run this script from cairo-rs-py directory +exit_code=0 +# We need to be inside starknet-devnet in order to run poetry +cd starknet-devnet +for file in test/test_*.py; do + # Skip files that dont run entrypoints and dont produce memory outputs + if ! ([ "$file" = "test/test_estimate_fee.py" ] || [ "$file" = "test/test_postman.py" ] || [ "$file" = "test/testnet_deployment.py" ] || [ "$file" = "test/testnet_deploy.py" ] || [ "$file" = "test/test_api_specifications.py" ] || [ "$file" = "test/test_fork_cli_params.py" ]); then + # Run tests in cairo-rs-py env + . ../scripts/memory_comparator/cairo-rs-py/bin/activate + poetry run pytest $file + # Run tests in cairo-lang env + . ../scripts/memory_comparator/cairo-lang/bin/activate + poetry run pytest $file + # Compare memory outputs + class_hash_path="memory_files/class_hash" + execute_entry_point_path="memory_files/execute_entry_point" + memory_comparator_path="../scripts/memory_comparator/memory_comparator.py" + # Some tests do not use class_hash and dont generate memory files there + if ! ([ "$file" = "test/test_dump.py" ]); then + if ! $memory_comparator_path $class_hash_path.memory $class_hash_path.rs.memory; then + echo "Memory differs for last class_hash on test $file" + exit_code=1 + else + echo "Memory comparison successful" + fi + fi + # Some tests do not use execute_entry_point and dont generate memory files there + if ! ([ "$file" = "test/test_account_predeployed.py" ]); then + if ! $memory_comparator_path $execute_entry_point_path.memory $execute_entry_point_path.rs.memory; then + echo "Memory differs for last execute_entry_point on test $file" + exit_code=1 + else + echo "Memory comparison successful" + fi + fi + # Cleanup memory files + rm memory_files/*.memory + fi +done +exit "${exit_code}" diff --git a/src/cairo_runner.rs b/src/cairo_runner.rs index 08ef2ee8..ed40c349 100644 --- a/src/cairo_runner.rs +++ b/src/cairo_runner.rs @@ -6,7 +6,7 @@ use crate::{ }; use cairo_rs::{ bigint, - cairo_run::write_output, + cairo_run::{write_binary_memory, write_output}, hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor, serde::deserialize_program::Member, types::{ @@ -27,7 +27,14 @@ use pyo3::{ prelude::*, types::PyIterator, }; -use std::{any::Any, borrow::BorrowMut, collections::HashMap, iter::zip, path::PathBuf, rc::Rc}; +use std::{ + any::Any, + borrow::BorrowMut, + collections::HashMap, + iter::zip, + path::{Path, PathBuf}, + rc::Rc, +}; const MEMORY_GET_SEGMENT_USED_SIZE_MSG: &str = "Failed to segment used size"; const FAILED_TO_GET_INITIAL_FP: &str = "Failed to get initial segment"; @@ -214,6 +221,10 @@ impl PyCairoRunner { write_output(&mut self.inner, &mut (*self.pyvm.vm).borrow_mut()).map_err(to_py_error) } + pub fn write_binary_memory(&mut self, name: String) -> PyResult<()> { + write_binary_memory(&self.inner.relocated_memory, Path::new(&name)).map_err(to_py_error) + } + pub fn add_segment(&self) -> PyRelocatable { (*self.pyvm.vm).borrow_mut().add_memory_segment().into() }