cairo_lang_starknet_classes/
casm_contract_class.rs

1use std::cmp::Ordering;
2use std::sync::LazyLock;
3
4use cairo_lang_casm::assembler::AssembledCairoProgram;
5use cairo_lang_casm::hints::{Hint, PythonicHint};
6use cairo_lang_sierra::extensions::NamedType;
7use cairo_lang_sierra::extensions::array::ArrayType;
8use cairo_lang_sierra::extensions::bitwise::BitwiseType;
9use cairo_lang_sierra::extensions::circuit::{AddModType, MulModType};
10use cairo_lang_sierra::extensions::ec::EcOpType;
11use cairo_lang_sierra::extensions::enm::EnumType;
12use cairo_lang_sierra::extensions::felt252::Felt252Type;
13use cairo_lang_sierra::extensions::gas::{CostTokenMap, CostTokenType, GasBuiltinType};
14use cairo_lang_sierra::extensions::pedersen::PedersenType;
15use cairo_lang_sierra::extensions::poseidon::PoseidonType;
16use cairo_lang_sierra::extensions::range_check::{RangeCheck96Type, RangeCheckType};
17use cairo_lang_sierra::extensions::segment_arena::SegmentArenaType;
18use cairo_lang_sierra::extensions::snapshot::SnapshotType;
19use cairo_lang_sierra::extensions::starknet::syscalls::SystemType;
20use cairo_lang_sierra::extensions::structure::StructType;
21use cairo_lang_sierra::ids::{ConcreteTypeId, GenericTypeId};
22use cairo_lang_sierra::program::{ConcreteTypeLongId, GenericArg, TypeDeclaration};
23use cairo_lang_sierra_to_casm::compiler::{
24    CairoProgramDebugInfo, CompilationError, SierraToCasmConfig,
25};
26use cairo_lang_sierra_to_casm::metadata::{
27    MetadataComputationConfig, MetadataError, calc_metadata,
28};
29use cairo_lang_sierra_type_size::ProgramRegistryInfo;
30use cairo_lang_utils::bigint::{BigUintAsHex, deserialize_big_uint, serialize_big_uint};
31use cairo_lang_utils::require;
32use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
33use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
34use convert_case::{Case, Casing};
35use itertools::{Itertools, chain};
36use num_bigint::BigUint;
37use num_integer::Integer;
38use num_traits::Signed;
39use serde::{Deserialize, Serialize};
40use starknet_types_core::felt::Felt as Felt252;
41use starknet_types_core::hash::{Poseidon, StarkHash};
42use thiserror::Error;
43
44use crate::allowed_libfuncs::AllowedLibfuncsError;
45use crate::compiler_version::{
46    CONTRACT_SEGMENTATION_MINOR_VERSION, VersionId, current_compiler_version_id,
47    current_sierra_version_id,
48};
49use crate::contract_class::{ContractClass, ContractEntryPoint};
50use crate::contract_segmentation::{
51    NestedIntList, SegmentationError, compute_bytecode_segment_lengths,
52};
53use crate::felt252_serde::{Felt252SerdeError, sierra_from_felt252s};
54use crate::keccak::starknet_keccak;
55
56#[cfg(test)]
57#[path = "casm_contract_class_test.rs"]
58mod test;
59
60/// The expected gas cost of an entrypoint.
61pub const ENTRY_POINT_COST: i32 = 10000;
62
63static CONSTRUCTOR_ENTRY_POINT_SELECTOR: LazyLock<BigUint> =
64    LazyLock::new(|| starknet_keccak(b"constructor"));
65
66#[derive(Error, Debug, Eq, PartialEq)]
67pub enum StarknetSierraCompilationError {
68    #[error(transparent)]
69    CompilationError(#[from] Box<CompilationError>),
70    #[error(transparent)]
71    Felt252SerdeError(#[from] Felt252SerdeError),
72    #[error(transparent)]
73    MetadataError(#[from] MetadataError),
74    #[error(transparent)]
75    AllowedLibfuncsError(#[from] AllowedLibfuncsError),
76    #[error(transparent)]
77    SegmentationError(#[from] SegmentationError),
78    #[error("Invalid entry point.")]
79    EntryPointError,
80    #[error("Missing arguments in the entry point.")]
81    InvalidEntryPointSignatureMissingArgs,
82    #[error("Invalid entry point signature.")]
83    InvalidEntryPointSignature,
84    #[error("Invalid constructor entry point.")]
85    InvalidConstructorEntryPoint,
86    #[error("{0} is not a supported builtin type.")]
87    InvalidBuiltinType(ConcreteTypeId),
88    #[error("Invalid entry point signature - builtins are not in the expected order.")]
89    InvalidEntryPointSignatureWrongBuiltinsOrder,
90    #[error("Entry points not sorted by selectors.")]
91    EntryPointsOutOfOrder,
92    #[error("Duplicate entry point selector {selector}.")]
93    DuplicateEntryPointSelector { selector: BigUint },
94    #[error("Duplicate entry point function index {index}.")]
95    DuplicateEntryPointSierraFunction { index: usize },
96    #[error("Out of range value in serialization.")]
97    ValueOutOfRange,
98    #[error(
99        "Cannot compile Sierra version {version_in_contract} with the current compiler (sierra \
100         version: {version_of_compiler})"
101    )]
102    UnsupportedSierraVersion { version_in_contract: VersionId, version_of_compiler: VersionId },
103}
104
105fn skip_if_none<T>(opt_field: &Option<T>) -> bool {
106    opt_field.is_none()
107}
108
109/// Represents a contract in the Starknet network.
110#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
111pub struct CasmContractClass {
112    #[serde(serialize_with = "serialize_big_uint", deserialize_with = "deserialize_big_uint")]
113    pub prime: BigUint,
114    pub compiler_version: String,
115    pub bytecode: Vec<BigUintAsHex>,
116    #[serde(skip_serializing_if = "skip_if_none")]
117    pub bytecode_segment_lengths: Option<NestedIntList>,
118    pub hints: Vec<(usize, Vec<Hint>)>,
119
120    // Optional Pythonic hints in a format that can be executed by the Python VM.
121    #[serde(skip_serializing_if = "skip_if_none")]
122    pub pythonic_hints: Option<Vec<(usize, Vec<String>)>>,
123    pub entry_points_by_type: CasmContractEntryPoints,
124}
125impl CasmContractClass {
126    /// Returns the Poseidon hash value for the compiled contract class.
127    pub fn compiled_class_hash(&self) -> Felt252 {
128        self.compiled_class_hash_inner::<Poseidon>()
129    }
130
131    /// Returns the lengths of the bytecode segments.
132    ///
133    /// If the length field is missing, the entire bytecode is considered a single segment.
134    pub fn get_bytecode_segment_lengths(&self) -> NestedIntList {
135        self.bytecode_segment_lengths.clone().unwrap_or(NestedIntList::Leaf(self.bytecode.len()))
136    }
137
138    /// Returns the hash for a set of entry points.
139    fn entry_points_hash<H: StarkHash>(&self, entry_points: &[CasmContractEntryPoint]) -> Felt252 {
140        let mut entry_point_hash_elements = vec![];
141        for entry_point in entry_points {
142            entry_point_hash_elements.push(Felt252::from(&entry_point.selector));
143            entry_point_hash_elements.push(Felt252::from(entry_point.offset));
144            entry_point_hash_elements.push(H::hash_array(
145                &entry_point
146                    .builtins
147                    .iter()
148                    .map(|builtin| Felt252::from_bytes_be_slice(builtin.as_bytes()))
149                    .collect_vec(),
150            ));
151        }
152        H::hash_array(&entry_point_hash_elements)
153    }
154
155    /// Returns the bytecode hash.
156    fn compute_bytecode_hash<H: StarkHash>(&self) -> Felt252 {
157        let mut bytecode_iter = self.bytecode.iter().map(|big_uint| Felt252::from(&big_uint.value));
158
159        let (len, bytecode_hash) =
160            bytecode_hash_node::<H>(&mut bytecode_iter, &self.get_bytecode_segment_lengths());
161        assert_eq!(len, self.bytecode.len());
162
163        bytecode_hash
164    }
165
166    /// Generic over `H: StarkHash`; computes the combined Felt252 hash of all class components.
167    /// Uses entry point hashes (external, L1 handler, constructor) and bytecode to produce a single
168    /// hash.
169    fn compiled_class_hash_inner<H: StarkHash>(&self) -> Felt252 {
170        // Compute hashes on each component separately.
171        let external_funcs_hash = self.entry_points_hash::<H>(&self.entry_points_by_type.external);
172        let l1_handlers_hash = self.entry_points_hash::<H>(&self.entry_points_by_type.l1_handler);
173        let constructors_hash = self.entry_points_hash::<H>(&self.entry_points_by_type.constructor);
174        let bytecode_hash = self.compute_bytecode_hash::<H>();
175
176        // Compute total hash by hashing each component on top of the previous one.
177        H::hash_array(&[
178            Felt252::from_bytes_be_slice(b"COMPILED_CLASS_V1"),
179            external_funcs_hash,
180            l1_handlers_hash,
181            constructors_hash,
182            bytecode_hash,
183        ])
184    }
185}
186
187/// Computes the hash of a bytecode segment. See the documentation of `bytecode_hash_node` in
188/// the Starknet OS.
189///
190/// Returns the length of the processed segment and its hash.
191fn bytecode_hash_node<H: StarkHash>(
192    iter: &mut impl Iterator<Item = Felt252>,
193    node: &NestedIntList,
194) -> (usize, Felt252) {
195    match node {
196        NestedIntList::Leaf(len) => {
197            let data = &iter.take(*len).collect_vec();
198            assert_eq!(data.len(), *len);
199            (*len, H::hash_array(data))
200        }
201        NestedIntList::Node(nodes) => {
202            // Compute `1 + poseidon(len0, hash0, len1, hash1, ...)`.
203            let inner_nodes =
204                nodes.iter().map(|node| bytecode_hash_node::<H>(iter, node)).collect_vec();
205            let hash = H::hash_array(
206                &inner_nodes.iter().flat_map(|(len, hash)| [(*len).into(), *hash]).collect_vec(),
207            ) + 1;
208            (inner_nodes.iter().map(|(len, _)| len).sum(), hash)
209        }
210    }
211}
212
213/// Context for resolving types.
214pub struct TypeResolver<'a> {
215    type_decl: &'a [TypeDeclaration],
216}
217
218impl TypeResolver<'_> {
219    fn get_long_id(&self, type_id: &ConcreteTypeId) -> &ConcreteTypeLongId {
220        &self.type_decl[type_id.id as usize].long_id
221    }
222
223    fn get_generic_id(&self, type_id: &ConcreteTypeId) -> &GenericTypeId {
224        &self.get_long_id(type_id).generic_id
225    }
226
227    fn is_felt252_array_snapshot(&self, ty: &ConcreteTypeId) -> bool {
228        let long_id = self.get_long_id(ty);
229        if long_id.generic_id != SnapshotType::id() {
230            return false;
231        }
232
233        let [GenericArg::Type(inner_ty)] = long_id.generic_args.as_slice() else {
234            return false;
235        };
236
237        self.is_felt252_array(inner_ty)
238    }
239
240    fn is_felt252_array(&self, ty: &ConcreteTypeId) -> bool {
241        let long_id = self.get_long_id(ty);
242        if long_id.generic_id != ArrayType::id() {
243            return false;
244        }
245
246        let [GenericArg::Type(element_ty)] = long_id.generic_args.as_slice() else {
247            return false;
248        };
249
250        *self.get_generic_id(element_ty) == Felt252Type::id()
251    }
252
253    fn is_felt252_span(&self, ty: &ConcreteTypeId) -> bool {
254        let long_id = self.get_long_id(ty);
255        if long_id.generic_id != StructType::ID {
256            return false;
257        }
258
259        let [GenericArg::UserType(_), GenericArg::Type(element_ty)] =
260            long_id.generic_args.as_slice()
261        else {
262            return false;
263        };
264
265        self.is_felt252_array_snapshot(element_ty)
266    }
267
268    fn is_valid_entry_point_return_type(&self, ty: &ConcreteTypeId) -> bool {
269        // The return type must be an enum with two variants: (result, error).
270        let Some((result_tuple_ty, err_ty)) = self.extract_result_ty(ty) else {
271            return false;
272        };
273
274        // The result variant must be a tuple with one element: Span<felt252>;
275        let Some(result_ty) = self.extract_struct1(result_tuple_ty) else {
276            return false;
277        };
278        if !self.is_felt252_span(result_ty) {
279            return false;
280        }
281
282        // If the error type is Array<felt252>, it's a good error type, using the old panic
283        // mechanism.
284        if self.is_felt252_array(err_ty) {
285            return true;
286        }
287
288        // Otherwise, the error type must be a struct with two fields: (panic, data)
289        let Some((_panic_ty, err_data_ty)) = self.extract_struct2(err_ty) else {
290            return false;
291        };
292
293        // The data field must be a Span<felt252>.
294        self.is_felt252_array(err_data_ty)
295    }
296
297    /// Extracts types `TOk`, `TErr` from the type `Result<TOk, TErr>`.
298    fn extract_result_ty(&self, ty: &ConcreteTypeId) -> Option<(&ConcreteTypeId, &ConcreteTypeId)> {
299        let long_id = self.get_long_id(ty);
300        require(long_id.generic_id == EnumType::id())?;
301        let [GenericArg::UserType(_), GenericArg::Type(result_tuple_ty), GenericArg::Type(err_ty)] =
302            long_id.generic_args.as_slice()
303        else {
304            return None;
305        };
306        Some((result_tuple_ty, err_ty))
307    }
308
309    /// Extracts type `T` from the tuple type `(T,)`.
310    fn extract_struct1(&self, ty: &ConcreteTypeId) -> Option<&ConcreteTypeId> {
311        let long_id = self.get_long_id(ty);
312        require(long_id.generic_id == StructType::id())?;
313        let [GenericArg::UserType(_), GenericArg::Type(ty0)] = long_id.generic_args.as_slice()
314        else {
315            return None;
316        };
317        Some(ty0)
318    }
319
320    /// Extracts types `T0`, `T1` from the tuple type `(T0, T1)`.
321    fn extract_struct2(&self, ty: &ConcreteTypeId) -> Option<(&ConcreteTypeId, &ConcreteTypeId)> {
322        let long_id = self.get_long_id(ty);
323        require(long_id.generic_id == StructType::id())?;
324        let [GenericArg::UserType(_), GenericArg::Type(ty0), GenericArg::Type(ty1)] =
325            long_id.generic_args.as_slice()
326        else {
327            return None;
328        };
329        Some((ty0, ty1))
330    }
331}
332
333impl CasmContractClass {
334    pub fn from_contract_class(
335        contract_class: ContractClass,
336        add_pythonic_hints: bool,
337        max_bytecode_size: usize,
338    ) -> Result<Self, StarknetSierraCompilationError> {
339        Ok(Self::from_contract_class_with_debug_info(
340            contract_class,
341            add_pythonic_hints,
342            max_bytecode_size,
343        )?
344        .0)
345    }
346
347    pub fn from_contract_class_with_debug_info(
348        contract_class: ContractClass,
349        add_pythonic_hints: bool,
350        max_bytecode_size: usize,
351    ) -> Result<(Self, CairoProgramDebugInfo), StarknetSierraCompilationError> {
352        let prime = Felt252::prime();
353        for felt252 in &contract_class.sierra_program {
354            if felt252.value >= prime {
355                return Err(StarknetSierraCompilationError::ValueOutOfRange);
356            }
357        }
358
359        let (sierra_version, _, program) = sierra_from_felt252s(&contract_class.sierra_program)?;
360        let current_sierra_version = current_sierra_version_id();
361        if !(sierra_version.major == current_sierra_version.major
362            && sierra_version.minor <= current_sierra_version.minor)
363        {
364            return Err(StarknetSierraCompilationError::UnsupportedSierraVersion {
365                version_in_contract: sierra_version,
366                version_of_compiler: current_sierra_version,
367            });
368        }
369
370        match &contract_class.entry_points_by_type.constructor.as_slice() {
371            [] => {}
372            [ContractEntryPoint { selector, .. }]
373                if selector == &*CONSTRUCTOR_ENTRY_POINT_SELECTOR => {}
374            _ => {
375                return Err(StarknetSierraCompilationError::InvalidConstructorEntryPoint);
376            }
377        };
378
379        for entry_points in [
380            &contract_class.entry_points_by_type.constructor,
381            &contract_class.entry_points_by_type.external,
382            &contract_class.entry_points_by_type.l1_handler,
383        ] {
384            for (prev, next) in entry_points.iter().tuple_windows() {
385                match prev.selector.cmp(&next.selector) {
386                    Ordering::Less => {}
387                    Ordering::Equal => {
388                        return Err(StarknetSierraCompilationError::DuplicateEntryPointSelector {
389                            selector: prev.selector.clone(),
390                        });
391                    }
392                    Ordering::Greater => {
393                        return Err(StarknetSierraCompilationError::EntryPointsOutOfOrder);
394                    }
395                }
396            }
397        }
398
399        let entrypoint_function_indices = chain!(
400            &contract_class.entry_points_by_type.constructor,
401            &contract_class.entry_points_by_type.external,
402            &contract_class.entry_points_by_type.l1_handler,
403        )
404        .map(|entrypoint| entrypoint.function_idx);
405        // Count the number of times each function is used as an entry point.
406        let mut function_idx_usages = UnorderedHashMap::<usize, usize>::default();
407        for index in entrypoint_function_indices.clone() {
408            let usages = function_idx_usages.entry(index).or_default();
409            *usages += 1;
410            const MAX_SIERRA_FUNCTION_USAGES: usize = 2;
411            if *usages > MAX_SIERRA_FUNCTION_USAGES {
412                return Err(StarknetSierraCompilationError::DuplicateEntryPointSierraFunction {
413                    index,
414                });
415            }
416        }
417        let entrypoint_ids = entrypoint_function_indices.map(|idx| program.funcs[idx].id.clone());
418        // TODO(lior): Remove this assert and condition once the equation solver is removed in major
419        //   version 2.
420        assert_eq!(sierra_version.major, 1);
421        let no_eq_solver = sierra_version.minor >= 4;
422        let metadata_computation_config = MetadataComputationConfig {
423            function_set_costs: entrypoint_ids
424                .map(|id| (id, CostTokenMap::from_iter([(CostTokenType::Const, ENTRY_POINT_COST)])))
425                .collect(),
426            linear_gas_solver: no_eq_solver,
427            linear_ap_change_solver: no_eq_solver,
428            skip_non_linear_solver_comparisons: false,
429            compute_runtime_costs: false,
430        };
431        let program_info = ProgramRegistryInfo::new(&program).map_err(|err| {
432            StarknetSierraCompilationError::CompilationError(Box::new(
433                CompilationError::ProgramRegistryError(err),
434            ))
435        })?;
436        let metadata = calc_metadata(&program, &program_info, metadata_computation_config)?;
437        let cairo_program = cairo_lang_sierra_to_casm::compiler::compile(
438            &program,
439            &program_info,
440            &metadata,
441            SierraToCasmConfig { gas_usage_check: true, max_bytecode_size },
442        )?;
443
444        let AssembledCairoProgram { bytecode, hints } = cairo_program.assemble();
445        let bytecode = bytecode
446            .iter()
447            .map(|big_int| {
448                let (_q, reminder) = big_int.magnitude().div_rem(&prime);
449                BigUintAsHex {
450                    value: if big_int.is_negative() { &prime - reminder } else { reminder },
451                }
452            })
453            .collect_vec();
454
455        let bytecode_segment_lengths =
456            if sierra_version.minor >= CONTRACT_SEGMENTATION_MINOR_VERSION {
457                Some(compute_bytecode_segment_lengths(&program, &cairo_program, bytecode.len())?)
458            } else {
459                None
460            };
461
462        let builtin_types = UnorderedHashSet::<GenericTypeId>::from_iter([
463            RangeCheckType::id(),
464            BitwiseType::id(),
465            PedersenType::id(),
466            EcOpType::id(),
467            PoseidonType::id(),
468            SegmentArenaType::id(),
469            GasBuiltinType::id(),
470            SystemType::id(),
471            RangeCheck96Type::id(),
472            AddModType::id(),
473            MulModType::id(),
474        ]);
475
476        let as_casm_entry_point = |contract_entry_point: ContractEntryPoint| {
477            let Some(function) = program.funcs.get(contract_entry_point.function_idx) else {
478                return Err(StarknetSierraCompilationError::EntryPointError);
479            };
480            let statement_id = function.entry_point;
481
482            // The expected return types are [builtins.., gas_builtin, system, PanicResult].
483            let (panic_result, output_builtins) = function
484                .signature
485                .ret_types
486                .split_last()
487                .ok_or(StarknetSierraCompilationError::InvalidEntryPointSignature)?;
488            let (builtins, [gas_ty, system_ty]) = output_builtins
489                .split_last_chunk()
490                .ok_or(StarknetSierraCompilationError::InvalidEntryPointSignature)?;
491            let (input_span, input_builtins) = function
492                .signature
493                .param_types
494                .split_last()
495                .ok_or(StarknetSierraCompilationError::InvalidEntryPointSignatureMissingArgs)?;
496            require(input_builtins == output_builtins)
497                .ok_or(StarknetSierraCompilationError::InvalidEntryPointSignature)?;
498
499            let type_resolver = TypeResolver { type_decl: &program.type_declarations };
500            require(type_resolver.is_felt252_span(input_span))
501                .ok_or(StarknetSierraCompilationError::InvalidEntryPointSignature)?;
502
503            require(type_resolver.is_valid_entry_point_return_type(panic_result))
504                .ok_or(StarknetSierraCompilationError::InvalidEntryPointSignature)?;
505
506            for type_id in input_builtins {
507                if !builtin_types.contains(type_resolver.get_generic_id(type_id)) {
508                    return Err(StarknetSierraCompilationError::InvalidBuiltinType(
509                        type_id.clone(),
510                    ));
511                }
512            }
513
514            // Check that the last builtins are gas and system.
515            if *type_resolver.get_generic_id(system_ty) != SystemType::id()
516                || *type_resolver.get_generic_id(gas_ty) != GasBuiltinType::id()
517            {
518                return Err(
519                    StarknetSierraCompilationError::InvalidEntryPointSignatureWrongBuiltinsOrder,
520                );
521            }
522
523            let builtins = builtins
524                .iter()
525                .map(|type_id| match type_resolver.get_generic_id(type_id).0.as_str() {
526                    "RangeCheck96" => "range_check96".to_string(),
527                    name => name.to_case(Case::Snake),
528                })
529                .collect_vec();
530
531            let code_offset = cairo_program
532                .debug_info
533                .sierra_statement_info
534                .get(statement_id.0)
535                .ok_or(StarknetSierraCompilationError::EntryPointError)?
536                .start_offset;
537            assert_eq!(
538                metadata.gas_info.function_costs[&function.id],
539                CostTokenMap::from_iter([(CostTokenType::Const, ENTRY_POINT_COST as i64)]),
540                "Unexpected entry point cost."
541            );
542            Ok::<CasmContractEntryPoint, StarknetSierraCompilationError>(CasmContractEntryPoint {
543                selector: contract_entry_point.selector,
544                offset: code_offset,
545                builtins,
546            })
547        };
548
549        let as_casm_entry_points = |contract_entry_points: Vec<ContractEntryPoint>| {
550            let mut entry_points = vec![];
551            for contract_entry_point in contract_entry_points {
552                entry_points.push(as_casm_entry_point(contract_entry_point)?);
553            }
554            Ok::<Vec<CasmContractEntryPoint>, StarknetSierraCompilationError>(entry_points)
555        };
556
557        let pythonic_hints = if add_pythonic_hints {
558            Some(
559                hints
560                    .iter()
561                    .map(|(pc, hints)| {
562                        (*pc, hints.iter().map(|hint| hint.get_pythonic_hint()).collect_vec())
563                    })
564                    .collect_vec(),
565            )
566        } else {
567            None
568        };
569
570        let compiler_version = current_compiler_version_id().to_string();
571        let casm_contract_class = Self {
572            prime,
573            compiler_version,
574            bytecode,
575            bytecode_segment_lengths,
576            hints,
577            pythonic_hints,
578            entry_points_by_type: CasmContractEntryPoints {
579                external: as_casm_entry_points(contract_class.entry_points_by_type.external)?,
580                l1_handler: as_casm_entry_points(contract_class.entry_points_by_type.l1_handler)?,
581                constructor: as_casm_entry_points(contract_class.entry_points_by_type.constructor)?,
582            },
583        };
584
585        Ok((casm_contract_class, cairo_program.debug_info))
586    }
587}
588
589#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
590pub struct CasmContractEntryPoint {
591    /// A field element that encodes the signature of the called function.
592    #[serde(serialize_with = "serialize_big_uint", deserialize_with = "deserialize_big_uint")]
593    pub selector: BigUint,
594    /// The offset of the instruction that should be called within the contract bytecode.
595    pub offset: usize,
596    // list of builtins.
597    pub builtins: Vec<String>,
598}
599
600#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)]
601pub struct CasmContractEntryPoints {
602    #[serde(rename = "EXTERNAL")]
603    pub external: Vec<CasmContractEntryPoint>,
604    #[serde(rename = "L1_HANDLER")]
605    pub l1_handler: Vec<CasmContractEntryPoint>,
606    #[serde(rename = "CONSTRUCTOR")]
607    pub constructor: Vec<CasmContractEntryPoint>,
608}