diff --git a/Cargo.toml b/Cargo.toml index f0ee0a9fb..7c331633b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -130,7 +130,10 @@ with-debug-utils = [] with-mem-tracing = [] with-libfunc-profiling = [] with-segfault-catcher = [] -with-trace-dump = ["dep:sierra-emu"] +with-trace-dump = ["sierra-emu"] +# Enables the `ContractExecutor::Emu` variant for dispatching contract execution +# through the sierra-emu interpreter. +sierra-emu = ["dep:sierra-emu"] testing = ["dep:cairo-lang-compiler", "dep:cairo-lang-filesystem"] [dependencies] diff --git a/debug_utils/sierra-emu/src/vm.rs b/debug_utils/sierra-emu/src/vm.rs index 294cbc236..ed109b6d2 100644 --- a/debug_utils/sierra-emu/src/vm.rs +++ b/debug_utils/sierra-emu/src/vm.rs @@ -449,6 +449,32 @@ impl VirtualMachine { ContractExecutionResult::from_state(&last?) } + + /// One-shot convenience over [`VirtualMachine::new_starknet`], + /// [`VirtualMachine::call_contract`] and [`VirtualMachine::run`]: build a + /// Starknet VM for `program`, call the entry point identified by `selector` + /// and run it to completion. + /// + /// Returns `None` when execution never produced a final state. + #[allow(clippy::too_many_arguments)] + pub fn run_contract( + program: Arc, + entry_points: &ContractEntryPoints, + sierra_version: VersionId, + selector: Felt, + initial_gas: u64, + calldata: I, + builtin_costs: Option, + syscall_handler: &mut impl StarknetSyscallHandler, + ) -> Option + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + { + let mut vm = Self::new_starknet(program, entry_points, sierra_version); + vm.call_contract(selector, initial_gas, calldata, builtin_costs); + vm.run(syscall_handler) + } } #[derive(Clone, Debug)] diff --git a/src/executor.rs b/src/executor.rs index 9337c8e05..a7f0acfbc 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -3,6 +3,8 @@ //! This module provides methods to execute the programs, either via JIT or compiled ahead //! of time. It also provides a cache to avoid recompiling previously compiled programs. +#[cfg(feature = "sierra-emu")] +pub use self::emu_contract_executor::EmuContractExecutor; pub use self::{aot::AotNativeExecutor, contract::AotContractExecutor, jit::JitNativeExecutor}; use crate::{ arch::{AbiArgument, ValueWithInfoWrapper}, @@ -39,6 +41,8 @@ use std::{alloc::Layout, arch::global_asm, ptr::NonNull}; mod aot; mod contract; +#[cfg(feature = "sierra-emu")] +mod emu_contract_executor; mod jit; #[cfg(target_arch = "aarch64")] diff --git a/src/executor/emu_contract_executor.rs b/src/executor/emu_contract_executor.rs new file mode 100644 index 000000000..4622a652d --- /dev/null +++ b/src/executor/emu_contract_executor.rs @@ -0,0 +1,82 @@ +//! A sierra-emu-backed contract executor exposing the same `run` shape as +//! [`AotContractExecutor::run`](crate::executor::AotContractExecutor::run), so a caller +//! can swap the two behind a feature flag without changing call-site code. +//! +//! Both executors share the same `H: StarknetSyscallHandler` -- sierra-emu and +//! cairo-native re-export the trait from `cairo-native-syscalls`, so no adapter is +//! needed. + +use cairo_lang_sierra::program::Program; +use cairo_lang_starknet_classes::compiler_version::VersionId; +use cairo_lang_starknet_classes::contract_class::ContractEntryPoints; +use starknet_types_core::felt::Felt; +use std::sync::Arc; + +use crate::error::{Error, Result}; +use crate::execution_result::ContractExecutionResult; +use crate::starknet::StarknetSyscallHandler; +use crate::utils::BuiltinCosts; + +/// Runs contract entry points through the sierra-emu interpreter. +/// +/// Holds the program + entry points + sierra version triple that +/// `sierra_emu::VirtualMachine::run_contract` requires; the `Arc` is shared +/// across invocations rather than cloned per call. +#[derive(Debug, Clone)] +pub struct EmuContractExecutor { + pub program: Arc, + pub entry_points: ContractEntryPoints, + pub sierra_version: VersionId, +} + +impl EmuContractExecutor { + /// Run the contract entry point identified by `selector`. + /// + /// Mirrors [`AotContractExecutor::run`](crate::executor::AotContractExecutor::run) so + /// the two executor types are interchangeable at the call site. + pub fn run( + &self, + selector: Felt, + args: &[Felt], + gas: u64, + builtin_costs: Option, + mut syscall_handler: impl StarknetSyscallHandler, + ) -> Result { + // `run_contract` returns `None` when the VM never produced a final state -- + // propagate as an error rather than aborting the host. + let result = sierra_emu::VirtualMachine::run_contract( + Arc::clone(&self.program), + &self.entry_points, + self.sierra_version, + selector, + gas, + args.to_vec(), + builtin_costs.map(convert_builtin_costs), + &mut syscall_handler, + ) + .ok_or_else(|| { + Error::UnexpectedValue("sierra-emu VM produced no final state".to_string()) + })?; + + Ok(ContractExecutionResult { + remaining_gas: result.remaining_gas, + failure_flag: result.failure_flag, + return_values: result.return_values, + error_msg: result.error_msg, + builtin_stats: Default::default(), + }) + } +} + +fn convert_builtin_costs(builtin_costs: BuiltinCosts) -> sierra_emu::BuiltinCosts { + sierra_emu::BuiltinCosts { + r#const: builtin_costs.r#const, + pedersen: builtin_costs.pedersen, + bitwise: builtin_costs.bitwise, + ecop: builtin_costs.ecop, + poseidon: builtin_costs.poseidon, + add_mod: builtin_costs.add_mod, + mul_mod: builtin_costs.mul_mod, + blake: builtin_costs.blake, + } +}