1use std::collections::HashMap;
3
4use cairo_lang_casm::hints::Hint;
5use cairo_lang_runnable_utils::builder::{BuildError, EntryCodeConfig, RunnableBuilder};
6use cairo_lang_sierra::extensions::NamedType;
7use cairo_lang_sierra::extensions::enm::EnumType;
8use cairo_lang_sierra::extensions::gas::{CostTokenType, GasBuiltinType};
9use cairo_lang_sierra::ids::{ConcreteTypeId, GenericTypeId};
10use cairo_lang_sierra::program::{Function, GenericArg};
11use cairo_lang_sierra_to_casm::metadata::MetadataComputationConfig;
12use cairo_lang_starknet::contract::ContractInfo;
13use cairo_lang_utils::casts::IntoOrPanic;
14use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
15use cairo_lang_utils::{extract_matches, require};
16use cairo_vm::hint_processor::hint_processor_definition::HintProcessor;
17use cairo_vm::serde::deserialize_program::HintParams;
18use cairo_vm::types::builtin_name::BuiltinName;
19use cairo_vm::vm::errors::cairo_run_errors::CairoRunError;
20use cairo_vm::vm::runners::cairo_runner::{ExecutionResources, RunResources};
21use cairo_vm::vm::vm_core::VirtualMachine;
22use casm_run::hint_to_hint_params;
23pub use casm_run::{CairoHintProcessor, StarknetState};
24use num_bigint::BigInt;
25use num_traits::ToPrimitive;
26use profiling::ProfilingInfo;
27use starknet_types_core::felt::Felt as Felt252;
28use thiserror::Error;
29
30use crate::casm_run::{RunFunctionResult, StarknetHintProcessor};
31use crate::profiling::ProfilerConfig;
32
33pub mod casm_run;
34pub mod clap;
35pub mod profiling;
36pub mod short_string;
37
38const MAX_STACK_TRACE_DEPTH_DEFAULT: usize = 100;
39
40#[derive(Debug, Error)]
41pub enum RunnerError {
42 #[error(transparent)]
43 BuildError(#[from] BuildError),
44 #[error("Not enough gas to call function.")]
45 NotEnoughGasToCall,
46 #[error("Function param {param_index} only partially contains argument {arg_index}.")]
47 ArgumentUnaligned { param_index: usize, arg_index: usize },
48 #[error("Function expects arguments of size {expected} and received {actual} instead.")]
49 ArgumentsSizeMismatch { expected: usize, actual: usize },
50 #[error(transparent)]
51 CairoRunError(#[from] Box<CairoRunError>),
52}
53
54pub struct RunResultStarknet {
56 pub gas_counter: Option<Felt252>,
57 pub memory: Vec<Option<Felt252>>,
58 pub value: RunResultValue,
59 pub starknet_state: StarknetState,
60 pub used_resources: StarknetExecutionResources,
61 pub profiling_info: Option<ProfilingInfo>,
63}
64
65#[derive(Debug, Eq, PartialEq, Clone)]
67pub struct RunResult {
68 pub gas_counter: Option<Felt252>,
69 pub memory: Vec<Option<Felt252>>,
70 pub value: RunResultValue,
71 pub used_resources: ExecutionResources,
72 pub profiling_info: Option<ProfilingInfo>,
74}
75
76#[derive(Debug, Eq, PartialEq, Clone, Default)]
79pub struct StarknetExecutionResources {
80 pub basic_resources: ExecutionResources,
82 pub syscalls: HashMap<String, usize>,
84}
85
86impl std::ops::AddAssign<StarknetExecutionResources> for StarknetExecutionResources {
87 fn add_assign(&mut self, other: Self) {
89 self.basic_resources += &other.basic_resources;
90 for (k, v) in other.syscalls {
91 *self.syscalls.entry(k).or_default() += v;
92 }
93 }
94}
95
96#[derive(Debug, Eq, PartialEq, Clone)]
98pub enum RunResultValue {
99 Success(Vec<Felt252>),
101 Panic(Vec<Felt252>),
103}
104
105pub fn token_gas_cost(token_type: CostTokenType) -> usize {
107 match token_type {
108 CostTokenType::Const => 1,
109 CostTokenType::Step
110 | CostTokenType::Hole
111 | CostTokenType::RangeCheck
112 | CostTokenType::RangeCheck96 => {
113 panic!("Token type {token_type:?} has no gas cost.")
114 }
115 CostTokenType::Pedersen => 4050,
116 CostTokenType::Poseidon => 491,
117 CostTokenType::Bitwise => 583,
118 CostTokenType::EcOp => 4085,
119 CostTokenType::AddMod => 230,
120 CostTokenType::MulMod => 604,
121 CostTokenType::Blake => 3334,
122 }
123}
124
125#[derive(Debug, Clone)]
127pub enum Arg {
128 Value(Felt252),
129 Array(Vec<Arg>),
130}
131impl Arg {
132 pub fn size(&self) -> usize {
134 match self {
135 Self::Value(_) => 1,
136 Self::Array(_) => 2,
137 }
138 }
139}
140impl From<Felt252> for Arg {
141 fn from(value: Felt252) -> Self {
142 Self::Value(value)
143 }
144}
145
146pub fn build_hints_dict(
148 hints: &[(usize, Vec<Hint>)],
149) -> (HashMap<usize, Vec<HintParams>>, HashMap<String, Hint>) {
150 let mut hints_dict: HashMap<usize, Vec<HintParams>> = HashMap::new();
151 let mut string_to_hint: HashMap<String, Hint> = HashMap::new();
152
153 for (offset, offset_hints) in hints {
154 for hint in offset_hints {
156 string_to_hint.insert(hint.representing_string(), hint.clone());
157 }
158 hints_dict.insert(*offset, offset_hints.iter().map(hint_to_hint_params).collect());
160 }
161 (hints_dict, string_to_hint)
162}
163
164pub struct PreparedStarknetContext {
172 pub hints_dict: HashMap<usize, Vec<HintParams>>,
173 pub bytecode: Vec<BigInt>,
174 pub builtins: Vec<BuiltinName>,
175}
176
177pub struct SierraCasmRunner {
179 builder: RunnableBuilder,
181 starknet_contracts_info: OrderedHashMap<Felt252, ContractInfo>,
183 run_profiler: Option<ProfilingInfoCollectionConfig>,
185}
186impl SierraCasmRunner {
187 pub fn new(
188 sierra_program: cairo_lang_sierra::program::Program,
189 metadata_config: Option<MetadataComputationConfig>,
190 starknet_contracts_info: OrderedHashMap<Felt252, ContractInfo>,
191 run_profiler: Option<ProfilingInfoCollectionConfig>,
192 ) -> Result<Self, RunnerError> {
193 Ok(Self {
194 builder: RunnableBuilder::new(sierra_program, metadata_config)?,
195 starknet_contracts_info,
196 run_profiler,
197 })
198 }
199
200 pub fn run_function_with_starknet_context(
202 &self,
203 func: &Function,
204 args: Vec<Arg>,
205 available_gas: Option<usize>,
206 starknet_state: StarknetState,
207 ) -> Result<RunResultStarknet, RunnerError> {
208 let (mut hint_processor, ctx) =
209 self.prepare_starknet_context(func, args, available_gas, starknet_state)?;
210 self.run_function_with_prepared_starknet_context(func, &mut hint_processor, ctx)
211 }
212
213 pub fn run_function_with_prepared_starknet_context(
216 &self,
217 func: &Function,
218 hint_processor: &mut dyn StarknetHintProcessor,
219 PreparedStarknetContext { hints_dict, bytecode, builtins }: PreparedStarknetContext,
220 ) -> Result<RunResultStarknet, RunnerError> {
221 let RunResult { gas_counter, memory, value, used_resources, profiling_info } =
222 self.run_function(func, hint_processor, hints_dict, bytecode.iter(), builtins)?;
223 let mut all_used_resources = hint_processor.take_syscalls_used_resources();
224 all_used_resources.basic_resources += &used_resources;
225 Ok(RunResultStarknet {
226 gas_counter,
227 memory,
228 value,
229 starknet_state: hint_processor.take_starknet_state(),
230 used_resources: all_used_resources,
231 profiling_info,
232 })
233 }
234
235 fn inner_type_from_panic_wrapper(
237 &self,
238 ty: &GenericTypeId,
239 func: &Function,
240 ) -> Option<ConcreteTypeId> {
241 let generic_args = &func
242 .signature
243 .ret_types
244 .iter()
245 .find_map(|rt| {
246 let long_id = self.builder.type_long_id(rt);
247 (long_id.generic_id == *ty).then_some(long_id)
248 })
249 .unwrap()
250 .generic_args;
251
252 if *ty == EnumType::ID
253 && matches!(&generic_args[0], GenericArg::UserType(ut)
254 if ut.debug_name.as_ref().unwrap().starts_with("core::panics::PanicResult::"))
255 {
256 return Some(extract_matches!(&generic_args[1], GenericArg::Type).clone());
257 }
258 None
259 }
260
261 pub fn run_function<'a, Bytecode>(
265 &self,
266 func: &Function,
267 hint_processor: &mut dyn HintProcessor,
268 hints_dict: HashMap<usize, Vec<HintParams>>,
269 bytecode: Bytecode,
270 builtins: Vec<BuiltinName>,
271 ) -> Result<RunResult, RunnerError>
272 where
273 Bytecode: ExactSizeIterator<Item = &'a BigInt> + Clone,
274 {
275 let return_types =
276 self.builder.generic_id_and_size_from_concrete(&func.signature.ret_types);
277 let data_len = bytecode.len();
278 let RunFunctionResult { ap, mut used_resources, memory, relocated_trace } =
279 casm_run::run_function(
280 bytecode,
281 builtins,
282 |vm| initialize_vm(vm, data_len),
283 hint_processor,
284 hints_dict,
285 )?;
286
287 let header_end = relocated_trace.last().unwrap().pc;
290 used_resources.n_steps -=
291 relocated_trace.iter().position(|e| e.pc > header_end).unwrap() - 1;
292 used_resources.n_steps -=
293 relocated_trace.iter().rev().position(|e| e.pc > header_end).unwrap() - 1;
294
295 let (results_data, gas_counter) = self.get_results_data(&return_types, &memory, ap);
296 assert!(results_data.len() <= 1);
297
298 let value = match results_data.into_iter().next() {
299 None => RunResultValue::Success(vec![]),
301 Some((ty, values)) => {
302 let inner_ty = self
303 .inner_type_from_panic_wrapper(&ty, func)
304 .map(|it| self.builder.type_size(&it));
305 Self::handle_main_return_value(inner_ty, values, &memory)
306 }
307 };
308
309 let Self { builder, starknet_contracts_info: _, run_profiler } = self;
310
311 let load_offset = header_end + 1;
313
314 let profiling_info = run_profiler.as_ref().map(|config| {
315 ProfilingInfo::from_trace(builder, load_offset, config, &relocated_trace)
316 });
317
318 Ok(RunResult { gas_counter, memory, value, used_resources, profiling_info })
319 }
320
321 pub fn prepare_starknet_context(
327 &self,
328 func: &Function,
329 args: Vec<Arg>,
330 available_gas: Option<usize>,
331 starknet_state: StarknetState,
332 ) -> Result<(CairoHintProcessor<'_>, PreparedStarknetContext), RunnerError> {
333 let (assembled_program, builtins) =
334 self.builder.assemble_function_program(func, EntryCodeConfig::testing())?;
335 let (hints_dict, string_to_hint) = build_hints_dict(&assembled_program.hints);
336 let user_args = self.prepare_args(func, available_gas, args)?;
337 let hint_processor = CairoHintProcessor {
338 runner: Some(self),
339 user_args,
340 starknet_state,
341 string_to_hint,
342 run_resources: RunResources::default(),
343 syscalls_used_resources: Default::default(),
344 no_temporary_segments: true,
345 markers: Default::default(),
346 panic_traceback: Default::default(),
347 };
348 Ok((
349 hint_processor,
350 PreparedStarknetContext { hints_dict, bytecode: assembled_program.bytecode, builtins },
351 ))
352 }
353
354 fn prepare_args(
356 &self,
357 func: &Function,
358 available_gas: Option<usize>,
359 args: Vec<Arg>,
360 ) -> Result<Vec<Vec<Arg>>, RunnerError> {
361 let mut user_args = vec![];
362 if let Some(gas) = self
363 .requires_gas_builtin(func)
364 .then_some(self.get_initial_available_gas(func, available_gas)?)
365 {
366 user_args.push(vec![Arg::Value(Felt252::from(gas))]);
367 }
368 let mut expected_arguments_size = 0;
369 let actual_args_size = args_size(&args);
370 let mut arg_iter = args.into_iter().enumerate();
371 for (param_index, (_, param_size)) in self
372 .builder
373 .generic_id_and_size_from_concrete(&func.signature.param_types)
374 .into_iter()
375 .filter(|(ty, _)| self.builder.is_user_arg_type(ty))
376 .enumerate()
377 {
378 let mut curr_arg = vec![];
379 let param_size: usize = param_size.into_or_panic();
380 expected_arguments_size += param_size;
381 let mut taken_size = 0;
382 while taken_size < param_size {
383 let Some((arg_index, arg)) = arg_iter.next() else {
384 break;
385 };
386 taken_size += arg.size();
387 if taken_size > param_size {
388 return Err(RunnerError::ArgumentUnaligned { param_index, arg_index });
389 }
390 curr_arg.push(arg);
391 }
392 user_args.push(curr_arg);
393 }
394 if expected_arguments_size != actual_args_size {
395 return Err(RunnerError::ArgumentsSizeMismatch {
396 expected: expected_arguments_size,
397 actual: actual_args_size,
398 });
399 }
400 Ok(user_args)
401 }
402
403 pub fn handle_main_return_value(
405 inner_type_size: Option<i16>,
406 values: Vec<Felt252>,
407 cells: &[Option<Felt252>],
408 ) -> RunResultValue {
409 if let Some(inner_type_size) = inner_type_size {
410 if values[0] == Felt252::from(0) {
412 let inner_ty_size = inner_type_size as usize;
414 let skip_size = values.len() - inner_ty_size;
415 RunResultValue::Success(values.into_iter().skip(skip_size).collect())
416 } else {
417 let err_data_start = values[values.len() - 2].to_usize().unwrap();
419 let err_data_end = values[values.len() - 1].to_usize().unwrap();
420 RunResultValue::Panic(
421 cells[err_data_start..err_data_end]
422 .iter()
423 .cloned()
424 .map(Option::unwrap)
425 .collect(),
426 )
427 }
428 } else {
429 RunResultValue::Success(values)
431 }
432 }
433
434 pub fn get_results_data(
436 &self,
437 return_types: &[(GenericTypeId, i16)],
438 cells: &[Option<Felt252>],
439 mut ap: usize,
440 ) -> (Vec<(GenericTypeId, Vec<Felt252>)>, Option<Felt252>) {
441 let mut results_data = vec![];
442 for (ty, ty_size) in return_types.iter().rev() {
443 let size = *ty_size as usize;
444 let values: Vec<Felt252> =
445 ((ap - size)..ap).map(|index| cells[index].unwrap()).collect();
446 ap -= size;
447 results_data.push((ty.clone(), values));
448 }
449
450 let mut gas_counter = None;
452 results_data.retain_mut(|(ty, values)| {
453 let generic_ty = ty;
454 if *generic_ty == GasBuiltinType::ID {
455 gas_counter = Some(values.remove(0));
456 assert!(values.is_empty());
457 false
458 } else {
459 self.builder.is_user_arg_type(generic_ty)
460 }
461 });
462
463 (results_data, gas_counter)
464 }
465
466 pub fn find_function(&self, name_suffix: &str) -> Result<&Function, RunnerError> {
468 Ok(self.builder.find_function(name_suffix)?)
469 }
470
471 fn requires_gas_builtin(&self, func: &Function) -> bool {
473 func.signature
474 .param_types
475 .iter()
476 .any(|ty| self.builder.type_long_id(ty).generic_id == GasBuiltinType::ID)
477 }
478
479 pub fn get_initial_available_gas(
482 &self,
483 func: &Function,
484 available_gas: Option<usize>,
485 ) -> Result<usize, RunnerError> {
486 let Some(available_gas) = available_gas else {
487 return Ok(0);
488 };
489
490 let Some(required_gas) = self.initial_required_gas(func) else {
494 return Ok(0);
495 };
496
497 available_gas.checked_sub(required_gas).ok_or(RunnerError::NotEnoughGasToCall)
498 }
499
500 pub fn initial_required_gas(&self, func: &Function) -> Option<usize> {
501 let gas_info = &self.builder.metadata().gas_info;
502 require(!gas_info.function_costs.is_empty())?;
503 Some(
504 gas_info.function_costs[&func.id]
505 .iter()
506 .map(|(token_type, val)| val.into_or_panic::<usize>() * token_gas_cost(*token_type))
507 .sum(),
508 )
509 }
510}
511
512#[derive(Debug, Eq, PartialEq, Clone)]
514pub struct ProfilingInfoCollectionConfig {
515 pub max_stack_trace_depth: usize,
517 pub collect_scoped_sierra_statement_weights: bool,
523}
524
525impl ProfilingInfoCollectionConfig {
526 pub fn set_max_stack_trace_depth(&mut self, max_stack_depth: usize) -> &mut Self {
527 self.max_stack_trace_depth = max_stack_depth;
528 self
529 }
530
531 pub fn from_profiler_config(profiler_config: &ProfilerConfig) -> Self {
532 match profiler_config {
533 ProfilerConfig::Cairo | ProfilerConfig::Sierra => Self::default(),
534 ProfilerConfig::Scoped => ProfilingInfoCollectionConfig {
535 collect_scoped_sierra_statement_weights: true,
536 ..Self::default()
537 },
538 }
539 }
540}
541
542impl Default for ProfilingInfoCollectionConfig {
543 fn default() -> Self {
547 Self {
548 max_stack_trace_depth: if let Ok(max) = std::env::var("MAX_STACK_TRACE_DEPTH") {
549 if max.is_empty() {
550 MAX_STACK_TRACE_DEPTH_DEFAULT
551 } else {
552 max.parse::<usize>().expect("MAX_STACK_TRACE_DEPTH env var is not numeric")
553 }
554 } else {
555 MAX_STACK_TRACE_DEPTH_DEFAULT
556 },
557 collect_scoped_sierra_statement_weights: false,
558 }
559 }
560}
561
562pub fn initialize_vm(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Box<CairoRunError>> {
565 let builtin_cost_segment = vm.add_memory_segment();
567 for token_type in CostTokenType::iter_precost() {
568 vm.insert_value(
569 (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)).unwrap(),
570 Felt252::from(token_gas_cost(*token_type)),
571 )
572 .map_err(|e| Box::new(e.into()))?;
573 }
574 vm.insert_value((vm.get_pc() + data_len).unwrap(), builtin_cost_segment)
577 .map_err(|e| Box::new(e.into()))?;
578 Ok(())
579}
580
581fn args_size(args: &[Arg]) -> usize {
583 args.iter().map(Arg::size).sum()
584}