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;
31
32pub mod casm_run;
33pub mod profiling;
34pub mod short_string;
35
36const MAX_STACK_TRACE_DEPTH_DEFAULT: usize = 100;
37
38#[derive(Debug, Error)]
39pub enum RunnerError {
40 #[error(transparent)]
41 BuildError(#[from] BuildError),
42 #[error("Not enough gas to call function.")]
43 NotEnoughGasToCall,
44 #[error("Function param {param_index} only partially contains argument {arg_index}.")]
45 ArgumentUnaligned { param_index: usize, arg_index: usize },
46 #[error("Function expects arguments of size {expected} and received {actual} instead.")]
47 ArgumentsSizeMismatch { expected: usize, actual: usize },
48 #[error(transparent)]
49 CairoRunError(#[from] Box<CairoRunError>),
50}
51
52pub struct RunResultStarknet {
54 pub gas_counter: Option<Felt252>,
55 pub memory: Vec<Option<Felt252>>,
56 pub value: RunResultValue,
57 pub starknet_state: StarknetState,
58 pub used_resources: StarknetExecutionResources,
59 pub profiling_info: Option<ProfilingInfo>,
61}
62
63#[derive(Debug, Eq, PartialEq, Clone)]
65pub struct RunResult {
66 pub gas_counter: Option<Felt252>,
67 pub memory: Vec<Option<Felt252>>,
68 pub value: RunResultValue,
69 pub used_resources: ExecutionResources,
70 pub profiling_info: Option<ProfilingInfo>,
72}
73
74#[derive(Debug, Eq, PartialEq, Clone, Default)]
77pub struct StarknetExecutionResources {
78 pub basic_resources: ExecutionResources,
80 pub syscalls: HashMap<String, usize>,
82}
83
84impl std::ops::AddAssign<StarknetExecutionResources> for StarknetExecutionResources {
85 fn add_assign(&mut self, other: Self) {
87 self.basic_resources += &other.basic_resources;
88 for (k, v) in other.syscalls {
89 *self.syscalls.entry(k).or_default() += v;
90 }
91 }
92}
93
94#[derive(Debug, Eq, PartialEq, Clone)]
96pub enum RunResultValue {
97 Success(Vec<Felt252>),
99 Panic(Vec<Felt252>),
101}
102
103pub fn token_gas_cost(token_type: CostTokenType) -> usize {
105 match token_type {
106 CostTokenType::Const => 1,
107 CostTokenType::Step
108 | CostTokenType::Hole
109 | CostTokenType::RangeCheck
110 | CostTokenType::RangeCheck96 => {
111 panic!("Token type {token_type:?} has no gas cost.")
112 }
113 CostTokenType::Pedersen => 4050,
114 CostTokenType::Poseidon => 491,
115 CostTokenType::Bitwise => 583,
116 CostTokenType::EcOp => 4085,
117 CostTokenType::AddMod => 230,
118 CostTokenType::MulMod => 604,
119 }
120}
121
122#[derive(Debug, Clone)]
124pub enum Arg {
125 Value(Felt252),
126 Array(Vec<Arg>),
127}
128impl Arg {
129 pub fn size(&self) -> usize {
131 match self {
132 Self::Value(_) => 1,
133 Self::Array(_) => 2,
134 }
135 }
136}
137impl From<Felt252> for Arg {
138 fn from(value: Felt252) -> Self {
139 Self::Value(value)
140 }
141}
142
143pub fn build_hints_dict(
145 hints: &[(usize, Vec<Hint>)],
146) -> (HashMap<usize, Vec<HintParams>>, HashMap<String, Hint>) {
147 let mut hints_dict: HashMap<usize, Vec<HintParams>> = HashMap::new();
148 let mut string_to_hint: HashMap<String, Hint> = HashMap::new();
149
150 for (offset, offset_hints) in hints {
151 for hint in offset_hints {
153 string_to_hint.insert(hint.representing_string(), hint.clone());
154 }
155 hints_dict.insert(*offset, offset_hints.iter().map(hint_to_hint_params).collect());
157 }
158 (hints_dict, string_to_hint)
159}
160
161pub struct SierraCasmRunner {
163 builder: RunnableBuilder,
165 starknet_contracts_info: OrderedHashMap<Felt252, ContractInfo>,
167 run_profiler: Option<ProfilingInfoCollectionConfig>,
169}
170impl SierraCasmRunner {
171 pub fn new(
172 sierra_program: cairo_lang_sierra::program::Program,
173 metadata_config: Option<MetadataComputationConfig>,
174 starknet_contracts_info: OrderedHashMap<Felt252, ContractInfo>,
175 run_profiler: Option<ProfilingInfoCollectionConfig>,
176 ) -> Result<Self, RunnerError> {
177 Ok(Self {
179 builder: RunnableBuilder::new(sierra_program, metadata_config)?,
180 starknet_contracts_info,
181 run_profiler,
182 })
183 }
184
185 pub fn run_function_with_starknet_context(
187 &self,
188 func: &Function,
189 args: Vec<Arg>,
190 available_gas: Option<usize>,
191 starknet_state: StarknetState,
192 ) -> Result<RunResultStarknet, RunnerError> {
193 let (assembled_program, builtins) =
194 self.builder.assemble_function_program(func, EntryCodeConfig::testing())?;
195 let (hints_dict, string_to_hint) = build_hints_dict(&assembled_program.hints);
196 let user_args = self.prepare_args(func, available_gas, args)?;
197 let mut hint_processor = CairoHintProcessor {
198 runner: Some(self),
199 user_args,
200 starknet_state,
201 string_to_hint,
202 run_resources: RunResources::default(),
203 syscalls_used_resources: Default::default(),
204 no_temporary_segments: true,
205 markers: Default::default(),
206 panic_traceback: Default::default(),
207 };
208 let RunResult { gas_counter, memory, value, used_resources, profiling_info } = self
209 .run_function(
210 func,
211 &mut hint_processor,
212 hints_dict,
213 assembled_program.bytecode.iter(),
214 builtins,
215 )?;
216 let mut all_used_resources = hint_processor.syscalls_used_resources;
217 all_used_resources.basic_resources += &used_resources;
218 Ok(RunResultStarknet {
219 gas_counter,
220 memory,
221 value,
222 starknet_state: hint_processor.starknet_state,
223 used_resources: all_used_resources,
224 profiling_info,
225 })
226 }
227
228 fn inner_type_from_panic_wrapper(
230 &self,
231 ty: &GenericTypeId,
232 func: &Function,
233 ) -> Option<ConcreteTypeId> {
234 let generic_args = &func
235 .signature
236 .ret_types
237 .iter()
238 .find_map(|rt| {
239 let long_id = self.builder.type_long_id(rt);
240 (long_id.generic_id == *ty).then_some(long_id)
241 })
242 .unwrap()
243 .generic_args;
244
245 if *ty == EnumType::ID
246 && matches!(&generic_args[0], GenericArg::UserType(ut)
247 if ut.debug_name.as_ref().unwrap().starts_with("core::panics::PanicResult::"))
248 {
249 return Some(extract_matches!(&generic_args[1], GenericArg::Type).clone());
250 }
251 None
252 }
253
254 pub fn run_function<'a, Bytecode>(
258 &self,
259 func: &Function,
260 hint_processor: &mut dyn HintProcessor,
261 hints_dict: HashMap<usize, Vec<HintParams>>,
262 bytecode: Bytecode,
263 builtins: Vec<BuiltinName>,
264 ) -> Result<RunResult, RunnerError>
265 where
266 Bytecode: ExactSizeIterator<Item = &'a BigInt> + Clone,
267 {
268 let return_types =
269 self.builder.generic_id_and_size_from_concrete(&func.signature.ret_types);
270 let data_len = bytecode.len();
271 let RunFunctionResult { ap, mut used_resources, memory, relocated_trace } =
272 casm_run::run_function(
273 bytecode,
274 builtins,
275 |vm| initialize_vm(vm, data_len),
276 hint_processor,
277 hints_dict,
278 )?;
279
280 let header_end = relocated_trace.last().unwrap().pc;
283 used_resources.n_steps -=
284 relocated_trace.iter().position(|e| e.pc > header_end).unwrap() - 1;
285 used_resources.n_steps -=
286 relocated_trace.iter().rev().position(|e| e.pc > header_end).unwrap() - 1;
287
288 let (results_data, gas_counter) = self.get_results_data(&return_types, &memory, ap);
289 assert!(results_data.len() <= 1);
290
291 let value = if results_data.is_empty() {
292 RunResultValue::Success(vec![])
294 } else {
295 let (ty, values) = results_data[0].clone();
296 let inner_ty =
297 self.inner_type_from_panic_wrapper(&ty, func).map(|it| self.builder.type_size(&it));
298 Self::handle_main_return_value(inner_ty, values, &memory)
299 };
300
301 let Self { builder, starknet_contracts_info: _, run_profiler } = self;
302
303 let load_offset = header_end + 1;
305
306 let profiling_info = run_profiler.as_ref().map(|config| {
307 ProfilingInfo::from_trace(builder, load_offset, config, &relocated_trace)
308 });
309
310 Ok(RunResult { gas_counter, memory, value, used_resources, profiling_info })
311 }
312
313 fn prepare_args(
315 &self,
316 func: &Function,
317 available_gas: Option<usize>,
318 args: Vec<Arg>,
319 ) -> Result<Vec<Vec<Arg>>, RunnerError> {
320 let mut user_args = vec![];
321 if let Some(gas) = self
322 .requires_gas_builtin(func)
323 .then_some(self.get_initial_available_gas(func, available_gas)?)
324 {
325 user_args.push(vec![Arg::Value(Felt252::from(gas))]);
326 }
327 let mut expected_arguments_size = 0;
328 let actual_args_size = args_size(&args);
329 let mut arg_iter = args.into_iter().enumerate();
330 for (param_index, (_, param_size)) in self
331 .builder
332 .generic_id_and_size_from_concrete(&func.signature.param_types)
333 .into_iter()
334 .filter(|(ty, _)| self.builder.is_user_arg_type(ty))
335 .enumerate()
336 {
337 let mut curr_arg = vec![];
338 let param_size: usize = param_size.into_or_panic();
339 expected_arguments_size += param_size;
340 let mut taken_size = 0;
341 while taken_size < param_size {
342 let Some((arg_index, arg)) = arg_iter.next() else {
343 break;
344 };
345 taken_size += arg.size();
346 if taken_size > param_size {
347 return Err(RunnerError::ArgumentUnaligned { param_index, arg_index });
348 }
349 curr_arg.push(arg);
350 }
351 user_args.push(curr_arg);
352 }
353 if expected_arguments_size != actual_args_size {
354 return Err(RunnerError::ArgumentsSizeMismatch {
355 expected: expected_arguments_size,
356 actual: actual_args_size,
357 });
358 }
359 Ok(user_args)
360 }
361
362 pub fn handle_main_return_value(
364 inner_type_size: Option<i16>,
365 values: Vec<Felt252>,
366 cells: &[Option<Felt252>],
367 ) -> RunResultValue {
368 if let Some(inner_type_size) = inner_type_size {
369 if values[0] == Felt252::from(0) {
371 let inner_ty_size = inner_type_size as usize;
373 let skip_size = values.len() - inner_ty_size;
374 RunResultValue::Success(values.into_iter().skip(skip_size).collect())
375 } else {
376 let err_data_start = values[values.len() - 2].to_usize().unwrap();
378 let err_data_end = values[values.len() - 1].to_usize().unwrap();
379 RunResultValue::Panic(
380 cells[err_data_start..err_data_end]
381 .iter()
382 .cloned()
383 .map(Option::unwrap)
384 .collect(),
385 )
386 }
387 } else {
388 RunResultValue::Success(values)
390 }
391 }
392
393 pub fn get_results_data(
395 &self,
396 return_types: &[(GenericTypeId, i16)],
397 cells: &[Option<Felt252>],
398 mut ap: usize,
399 ) -> (Vec<(GenericTypeId, Vec<Felt252>)>, Option<Felt252>) {
400 let mut results_data = vec![];
401 for (ty, ty_size) in return_types.iter().rev() {
402 let size = *ty_size as usize;
403 let values: Vec<Felt252> =
404 ((ap - size)..ap).map(|index| cells[index].unwrap()).collect();
405 ap -= size;
406 results_data.push((ty.clone(), values));
407 }
408
409 let mut gas_counter = None;
411 results_data.retain_mut(|(ty, values)| {
412 let generic_ty = ty;
413 if *generic_ty == GasBuiltinType::ID {
414 gas_counter = Some(values.remove(0));
415 assert!(values.is_empty());
416 false
417 } else {
418 self.builder.is_user_arg_type(generic_ty)
419 }
420 });
421
422 (results_data, gas_counter)
423 }
424
425 pub fn find_function(&self, name_suffix: &str) -> Result<&Function, RunnerError> {
427 Ok(self.builder.find_function(name_suffix)?)
428 }
429
430 fn requires_gas_builtin(&self, func: &Function) -> bool {
432 func.signature
433 .param_types
434 .iter()
435 .any(|ty| self.builder.type_long_id(ty).generic_id == GasBuiltinType::ID)
436 }
437
438 pub fn get_initial_available_gas(
441 &self,
442 func: &Function,
443 available_gas: Option<usize>,
444 ) -> Result<usize, RunnerError> {
445 let Some(available_gas) = available_gas else {
446 return Ok(0);
447 };
448
449 let Some(required_gas) = self.initial_required_gas(func) else {
453 return Ok(0);
454 };
455
456 available_gas.checked_sub(required_gas).ok_or(RunnerError::NotEnoughGasToCall)
457 }
458
459 pub fn initial_required_gas(&self, func: &Function) -> Option<usize> {
460 let gas_info = &self.builder.metadata().gas_info;
461 require(!gas_info.function_costs.is_empty())?;
462 Some(
463 gas_info.function_costs[&func.id]
464 .iter()
465 .map(|(token_type, val)| val.into_or_panic::<usize>() * token_gas_cost(*token_type))
466 .sum(),
467 )
468 }
469}
470
471#[derive(Debug, Eq, PartialEq, Clone)]
473pub struct ProfilingInfoCollectionConfig {
474 pub max_stack_trace_depth: usize,
476 pub collect_scoped_sierra_statement_weights: bool,
482}
483
484impl ProfilingInfoCollectionConfig {
485 pub fn set_max_stack_trace_depth(&mut self, max_stack_depth: usize) -> &mut Self {
486 self.max_stack_trace_depth = max_stack_depth;
487 self
488 }
489}
490
491impl Default for ProfilingInfoCollectionConfig {
492 fn default() -> Self {
496 Self {
497 max_stack_trace_depth: if let Ok(max) = std::env::var("MAX_STACK_TRACE_DEPTH") {
498 if max.is_empty() {
499 MAX_STACK_TRACE_DEPTH_DEFAULT
500 } else {
501 max.parse::<usize>()
502 .expect("MAX_STACK_TRACE_DEPTH_DEFAULT env var is not numeric")
503 }
504 } else {
505 MAX_STACK_TRACE_DEPTH_DEFAULT
506 },
507 collect_scoped_sierra_statement_weights: false,
508 }
509 }
510}
511
512pub fn initialize_vm(vm: &mut VirtualMachine, data_len: usize) -> Result<(), Box<CairoRunError>> {
515 let builtin_cost_segment = vm.add_memory_segment();
517 for token_type in CostTokenType::iter_precost() {
518 vm.insert_value(
519 (builtin_cost_segment + (token_type.offset_in_builtin_costs() as usize)).unwrap(),
520 Felt252::from(token_gas_cost(*token_type)),
521 )
522 .map_err(|e| Box::new(e.into()))?;
523 }
524 vm.insert_value((vm.get_pc() + data_len).unwrap(), builtin_cost_segment)
527 .map_err(|e| Box::new(e.into()))?;
528 Ok(())
529}
530
531fn args_size(args: &[Arg]) -> usize {
533 args.iter().map(Arg::size).sum()
534}