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