cairo_lang_starknet/
abi.rs

1use std::collections::{HashMap, HashSet};
2
3use cairo_lang_defs::ids::{
4    FunctionWithBodyId, ImplAliasId, ImplDefId, LanguageElementId, ModuleId, ModuleItemId,
5    NamedLanguageElementId, SubmoduleId, TopLevelLanguageElementId, TraitFunctionId, TraitId,
6};
7use cairo_lang_diagnostics::{DiagnosticAdded, Maybe};
8use cairo_lang_semantic::corelib::core_submodule;
9use cairo_lang_semantic::db::SemanticGroup;
10use cairo_lang_semantic::items::attribute::SemanticQueryAttrs;
11use cairo_lang_semantic::items::enm::SemanticEnumEx;
12use cairo_lang_semantic::items::imp::{ImplLongId, ImplLookupContext};
13use cairo_lang_semantic::types::{ConcreteEnumLongId, ConcreteStructLongId, get_impl_at_context};
14use cairo_lang_semantic::{
15    ConcreteTraitLongId, ConcreteTypeId, GenericArgumentId, GenericParam, Mutability, Signature,
16    TypeId, TypeLongId,
17};
18use cairo_lang_starknet_classes::abi::{
19    Constructor, Contract, Enum, EnumVariant, Event, EventField, EventFieldKind, EventKind,
20    Function, Imp, Input, Interface, Item, L1Handler, Output, StateMutability, Struct,
21    StructMember,
22};
23use cairo_lang_syntax::node::helpers::QueryAttrs;
24use cairo_lang_syntax::node::ids::SyntaxStablePtrId;
25use cairo_lang_syntax::node::{Terminal, TypedStablePtr, TypedSyntaxNode, ast};
26use cairo_lang_utils::ordered_hash_set::OrderedHashSet;
27use cairo_lang_utils::{Intern, LookupIntern, require, try_extract_matches};
28use itertools::zip_eq;
29use smol_str::SmolStr;
30use thiserror::Error;
31
32use crate::plugin::aux_data::StarkNetEventAuxData;
33use crate::plugin::consts::{
34    ABI_ATTR, ABI_ATTR_EMBED_V0_ARG, ABI_ATTR_PER_ITEM_ARG, ACCOUNT_CONTRACT_ENTRY_POINT_SELECTORS,
35    CONSTRUCTOR_ATTR, CONTRACT_ATTR, CONTRACT_ATTR_ACCOUNT_ARG, CONTRACT_STATE_NAME,
36    EMBEDDABLE_ATTR, EVENT_ATTR, EVENT_TYPE_NAME, EXTERNAL_ATTR, FLAT_ATTR, INTERFACE_ATTR,
37    L1_HANDLER_ATTR, VALIDATE_DEPLOY_ENTRY_POINT_SELECTOR,
38};
39use crate::plugin::events::EventData;
40
41#[cfg(test)]
42#[path = "abi_test.rs"]
43mod test;
44
45/// Event information.
46enum EventInfo {
47    /// The event is a struct.
48    Struct,
49    /// The event is an enum, contains its set of selectors.
50    Enum(HashSet<String>),
51}
52
53/// The information of an entrypoint.
54struct EntryPointInfo {
55    /// The source of the entry point.
56    source: Source,
57    /// The signature of the entry point.
58    inputs: Vec<Input>,
59}
60
61#[derive(Clone, Debug, Default)]
62/// Configuration for the abi builder.
63pub struct BuilderConfig {
64    /// Whether to run account contract validations.
65    pub account_contract_validations: bool,
66}
67
68pub struct AbiBuilder<'a> {
69    /// The db.
70    db: &'a dyn SemanticGroup,
71    /// The builder configuration.
72    config: BuilderConfig,
73
74    // TODO(spapini): Add storage variables.
75    /// The constructed ABI.
76    abi_items: OrderedHashSet<Item>,
77
78    /// List of type that were included abi.
79    /// Used to avoid redundancy.
80    types: HashSet<TypeId>,
81
82    /// A map of events that were included in the abi to their info.
83    /// Used to avoid redundancy, as well as preventing enum events from repeating selectors.
84    event_info: HashMap<TypeId, EventInfo>,
85
86    /// List of entry point names that were included in the abi.
87    /// Used to avoid duplication.
88    entry_points: HashMap<String, EntryPointInfo>,
89
90    /// The constructor for the contract.
91    ctor: Option<EntryPointInfo>,
92
93    /// Accumulated errors.
94    errors: Vec<ABIError>,
95}
96impl<'a> AbiBuilder<'a> {
97    /// Creates an `AbiBuilder` from a Starknet contract module.
98    pub fn from_submodule(
99        db: &'a dyn SemanticGroup,
100        submodule_id: SubmoduleId,
101        config: BuilderConfig,
102    ) -> Maybe<Self> {
103        let mut builder = Self {
104            db,
105            config,
106            abi_items: Default::default(),
107            types: HashSet::new(),
108            event_info: HashMap::new(),
109            entry_points: HashMap::new(),
110            ctor: None,
111            errors: Vec::new(),
112        };
113        builder.process_submodule_contract(submodule_id)?;
114        builder.account_contract_validations(submodule_id)?;
115        Ok(builder)
116    }
117
118    /// Returns the finalized ABI.
119    pub fn finalize(self) -> Result<Contract, ABIError> {
120        if let Some(err) = self.errors.into_iter().next() {
121            Err(err)
122        } else {
123            Ok(Contract::from_items(self.abi_items))
124        }
125    }
126
127    /// Returns the errors accumulated by the builder.
128    pub fn errors(&self) -> &[ABIError] {
129        &self.errors
130    }
131
132    /// Runs account contract validations if required.
133    fn account_contract_validations(&mut self, submodule_id: SubmoduleId) -> Maybe<()> {
134        if !self.config.account_contract_validations {
135            return Ok(());
136        }
137        let attrs = submodule_id.query_attr(self.db, CONTRACT_ATTR)?;
138        let mut is_account_contract = false;
139        for attr in attrs {
140            if attr.is_single_unnamed_arg(self.db.upcast(), CONTRACT_ATTR_ACCOUNT_ARG) {
141                is_account_contract = true;
142            } else if !attr.args.is_empty() {
143                self.errors.push(ABIError::IllegalContractAttrArgs);
144                return Ok(());
145            }
146        }
147        if is_account_contract {
148            for selector in ACCOUNT_CONTRACT_ENTRY_POINT_SELECTORS {
149                if !self.entry_points.contains_key(*selector) {
150                    self.errors.push(ABIError::EntryPointMissingForAccountContract {
151                        selector: selector.to_string(),
152                    });
153                }
154            }
155            if let Some(validate_deploy) =
156                self.entry_points.get(VALIDATE_DEPLOY_ENTRY_POINT_SELECTOR)
157            {
158                let ctor_inputs =
159                    self.ctor.as_ref().map(|ctor| ctor.inputs.as_slice()).unwrap_or(&[]);
160                if !validate_deploy.inputs.ends_with(ctor_inputs) {
161                    self.errors.push(ABIError::ValidateDeployMismatchingConstructor(
162                        validate_deploy.source,
163                    ));
164                }
165            }
166        } else {
167            for selector in ACCOUNT_CONTRACT_ENTRY_POINT_SELECTORS {
168                if let Some(info) = self.entry_points.get(*selector) {
169                    self.errors.push(ABIError::EntryPointSupportedOnlyOnAccountContract {
170                        selector: selector.to_string(),
171                        source_ptr: info.source,
172                    });
173                }
174            }
175        }
176        Ok(())
177    }
178
179    /// Adds a Starknet contract module to the ABI.
180    fn process_submodule_contract(&mut self, submodule_id: SubmoduleId) -> Maybe<()> {
181        let mut free_functions = Vec::new();
182        let mut enums = Vec::new();
183        let mut structs = Vec::new();
184        let mut impl_defs = Vec::new();
185        let mut impl_aliases = Vec::new();
186        for item in &*self.db.module_items(ModuleId::Submodule(submodule_id))? {
187            match item {
188                ModuleItemId::FreeFunction(id) => free_functions.push(*id),
189                ModuleItemId::Struct(id) => structs.push(*id),
190                ModuleItemId::Enum(id) => enums.push(*id),
191                ModuleItemId::Impl(id) => impl_defs.push(*id),
192                ModuleItemId::ImplAlias(id) => impl_aliases.push(*id),
193                _ => {}
194            }
195        }
196
197        // Get storage type for later validations.
198        let mut storage_type = None;
199        for struct_id in structs {
200            let struct_name = struct_id.name(self.db.upcast());
201            let concrete_struct_id =
202                ConcreteStructLongId { struct_id, generic_args: vec![] }.intern(self.db);
203            let source = Source::Struct(concrete_struct_id);
204            if struct_name == CONTRACT_STATE_NAME {
205                if storage_type.is_some() {
206                    self.errors.push(ABIError::MultipleStorages(source));
207                }
208                storage_type = Some(
209                    TypeLongId::Concrete(ConcreteTypeId::Struct(concrete_struct_id))
210                        .intern(self.db),
211                );
212            }
213            // Forbid a struct named Event.
214            if struct_name == EVENT_TYPE_NAME {
215                self.errors.push(ABIError::EventMustBeEnum(source));
216            }
217        }
218        let Some(storage_type) = storage_type else {
219            self.errors.push(ABIError::NoStorage);
220            return Ok(());
221        };
222
223        // Add impls to ABI.
224        for impl_def in impl_defs {
225            let source = Source::Impl(impl_def);
226            let is_of_interface =
227                self.db.impl_def_trait(impl_def)?.has_attr(self.db, INTERFACE_ATTR)?;
228            // TODO(v3): deprecate the external attribute.
229            if impl_def.has_attr(self.db, EXTERNAL_ATTR)? {
230                if is_of_interface {
231                    self.add_embedded_impl(source, impl_def, None)
232                        .unwrap_or_else(|err| self.errors.push(err));
233                } else {
234                    self.add_non_interface_impl(source, impl_def, storage_type)
235                        .unwrap_or_else(|err| self.errors.push(err));
236                }
237            } else if is_impl_abi_embed(self.db, impl_def)? {
238                if !is_of_interface {
239                    self.errors.push(ABIError::EmbeddedImplMustBeInterface(source));
240                }
241                self.add_embedded_impl(source, impl_def, None)
242                    .unwrap_or_else(|err| self.errors.push(err));
243            } else if is_impl_abi_per_item(self.db, impl_def)? {
244                if is_of_interface {
245                    self.errors.push(ABIError::ContractInterfaceImplCannotBePerItem(source));
246                }
247                self.add_per_item_impl(impl_def, storage_type)
248                    .unwrap_or_else(|err| self.errors.push(err));
249            }
250        }
251        for impl_alias in impl_aliases {
252            if impl_alias.has_attr_with_arg(self.db, ABI_ATTR, ABI_ATTR_EMBED_V0_ARG)? {
253                self.add_embedded_impl_alias(impl_alias)
254                    .unwrap_or_else(|err| self.errors.push(err));
255            }
256        }
257
258        // Add external functions, constructor and L1 handlers to ABI.
259        for free_function_id in free_functions {
260            self.maybe_add_function_with_body(
261                FunctionWithBodyId::Free(free_function_id),
262                storage_type,
263            )
264            .unwrap_or_else(|err| self.errors.push(err));
265        }
266
267        // Add events to ABI.
268        for enum_id in enums {
269            let enm_name = enum_id.name(self.db.upcast());
270            if enm_name == EVENT_TYPE_NAME && enum_id.has_attr(self.db.upcast(), EVENT_ATTR)? {
271                // Get the ConcreteEnumId from the EnumId.
272                let concrete_enum_id =
273                    ConcreteEnumLongId { enum_id, generic_args: vec![] }.intern(self.db);
274                let source = Source::Enum(concrete_enum_id);
275                // Check that the enum has no generic parameters.
276                if !self.db.enum_generic_params(enum_id).unwrap_or_default().is_empty() {
277                    self.errors.push(ABIError::EventWithGenericParams(source));
278                }
279                // Get the TypeId of the enum.
280                let ty =
281                    TypeLongId::Concrete(ConcreteTypeId::Enum(concrete_enum_id)).intern(self.db);
282                self.add_event(ty, source).unwrap_or_else(|err| self.errors.push(err));
283            }
284        }
285        Ok(())
286    }
287
288    /// Adds an interface to the ABI.
289    fn add_interface(&mut self, source: Source, trait_id: TraitId) -> Result<(), ABIError> {
290        // Get storage type
291        let generic_params = self.db.trait_generic_params(trait_id)?;
292        let [GenericParam::Type(storage_type)] = generic_params.as_slice() else {
293            return Err(ABIError::ExpectedOneGenericParam(source));
294        };
295        let storage_type = TypeLongId::GenericParameter(storage_type.id).intern(self.db);
296
297        let interface_path = trait_id.full_path(self.db.upcast());
298        let mut items = Vec::new();
299        for function in self.db.trait_functions(trait_id).unwrap_or_default().values() {
300            let f = self.trait_function_as_abi(*function, storage_type)?;
301            self.add_entry_point(function.name(self.db.upcast()).into(), EntryPointInfo {
302                source,
303                inputs: f.inputs.clone(),
304            })?;
305            items.push(Item::Function(f));
306        }
307
308        let interface_item = Item::Interface(Interface { name: interface_path.clone(), items });
309        self.add_abi_item(interface_item, true, source)?;
310
311        Ok(())
312    }
313
314    /// Adds the functions of an `external` impl of non-interface trait to the ABI as external
315    /// functions.
316    fn add_non_interface_impl(
317        &mut self,
318        source: Source,
319        impl_def_id: ImplDefId,
320        storage_type: TypeId,
321    ) -> Result<(), ABIError> {
322        let trait_id = self.db.impl_def_trait(impl_def_id)?;
323        for function in self.db.trait_functions(trait_id).unwrap_or_default().values() {
324            let function_abi = self.trait_function_as_abi(*function, storage_type)?;
325            self.add_abi_item(Item::Function(function_abi), true, source)?;
326        }
327
328        Ok(())
329    }
330
331    /// Adds an impl of a starknet interface to the ABI.
332    /// `impl_alias_name` can override the given impl name and is used in the ABI if set.
333    fn add_embedded_impl(
334        &mut self,
335        source: Source,
336        impl_def_id: ImplDefId,
337        impl_alias_name: Option<String>,
338    ) -> Result<(), ABIError> {
339        let impl_name = impl_def_id.name(self.db.upcast());
340
341        let trt = self.db.impl_def_concrete_trait(impl_def_id)?;
342
343        let trait_id = trt.trait_id(self.db);
344        let interface_name = trait_id.full_path(self.db.upcast());
345
346        let abi_name = impl_alias_name.unwrap_or(impl_name.into());
347        let impl_item = Item::Impl(Imp { name: abi_name, interface_name });
348        self.add_abi_item(impl_item, true, source)?;
349        self.add_interface(source, trait_id)?;
350
351        Ok(())
352    }
353
354    /// Adds an embedded impl to the ABI.
355    fn add_per_item_impl(
356        &mut self,
357        impl_def_id: ImplDefId,
358        storage_type: TypeId,
359    ) -> Result<(), ABIError> {
360        for impl_function_id in self.db.impl_functions(impl_def_id).unwrap_or_default().values() {
361            self.maybe_add_function_with_body(
362                FunctionWithBodyId::Impl(*impl_function_id),
363                storage_type,
364            )?;
365        }
366        Ok(())
367    }
368
369    /// Adds an embedded impl alias to the ABI.
370    fn add_embedded_impl_alias(&mut self, impl_alias_id: ImplAliasId) -> Result<(), ABIError> {
371        let source = Source::ImplAlias(impl_alias_id);
372        let impl_def = self.db.impl_alias_impl_def(impl_alias_id)?;
373
374        // Verify the impl definition has #[starknet::embeddable].
375        if !impl_def.has_attr(self.db, EMBEDDABLE_ATTR)? {
376            return Err(ABIError::EmbeddedImplNotEmbeddable(source));
377        }
378
379        // Verify the trait is marked as #[starknet::interface].
380        if !self.db.impl_def_trait(impl_def)?.has_attr(self.db, INTERFACE_ATTR)? {
381            return Err(ABIError::EmbeddedImplMustBeInterface(source));
382        }
383
384        // Add the impl to the ABI.
385        self.add_embedded_impl(
386            source,
387            impl_def,
388            Some(impl_alias_id.name(self.db.upcast()).into()),
389        )?;
390
391        Ok(())
392    }
393
394    /// Adds a function to the ABI according to its attributes.
395    fn maybe_add_function_with_body(
396        &mut self,
397        function_with_body_id: FunctionWithBodyId,
398        storage_type: TypeId,
399    ) -> Result<(), ABIError> {
400        if function_with_body_id.has_attr(self.db.upcast(), EXTERNAL_ATTR)? {
401            self.add_function_with_body(function_with_body_id, storage_type)?;
402        } else if function_with_body_id.has_attr(self.db.upcast(), CONSTRUCTOR_ATTR)? {
403            self.add_constructor(function_with_body_id, storage_type)?;
404        } else if function_with_body_id.has_attr(self.db.upcast(), L1_HANDLER_ATTR)? {
405            self.add_l1_handler(function_with_body_id, storage_type)?;
406        }
407        Ok(())
408    }
409
410    /// Adds a function to the ABI.
411    fn add_function_with_body(
412        &mut self,
413        function_with_body_id: FunctionWithBodyId,
414        storage_type: TypeId,
415    ) -> Result<(), ABIError> {
416        let name: String = function_with_body_id.name(self.db.upcast()).into();
417        let signature = self.db.function_with_body_signature(function_with_body_id)?;
418
419        let function = self.function_as_abi(&name, signature, storage_type)?;
420        self.add_abi_item(Item::Function(function), true, Source::Function(function_with_body_id))?;
421
422        Ok(())
423    }
424
425    /// Adds a constructor to the ABI.
426    fn add_constructor(
427        &mut self,
428        function_with_body_id: FunctionWithBodyId,
429        storage_type: TypeId,
430    ) -> Result<(), ABIError> {
431        let source = Source::Function(function_with_body_id);
432        if self.ctor.is_some() {
433            return Err(ABIError::MultipleConstructors(source));
434        }
435        let name = function_with_body_id.name(self.db.upcast()).into();
436        let signature = self.db.function_with_body_signature(function_with_body_id)?;
437
438        let (inputs, state_mutability) =
439            self.get_function_signature_inputs_and_mutability(&signature, storage_type)?;
440        self.ctor = Some(EntryPointInfo { source, inputs: inputs.clone() });
441        require(state_mutability == StateMutability::External).ok_or(ABIError::UnexpectedType)?;
442
443        let constructor_item = Item::Constructor(Constructor { name, inputs });
444        self.add_abi_item(constructor_item, true, source)?;
445
446        Ok(())
447    }
448
449    /// Adds an L1 handler to the ABI.
450    fn add_l1_handler(
451        &mut self,
452        function_with_body_id: FunctionWithBodyId,
453        storage_type: TypeId,
454    ) -> Result<(), ABIError> {
455        let name = function_with_body_id.name(self.db.upcast()).into();
456        let signature = self.db.function_with_body_signature(function_with_body_id)?;
457
458        let (inputs, state_mutability) =
459            self.get_function_signature_inputs_and_mutability(&signature, storage_type)?;
460
461        let outputs = self.get_signature_outputs(&signature)?;
462
463        let l1_handler_item =
464            Item::L1Handler(L1Handler { name, inputs, outputs, state_mutability });
465        self.add_abi_item(l1_handler_item, true, Source::Function(function_with_body_id))?;
466
467        Ok(())
468    }
469
470    /// Inspects a free function and returns its inputs and state mutability.
471    fn get_function_signature_inputs_and_mutability(
472        &mut self,
473        signature: &cairo_lang_semantic::Signature,
474        storage_type: TypeId,
475    ) -> Result<(Vec<Input>, StateMutability), ABIError> {
476        let mut params = signature.params.iter();
477        let Some(first_param) = params.next() else {
478            return Err(ABIError::EntrypointMustHaveSelf);
479        };
480        require(first_param.name == "self").ok_or(ABIError::EntrypointMustHaveSelf)?;
481        let is_ref = first_param.mutability == Mutability::Reference;
482        let expected_storage_ty = is_ref
483            .then_some(storage_type)
484            .unwrap_or_else(|| TypeLongId::Snapshot(storage_type).intern(self.db));
485        require(first_param.ty == expected_storage_ty).ok_or(ABIError::UnexpectedType)?;
486        let state_mutability =
487            if is_ref { StateMutability::External } else { StateMutability::View };
488        let mut inputs = vec![];
489        for param in params {
490            self.add_type(param.ty)?;
491            inputs.push(Input {
492                name: param.id.name(self.db.upcast()).into(),
493                ty: param.ty.format(self.db),
494            });
495        }
496        Ok((inputs, state_mutability))
497    }
498
499    /// Gets the output types of the given signature.
500    fn get_signature_outputs(
501        &mut self,
502        signature: &cairo_lang_semantic::Signature,
503    ) -> Result<Vec<Output>, ABIError> {
504        // TODO(spapini): output refs?
505        Ok(if signature.return_type.is_unit(self.db) {
506            vec![]
507        } else {
508            self.add_type(signature.return_type)?;
509            vec![Output { ty: signature.return_type.format(self.db) }]
510        })
511    }
512
513    /// Converts a TraitFunctionId to an ABI::Function.
514    fn trait_function_as_abi(
515        &mut self,
516        trait_function_id: TraitFunctionId,
517        storage_type: TypeId,
518    ) -> Result<Function, ABIError> {
519        let name: String = trait_function_id.name(self.db.upcast()).into();
520        let signature = self.db.trait_function_signature(trait_function_id)?;
521
522        self.function_as_abi(&name, signature, storage_type)
523    }
524
525    /// Converts a function name and signature to an ABI::Function.
526    fn function_as_abi(
527        &mut self,
528        name: &str,
529        signature: Signature,
530        storage_type: TypeId,
531    ) -> Result<Function, ABIError> {
532        let (inputs, state_mutability) =
533            self.get_function_signature_inputs_and_mutability(&signature, storage_type)?;
534
535        let outputs = self.get_signature_outputs(&signature)?;
536
537        Ok(Function { name: name.to_string(), inputs, outputs, state_mutability })
538    }
539
540    /// Adds an event to the ABI from a type with an Event derive.
541    fn add_event(&mut self, type_id: TypeId, source: Source) -> Result<(), ABIError> {
542        if self.event_info.contains_key(&type_id) {
543            // The event was handled previously.
544            return Ok(());
545        }
546
547        let concrete = try_extract_matches!(type_id.lookup_intern(self.db), TypeLongId::Concrete)
548            .ok_or(ABIError::UnexpectedType)?;
549        let (event_kind, source) = match fetch_event_data(self.db, type_id)
550            .ok_or(ABIError::EventNotDerived(source))?
551        {
552            EventData::Struct { members } => {
553                let ConcreteTypeId::Struct(concrete_struct_id) = concrete else {
554                    unreachable!();
555                };
556                let concrete_members = self.db.concrete_struct_members(concrete_struct_id)?;
557                let event_fields = members
558                    .into_iter()
559                    .map(|(name, kind)| {
560                        let concrete_member = &concrete_members[&name];
561                        let ty = concrete_member.ty;
562                        self.add_event_field(kind, ty, name, Source::Member(concrete_member.id))
563                    })
564                    .collect::<Result<_, ABIError>>()?;
565                self.event_info.insert(type_id, EventInfo::Struct);
566                (EventKind::Struct { members: event_fields }, Source::Struct(concrete_struct_id))
567            }
568            EventData::Enum { variants } => {
569                let ConcreteTypeId::Enum(concrete_enum_id) = concrete else {
570                    unreachable!();
571                };
572                let mut selectors = HashSet::new();
573                let mut add_selector = |selector: &str, source_ptr| {
574                    if !selectors.insert(selector.to_string()) {
575                        Err(ABIError::EventSelectorDuplication {
576                            event: type_id.format(self.db),
577                            selector: selector.to_string(),
578                            source_ptr,
579                        })
580                    } else {
581                        Ok(())
582                    }
583                };
584                let concrete_variants = self.db.concrete_enum_variants(concrete_enum_id)?;
585                let event_fields = zip_eq(variants, concrete_variants)
586                    .map(|((name, kind), concrete_variant)| {
587                        let source = Source::Variant(concrete_variant.id);
588                        if kind == EventFieldKind::Nested {
589                            add_selector(&name, source)?;
590                        }
591                        let field =
592                            self.add_event_field(kind, concrete_variant.ty, name.clone(), source)?;
593                        if kind == EventFieldKind::Flat {
594                            if let EventInfo::Enum(inner) = &self.event_info[&concrete_variant.ty] {
595                                for selector in inner {
596                                    add_selector(selector, source)?;
597                                }
598                            } else {
599                                let bad_attr = concrete_variant
600                                    .concrete_enum_id
601                                    .enum_id(self.db)
602                                    .stable_ptr(self.db.upcast())
603                                    .lookup(self.db.upcast())
604                                    .variants(self.db.upcast())
605                                    .elements(self.db.upcast())
606                                    .into_iter()
607                                    .find_map(|v| {
608                                        if v.name(self.db.upcast()).text(self.db.upcast()) == name {
609                                            v.find_attr(self.db.upcast(), FLAT_ATTR)
610                                        } else {
611                                            None
612                                        }
613                                    })
614                                    .expect("Impossible mismatch between AuxData and syntax");
615                                return Err(ABIError::EventFlatVariantMustBeEnum(bad_attr));
616                            }
617                        }
618                        Ok(field)
619                    })
620                    .collect::<Result<_, ABIError>>()?;
621                self.event_info.insert(type_id, EventInfo::Enum(selectors));
622                (EventKind::Enum { variants: event_fields }, Source::Enum(concrete_enum_id))
623            }
624        };
625        let event_item = Item::Event(Event { name: type_id.format(self.db), kind: event_kind });
626        self.add_abi_item(event_item, true, source)?;
627
628        Ok(())
629    }
630
631    /// Adds an event field to the ABI.
632    fn add_event_field(
633        &mut self,
634        kind: EventFieldKind,
635        ty: TypeId,
636        name: SmolStr,
637        source: Source,
638    ) -> Result<EventField, ABIError> {
639        match kind {
640            EventFieldKind::KeySerde | EventFieldKind::DataSerde => self.add_type(ty)?,
641            EventFieldKind::Nested | EventFieldKind::Flat => self.add_event(ty, source)?,
642        };
643        Ok(EventField { name: name.into(), ty: ty.format(self.db), kind })
644    }
645
646    /// Adds a type to the ABI from a TypeId.
647    fn add_type(&mut self, type_id: TypeId) -> Result<(), ABIError> {
648        if !self.types.insert(type_id) {
649            // The type was handled previously.
650            return Ok(());
651        }
652
653        match type_id.lookup_intern(self.db) {
654            TypeLongId::Concrete(concrete) => self.add_concrete_type(concrete),
655            TypeLongId::Tuple(inner_types) => {
656                for ty in inner_types {
657                    self.add_type(ty)?;
658                }
659                Ok(())
660            }
661            TypeLongId::Snapshot(ty) => self.add_type(ty),
662            TypeLongId::FixedSizeArray { type_id, .. } => {
663                self.add_type(type_id)?;
664                Ok(())
665            }
666            TypeLongId::Coupon(_)
667            | TypeLongId::GenericParameter(_)
668            | TypeLongId::Var(_)
669            | TypeLongId::TraitType(_)
670            | TypeLongId::ImplType(_)
671            | TypeLongId::Missing(_)
672            | TypeLongId::Closure(_) => Err(ABIError::UnexpectedType),
673        }
674    }
675
676    /// Adds a concrete type and all inner types that it depends on to ABI.
677    /// native types are skipped.
678    fn add_concrete_type(&mut self, concrete: ConcreteTypeId) -> Result<(), ABIError> {
679        // If we have Array<T>, then we might need to add the type T to the ABI.
680        for generic_arg in concrete.generic_args(self.db) {
681            if let GenericArgumentId::Type(type_id) = generic_arg {
682                self.add_type(type_id)?;
683            }
684        }
685
686        match concrete {
687            ConcreteTypeId::Struct(id) => {
688                let members = self.add_and_get_struct_members(id)?;
689                let struct_item = Item::Struct(Struct { name: concrete.format(self.db), members });
690                self.add_abi_item(struct_item, true, Source::Struct(id))?;
691            }
692            ConcreteTypeId::Enum(id) => {
693                let variants = self.add_and_get_enum_variants(id)?;
694                let enum_item = Item::Enum(Enum { name: concrete.format(self.db), variants });
695                self.add_abi_item(enum_item, true, Source::Enum(id))?;
696            }
697            ConcreteTypeId::Extern(_) => {}
698        }
699        Ok(())
700    }
701
702    /// Adds the types of struct members to the ABI, and returns them.
703    fn add_and_get_struct_members(
704        &mut self,
705        id: cairo_lang_semantic::ConcreteStructId,
706    ) -> Result<Vec<StructMember>, ABIError> {
707        self.db
708            .concrete_struct_members(id)?
709            .iter()
710            .map(|(name, member)| {
711                self.add_type(member.ty)?;
712                Ok(StructMember { name: name.to_string(), ty: member.ty.format(self.db) })
713            })
714            .collect()
715    }
716
717    /// Adds the types of struct variants to the ABI, and returns them.
718    fn add_and_get_enum_variants(
719        &mut self,
720        id: cairo_lang_semantic::ConcreteEnumId,
721    ) -> Result<Vec<EnumVariant>, ABIError> {
722        let generic_id = id.enum_id(self.db);
723
724        self.db
725            .enum_variants(generic_id)?
726            .iter()
727            .map(|(name, variant_id)| {
728                let variant = self.db.concrete_enum_variant(
729                    id,
730                    &self.db.variant_semantic(generic_id, *variant_id)?,
731                )?;
732                self.add_type(variant.ty)?;
733                Ok(EnumVariant { name: name.to_string(), ty: variant.ty.format(self.db) })
734            })
735            .collect::<Result<Vec<_>, ABIError>>()
736    }
737
738    /// Adds an item to the ABI.
739    /// Returns OK on success, or an ABIError on failure.
740    fn add_abi_item(
741        &mut self,
742        item: Item,
743        prevent_dups: bool,
744        source: Source,
745    ) -> Result<(), ABIError> {
746        if let Some((name, inputs)) = match &item {
747            Item::Function(item) => Some((item.name.to_string(), item.inputs.clone())),
748            Item::Constructor(item) => Some((item.name.to_string(), item.inputs.clone())),
749            Item::L1Handler(item) => Some((item.name.to_string(), item.inputs.clone())),
750            _ => None,
751        } {
752            self.add_entry_point(name, EntryPointInfo { source, inputs })?;
753        }
754
755        self.insert_abi_item(item, prevent_dups.then_some(source))
756    }
757
758    /// Inserts an item to the set of items.
759    /// Returns OK on success, or an ABIError on failure, e.g. if `prevent_dups` is true but the
760    /// item already existed.
761    /// This is the only way to insert an item to the ABI, to make sure the caller explicitly
762    /// specifies whether duplication is OK or not.
763    /// Should not be used directly, but only through `AbiBuilder::add_abi_item`.
764    fn insert_abi_item(
765        &mut self,
766        item: Item,
767        prevent_dups: Option<Source>,
768    ) -> Result<(), ABIError> {
769        let description = match &item {
770            Item::Function(item) => format!("Function '{}'", item.name),
771            Item::Constructor(item) => format!("Constructor '{}'", item.name),
772            Item::L1Handler(item) => format!("L1 Handler '{}'", item.name),
773            Item::Event(item) => format!("Event '{}'", item.name),
774            Item::Struct(item) => format!("Struct '{}'", item.name),
775            Item::Enum(item) => format!("Enum '{}'", item.name),
776            Item::Interface(item) => format!("Interface '{}'", item.name),
777            Item::Impl(item) => format!("Impl '{}'", item.name),
778        };
779        let already_existed = !self.abi_items.insert(item);
780        if already_existed {
781            if let Some(source) = prevent_dups {
782                return Err(ABIError::InvalidDuplicatedItem { description, source_ptr: source });
783            }
784        }
785
786        Ok(())
787    }
788
789    /// Adds an entry point name to the set of names, to track unsupported duplication.
790    fn add_entry_point(&mut self, name: String, info: EntryPointInfo) -> Result<(), ABIError> {
791        let source_ptr = info.source;
792        if self.entry_points.insert(name.clone(), info).is_some() {
793            return Err(ABIError::DuplicateEntryPointName { name, source_ptr });
794        }
795        Ok(())
796    }
797}
798
799/// Checks whether the impl is marked with #[abi(embed_v0)].
800fn is_impl_abi_embed(db: &dyn SemanticGroup, imp: ImplDefId) -> Maybe<bool> {
801    imp.has_attr_with_arg(db, ABI_ATTR, ABI_ATTR_EMBED_V0_ARG)
802}
803
804/// Checks whether the impl is marked with `#[abi(per_item)]`.
805fn is_impl_abi_per_item(db: &dyn SemanticGroup, imp: ImplDefId) -> Maybe<bool> {
806    imp.has_attr_with_arg(db, ABI_ATTR, ABI_ATTR_PER_ITEM_ARG)
807}
808
809/// Fetch the event data for the given type. Returns None if the given event type doesn't derive
810/// `starknet::Event` by using the `derive` attribute.
811fn fetch_event_data(db: &dyn SemanticGroup, event_type_id: TypeId) -> Option<EventData> {
812    let starknet_module = core_submodule(db, "starknet");
813    // `starknet::event`.
814    let event_module = try_extract_matches!(
815        db.module_item_by_name(starknet_module, "event".into()).unwrap().unwrap(),
816        ModuleItemId::Submodule
817    )?;
818    // `starknet::event::Event`.
819    let event_trait_id = try_extract_matches!(
820        db.module_item_by_name(ModuleId::Submodule(event_module), "Event".into()).unwrap().unwrap(),
821        ModuleItemId::Trait
822    )?;
823    // `starknet::event::Event<ThisEvent>`.
824    let concrete_trait_id = ConcreteTraitLongId {
825        trait_id: event_trait_id,
826        generic_args: vec![GenericArgumentId::Type(event_type_id)],
827    }
828    .intern(db);
829    // The impl of `starknet::event::Event<ThisEvent>`.
830    let event_impl =
831        get_impl_at_context(db.upcast(), ImplLookupContext::default(), concrete_trait_id, None)
832            .ok()?;
833    let concrete_event_impl =
834        try_extract_matches!(event_impl.lookup_intern(db), ImplLongId::Concrete)?;
835    let impl_def_id = concrete_event_impl.impl_def_id(db);
836
837    // Attempt to extract the event data from the aux data from the impl generation.
838    let module_file = impl_def_id.module_file_id(db.upcast());
839    let all_aux_data = db.module_generated_file_aux_data(module_file.0).ok()?;
840    let aux_data = all_aux_data.get(module_file.1.0)?.as_ref()?;
841    Some(aux_data.0.as_any().downcast_ref::<StarkNetEventAuxData>()?.event_data.clone())
842}
843
844#[derive(Error, Debug)]
845pub enum ABIError {
846    #[error("Semantic error")]
847    SemanticError,
848    #[error("Event must be an enum.")]
849    EventMustBeEnum(Source),
850    #[error("`starknet::Event` variant marked with `#[flat]` must be an enum.")]
851    EventFlatVariantMustBeEnum(ast::Attribute),
852    #[error("Event must have no generic parameters.")]
853    EventWithGenericParams(Source),
854    #[error("Event type must derive `starknet::Event`.")]
855    EventNotDerived(Source),
856    #[error("Event `{event}` has duplicate selector `{selector}`.")]
857    EventSelectorDuplication { event: String, selector: String, source_ptr: Source },
858    #[error("Interfaces must have exactly one generic parameter.")]
859    ExpectedOneGenericParam(Source),
860    #[error("Contracts must have only one constructor.")]
861    MultipleConstructors(Source),
862    #[error("Contracts must have a Storage struct.")]
863    NoStorage,
864    #[error("Contracts must have only one Storage struct.")]
865    MultipleStorages(Source),
866    #[error("Got unexpected type.")]
867    UnexpectedType,
868    #[error("Entrypoints must have a self first param.")]
869    EntrypointMustHaveSelf,
870    #[error("An embedded impl must be an impl of a trait marked with #[starknet::interface].")]
871    EmbeddedImplMustBeInterface(Source),
872    #[error("Embedded impls must be annotated with #[starknet::embeddable].")]
873    EmbeddedImplNotEmbeddable(Source),
874    #[error(
875        "An impl marked with #[abi(per_item)] can't be of a trait marked with \
876         #[starknet::interface].\n    Consider using #[abi(embed_v0)] instead, or use a \
877         non-interface trait."
878    )]
879    ContractInterfaceImplCannotBePerItem(Source),
880    #[error(
881        "Invalid duplicated item: {description} is used twice in the same contract. This is not \
882         supported."
883    )]
884    InvalidDuplicatedItem { description: String, source_ptr: Source },
885    #[error("Duplicate entry point: '{name}'. This is not currently supported.")]
886    DuplicateEntryPointName { name: String, source_ptr: Source },
887    #[error("Only supported argument for #[starknet::contract] is `account` or nothing.")]
888    IllegalContractAttrArgs,
889    #[error(
890        "`{selector}` is a reserved entry point name for account contracts only (marked with \
891         `#[starknet::contract(account)]`)."
892    )]
893    EntryPointSupportedOnlyOnAccountContract { selector: String, source_ptr: Source },
894    #[error("`{selector}` entry point must exist for account contracts.")]
895    EntryPointMissingForAccountContract { selector: String },
896    #[error("`{VALIDATE_DEPLOY_ENTRY_POINT_SELECTOR}` entry point must match the constructor.")]
897    ValidateDeployMismatchingConstructor(Source),
898}
899impl ABIError {
900    pub fn location(&self, db: &dyn SemanticGroup) -> Option<SyntaxStablePtrId> {
901        // TODO(orizi): Add more error locations.
902        match self {
903            ABIError::SemanticError => None,
904            ABIError::EventFlatVariantMustBeEnum(attr) => Some(attr.stable_ptr().untyped()),
905            ABIError::NoStorage => None,
906            ABIError::UnexpectedType => None,
907            ABIError::EntrypointMustHaveSelf => None,
908            ABIError::EventNotDerived(source)
909            | ABIError::EventSelectorDuplication { source_ptr: source, .. }
910            | ABIError::EventMustBeEnum(source)
911            | ABIError::EventWithGenericParams(source)
912            | ABIError::ExpectedOneGenericParam(source)
913            | ABIError::MultipleConstructors(source)
914            | ABIError::MultipleStorages(source)
915            | ABIError::EmbeddedImplMustBeInterface(source)
916            | ABIError::EmbeddedImplNotEmbeddable(source)
917            | ABIError::ContractInterfaceImplCannotBePerItem(source)
918            | ABIError::InvalidDuplicatedItem { source_ptr: source, .. }
919            | ABIError::DuplicateEntryPointName { source_ptr: source, .. }
920            | ABIError::EntryPointSupportedOnlyOnAccountContract { source_ptr: source, .. }
921            | ABIError::ValidateDeployMismatchingConstructor(source) => Some(source.location(db)),
922            ABIError::IllegalContractAttrArgs => None,
923            ABIError::EntryPointMissingForAccountContract { .. } => None,
924        }
925    }
926}
927impl From<DiagnosticAdded> for ABIError {
928    fn from(_: DiagnosticAdded) -> Self {
929        ABIError::SemanticError
930    }
931}
932
933/// The source of an ABI item, used for error reporting.
934#[derive(Clone, Copy, Debug, PartialEq, Eq)]
935pub enum Source {
936    Function(FunctionWithBodyId),
937    Impl(ImplDefId),
938    ImplAlias(ImplAliasId),
939    Struct(cairo_lang_semantic::ConcreteStructId),
940    Member(cairo_lang_defs::ids::MemberId),
941    Enum(cairo_lang_semantic::ConcreteEnumId),
942    Variant(cairo_lang_defs::ids::VariantId),
943    Trait(TraitId),
944}
945impl Source {
946    fn location(&self, db: &dyn SemanticGroup) -> SyntaxStablePtrId {
947        match self {
948            Source::Function(id) => id.untyped_stable_ptr(db.upcast()),
949            Source::Impl(id) => id.untyped_stable_ptr(db.upcast()),
950            Source::ImplAlias(id) => id.untyped_stable_ptr(db.upcast()),
951            Source::Struct(id) => id.struct_id(db).untyped_stable_ptr(db.upcast()),
952            Source::Member(id) => id.untyped_stable_ptr(db.upcast()),
953            Source::Enum(id) => id.enum_id(db).untyped_stable_ptr(db.upcast()),
954            Source::Variant(id) => id.untyped_stable_ptr(db.upcast()),
955            Source::Trait(id) => id.untyped_stable_ptr(db.upcast()),
956        }
957    }
958}