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
51enum EventInfo {
53 Struct,
55 Enum(HashSet<String>),
57}
58
59struct EntryPointInfo<'db> {
61 source: Source<'db>,
63 inputs: Vec<Input>,
65}
66
67#[derive(Clone, Debug, Default)]
68pub struct BuilderConfig {
70 pub account_contract_validations: bool,
72}
73
74pub struct AbiBuilder<'db> {
75 db: &'db dyn Database,
77 config: BuilderConfig,
79
80 abi_items: OrderedHashSet<Item>,
83
84 types: HashSet<TypeId<'db>>,
87
88 event_info: HashMap<TypeId<'db>, EventInfo>,
91
92 entry_points: HashMap<String, EntryPointInfo<'db>>,
95
96 ctor: Option<EntryPointInfo<'db>>,
98
99 errors: Vec<ABIError<'db>>,
101}
102impl<'db> AbiBuilder<'db> {
103 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 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 pub fn errors(&self) -> &[ABIError<'db>] {
135 &self.errors
136 }
137
138 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 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 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 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 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 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 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 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 let concrete_enum_id =
279 ConcreteEnumLongId { enum_id, generic_args: vec![] }.intern(self.db);
280 let source = Source::Enum(concrete_enum_id);
281 if !self.db.enum_generic_params(enum_id).unwrap_or_default().is_empty() {
283 self.errors.push(ABIError::EventWithGenericParams(source));
284 }
285 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 fn add_interface(
296 &mut self,
297 source: Source<'db>,
298 trait_id: TraitId<'db>,
299 ) -> Result<(), ABIError<'db>> {
300 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 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 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 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 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 if !impl_def.has_attr(self.db, EMBEDDABLE_ATTR)? {
395 return Err(ABIError::EmbeddedImplNotEmbeddable(source));
396 }
397
398 if !self.db.impl_def_trait(impl_def)?.has_attr(self.db, INTERFACE_ATTR)? {
400 return Err(ABIError::EmbeddedImplMustBeInterface(source));
401 }
402
403 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 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 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 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 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 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 fn get_signature_outputs(
520 &mut self,
521 signature: &cairo_lang_semantic::Signature<'db>,
522 ) -> Result<Vec<Output>, ABIError<'db>> {
523 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 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 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 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 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 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 fn add_type(&mut self, type_id: TypeId<'db>) -> Result<(), ABIError<'db>> {
670 if !self.types.insert(type_id) {
671 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 fn add_concrete_type(&mut self, concrete: ConcreteTypeId<'db>) -> Result<(), ABIError<'db>> {
700 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 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 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 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 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 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
822fn 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
827fn 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
832fn 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 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 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 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 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 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 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#[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}