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
45enum EventInfo {
47 Struct,
49 Enum(HashSet<String>),
51}
52
53struct EntryPointInfo {
55 source: Source,
57 inputs: Vec<Input>,
59}
60
61#[derive(Clone, Debug, Default)]
62pub struct BuilderConfig {
64 pub account_contract_validations: bool,
66}
67
68pub struct AbiBuilder<'a> {
69 db: &'a dyn SemanticGroup,
71 config: BuilderConfig,
73
74 abi_items: OrderedHashSet<Item>,
77
78 types: HashSet<TypeId>,
81
82 event_info: HashMap<TypeId, EventInfo>,
85
86 entry_points: HashMap<String, EntryPointInfo>,
89
90 ctor: Option<EntryPointInfo>,
92
93 errors: Vec<ABIError>,
95}
96impl<'a> AbiBuilder<'a> {
97 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 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 pub fn errors(&self) -> &[ABIError] {
129 &self.errors
130 }
131
132 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 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 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 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 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 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 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 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 let concrete_enum_id =
273 ConcreteEnumLongId { enum_id, generic_args: vec![] }.intern(self.db);
274 let source = Source::Enum(concrete_enum_id);
275 if !self.db.enum_generic_params(enum_id).unwrap_or_default().is_empty() {
277 self.errors.push(ABIError::EventWithGenericParams(source));
278 }
279 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 fn add_interface(&mut self, source: Source, trait_id: TraitId) -> Result<(), ABIError> {
290 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 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 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 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 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 if !impl_def.has_attr(self.db, EMBEDDABLE_ATTR)? {
376 return Err(ABIError::EmbeddedImplNotEmbeddable(source));
377 }
378
379 if !self.db.impl_def_trait(impl_def)?.has_attr(self.db, INTERFACE_ATTR)? {
381 return Err(ABIError::EmbeddedImplMustBeInterface(source));
382 }
383
384 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 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 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 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 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 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 fn get_signature_outputs(
501 &mut self,
502 signature: &cairo_lang_semantic::Signature,
503 ) -> Result<Vec<Output>, ABIError> {
504 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 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 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 fn add_event(&mut self, type_id: TypeId, source: Source) -> Result<(), ABIError> {
542 if self.event_info.contains_key(&type_id) {
543 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 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 fn add_type(&mut self, type_id: TypeId) -> Result<(), ABIError> {
648 if !self.types.insert(type_id) {
649 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 fn add_concrete_type(&mut self, concrete: ConcreteTypeId) -> Result<(), ABIError> {
679 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 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 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 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 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 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
799fn 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
804fn 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
809fn fetch_event_data(db: &dyn SemanticGroup, event_type_id: TypeId) -> Option<EventData> {
812 let starknet_module = core_submodule(db, "starknet");
813 let event_module = try_extract_matches!(
815 db.module_item_by_name(starknet_module, "event".into()).unwrap().unwrap(),
816 ModuleItemId::Submodule
817 )?;
818 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 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 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 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 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#[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}