cairo_annotations/
trace_data.rs

1use crate::felt_deserialize::deserialize as felt_deserialize;
2use camino::Utf8PathBuf;
3use serde::{Deserialize, Serialize};
4use starknet_types_core::felt::Felt;
5use std::collections::HashMap;
6use std::ops::{AddAssign, SubAssign};
7use strum::VariantArray;
8use strum_macros::{Display, EnumString, VariantArray};
9
10#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
11pub struct ClassHash(#[serde(deserialize_with = "felt_deserialize")] pub Felt);
12
13#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
14pub struct ContractAddress(#[serde(deserialize_with = "felt_deserialize")] pub Felt);
15
16#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
17pub struct EntryPointSelector(#[serde(deserialize_with = "felt_deserialize")] pub Felt);
18
19/// Versioned representation of `CallTrace`.
20///
21/// Always prefer using this enum when Serializing/Deserializing instead of inner ones.
22#[derive(Debug, Clone, Serialize, Deserialize)]
23#[serde(untagged)]
24pub enum VersionedCallTrace {
25    V1(CallTraceV1),
26}
27
28/// Tree structure representing trace of a call.
29/// This struct should be serialized and used as an input to cairo-profiler.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct CallTraceV1 {
32    pub entry_point: CallEntryPoint,
33    #[serde(rename = "used_execution_resources")]
34    pub cumulative_resources: ExecutionResources,
35    pub used_l1_resources: L1Resources,
36    pub nested_calls: Vec<CallTraceNode>,
37    pub cairo_execution_info: Option<CairoExecutionInfo>,
38}
39
40/// Struct needed for function level profiling.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct CairoExecutionInfo {
43    /// Path to a file with serialized `ContractClass` or `VersionedProgram`.
44    pub source_sierra_path: Utf8PathBuf,
45    pub casm_level_info: CasmLevelInfo,
46    /// `enable-gas` option from [cairo] section in `Scarb.toml`
47    pub enable_gas: Option<bool>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct CasmLevelInfo {
52    pub run_with_call_header: bool,
53    pub vm_trace: Vec<TraceEntry>,
54    /// Executable program offset information
55    pub program_offset: Option<usize>,
56}
57
58/// Enum representing node of a trace of a call.
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub enum CallTraceNode {
61    EntryPointCall(Box<CallTraceV1>),
62    DeployWithoutConstructor,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct TraceEntry {
67    pub pc: usize,
68    pub ap: usize,
69    pub fp: usize,
70}
71
72type SyscallCounter = HashMap<DeprecatedSyscallSelector, SyscallUsage>;
73
74#[derive(Debug, Default, Clone, Deserialize, Serialize)]
75pub struct SyscallUsage {
76    pub call_count: usize,
77    pub linear_factor: usize,
78}
79
80#[derive(Debug, Default, Clone, Deserialize, Serialize)]
81pub struct ExecutionResources {
82    pub vm_resources: VmExecutionResources,
83    pub gas_consumed: Option<u64>,
84    /// Present for `snforge` >= `0.46.0`.
85    pub syscall_counter: Option<SyscallCounter>,
86}
87
88#[derive(Clone, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
89pub struct VmExecutionResources {
90    pub n_steps: usize,
91    pub n_memory_holes: usize,
92    pub builtin_instance_counter: HashMap<String, usize>,
93}
94
95#[derive(
96    Clone,
97    Copy,
98    Debug,
99    Display,
100    Deserialize,
101    Serialize,
102    Eq,
103    Hash,
104    PartialEq,
105    EnumString,
106    VariantArray,
107)]
108pub enum DeprecatedSyscallSelector {
109    CallContract,
110    DelegateCall,
111    DelegateL1Handler,
112    Deploy,
113    EmitEvent,
114    GetBlockHash,
115    GetBlockNumber,
116    GetBlockTimestamp,
117    GetCallerAddress,
118    GetContractAddress,
119    GetClassHashAt,
120    GetExecutionInfo,
121    GetSequencerAddress,
122    GetTxInfo,
123    GetTxSignature,
124    Keccak,
125    LibraryCall,
126    LibraryCallL1Handler,
127    ReplaceClass,
128    Secp256k1Add,
129    Secp256k1GetPointFromX,
130    Secp256k1GetXy,
131    Secp256k1Mul,
132    Secp256k1New,
133    Secp256r1Add,
134    Secp256r1GetPointFromX,
135    Secp256r1GetXy,
136    Secp256r1Mul,
137    Secp256r1New,
138    SendMessageToL1,
139    StorageRead,
140    StorageWrite,
141    Sha256ProcessBlock,
142    KeccakRound,
143    MetaTxV0,
144}
145
146impl DeprecatedSyscallSelector {
147    #[must_use]
148    pub fn all() -> &'static [Self] {
149        Self::VARIANTS
150    }
151}
152
153#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
154pub struct CallEntryPoint {
155    pub class_hash: Option<ClassHash>,
156    pub entry_point_type: EntryPointType,
157    pub entry_point_selector: EntryPointSelector,
158    pub contract_address: ContractAddress,
159    pub call_type: CallType,
160
161    /// Contract name to display instead of contract address
162    pub contract_name: Option<String>,
163    /// Function name to display instead of entry point selector
164    pub function_name: Option<String>,
165    /// Calldata length to use for syscall cost estimation
166    /// Present for `snforge` >= `0.48.0`.
167    pub calldata_len: Option<usize>,
168    /// Events information to use for l2 gas cost estimation
169    /// Present for `snforge` >= `0.49.0`
170    pub events_summary: Option<Vec<SummedUpEvent>>,
171    /// Signature length to use for l2 gas cost estimation
172    /// Present for `snforge` >= `0.49.0`
173    pub signature_len: Option<usize>,
174}
175
176#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Deserialize, Serialize)]
177pub enum CallType {
178    #[default]
179    Call = 0,
180    Delegate = 1,
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
184pub enum EntryPointType {
185    #[serde(rename = "CONSTRUCTOR")]
186    Constructor,
187    #[serde(rename = "EXTERNAL")]
188    #[default]
189    External,
190    #[serde(rename = "L1_HANDLER")]
191    L1Handler,
192}
193
194#[derive(Clone, Default, Debug, Serialize, Deserialize)]
195pub struct L1Resources {
196    pub l2_l1_message_sizes: Vec<usize>,
197}
198
199impl AddAssign<&VmExecutionResources> for VmExecutionResources {
200    fn add_assign(&mut self, other: &VmExecutionResources) {
201        self.n_steps += other.n_steps;
202        self.n_memory_holes += other.n_memory_holes;
203
204        for (key, value) in &other.builtin_instance_counter {
205            *self
206                .builtin_instance_counter
207                .entry(key.clone())
208                .or_default() += *value;
209        }
210    }
211}
212
213impl SubAssign<&VmExecutionResources> for VmExecutionResources {
214    fn sub_assign(&mut self, other: &VmExecutionResources) {
215        self.n_steps = self.n_steps.saturating_sub(other.n_steps);
216        self.n_memory_holes = self.n_memory_holes.saturating_sub(other.n_memory_holes);
217
218        for (key, value) in &other.builtin_instance_counter {
219            if let Some(self_value) = self.builtin_instance_counter.get_mut(key) {
220                *self_value = self_value.saturating_sub(*value);
221            }
222        }
223        // Remove entries where the value is 0
224        self.builtin_instance_counter.retain(|_, value| *value > 0);
225    }
226}
227
228impl AddAssign<&ExecutionResources> for ExecutionResources {
229    fn add_assign(&mut self, other: &ExecutionResources) {
230        self.vm_resources += &other.vm_resources;
231        self.gas_consumed = match (self.gas_consumed, other.gas_consumed) {
232            (Some(self_gas), Some(other_gas)) => Some(self_gas + other_gas),
233            (Some(self_gas), None) => Some(self_gas),
234            (None, Some(other_gas)) => Some(other_gas),
235            (None, None) => None,
236        };
237
238        if let Some(other_counter) = &other.syscall_counter {
239            let self_counter = self.syscall_counter.get_or_insert_with(HashMap::new);
240            for (&selector, usage) in other_counter {
241                self_counter
242                    .entry(selector)
243                    .and_modify(|existing| {
244                        existing.call_count += usage.call_count;
245                        existing.linear_factor += usage.linear_factor;
246                    })
247                    .or_insert_with(|| usage.clone());
248            }
249        }
250    }
251}
252
253impl SubAssign<&ExecutionResources> for ExecutionResources {
254    fn sub_assign(&mut self, other: &ExecutionResources) {
255        self.vm_resources -= &other.vm_resources;
256
257        if let Some(other_gas) = other.gas_consumed
258            && let Some(self_gas) = &mut self.gas_consumed
259        {
260            *self_gas = self_gas.saturating_sub(other_gas);
261        }
262
263        if let Some(self_counter) = &mut self.syscall_counter
264            && let Some(other_counter) = &other.syscall_counter
265        {
266            for (selector, usage) in other_counter {
267                if let Some(self_usage) = self_counter.get_mut(selector) {
268                    self_usage.call_count = self_usage.call_count.saturating_sub(usage.call_count);
269                    self_usage.linear_factor =
270                        self_usage.linear_factor.saturating_sub(usage.linear_factor);
271                }
272            }
273            // Remove entries where both values are 0
274            self_counter.retain(|_, usage| usage.call_count > 0 || usage.linear_factor > 0);
275        }
276    }
277}
278
279#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
280pub struct SummedUpEvent {
281    pub keys_len: usize,
282    pub data_len: usize,
283}