1use starknet::core::types::contract::{AbiEntry, AbiEvent, SierraClass, TypedAbiEvent};
2use std::collections::HashMap;
3
4use crate::tokens::{Array, Composite, CompositeType, CoreBasic, Function, Token};
5use crate::{CainomeResult, Error};
6
7#[derive(Debug, Clone, PartialEq, Default)]
8pub struct TokenizedAbi {
9 pub enums: Vec<Token>,
11 pub structs: Vec<Token>,
13 pub functions: Vec<Token>,
15 pub interfaces: HashMap<String, Vec<Token>>,
17}
18
19pub struct AbiParser {}
20
21impl AbiParser {
22 pub fn tokens_from_abi_string(
33 abi: &str,
34 type_aliases: &HashMap<String, String>,
35 ) -> CainomeResult<TokenizedAbi> {
36 let abi_entries = Self::parse_abi_string(abi)?;
37 let tokenized_abi =
38 AbiParser::collect_tokens(&abi_entries, type_aliases).expect("failed tokens parsing");
39
40 Ok(tokenized_abi)
41 }
42
43 pub fn parse_abi_string(abi: &str) -> CainomeResult<Vec<AbiEntry>> {
53 let entries = if let Ok(sierra) = serde_json::from_str::<SierraClass>(abi) {
54 sierra.abi
55 } else {
56 serde_json::from_str::<Vec<AbiEntry>>(abi).map_err(Error::SerdeJson)?
57 };
58
59 Ok(entries)
60 }
61
62 pub fn collect_tokens(
64 entries: &[AbiEntry],
65 type_aliases: &HashMap<String, String>,
66 ) -> CainomeResult<TokenizedAbi> {
67 let mut token_candidates: HashMap<String, Vec<Token>> = HashMap::new();
68
69 for entry in entries {
71 Self::collect_entry_token(entry, &mut token_candidates)?;
72 }
73
74 let tokens = Self::filter_struct_enum_tokens(token_candidates);
75
76 let mut structs = vec![];
77 let mut enums = vec![];
78 let mut all_composites: HashMap<String, Composite> = HashMap::new();
82
83 for (_, mut t) in tokens {
85 for (type_path, alias) in type_aliases {
86 t.apply_alias(type_path, alias);
87 }
88
89 if let Token::Composite(ref c) = t {
90 all_composites.insert(c.type_path_no_generic(), c.clone());
91
92 match c.r#type {
93 CompositeType::Struct => structs.push(t),
94 CompositeType::Enum => enums.push(t),
95 _ => (),
96 }
97 }
98 }
99
100 let mut functions = vec![];
101 let mut interfaces: HashMap<String, Vec<Token>> = HashMap::new();
102
103 for entry in entries {
104 Self::collect_entry_function(
105 entry,
106 &all_composites,
107 &mut functions,
108 &mut interfaces,
109 None,
110 )?;
111 }
112
113 Ok(TokenizedAbi {
114 enums,
115 structs,
116 functions,
117 interfaces,
118 })
119 }
120
121 fn collect_entry_function(
131 entry: &AbiEntry,
132 all_composites: &HashMap<String, Composite>,
133 functions: &mut Vec<Token>,
134 interfaces: &mut HashMap<String, Vec<Token>>,
135 interface_name: Option<String>,
136 ) -> CainomeResult<()> {
137 fn get_existing_token_or_parsed(
140 type_path: &str,
141 all_composites: &HashMap<String, Composite>,
142 ) -> CainomeResult<Token> {
143 let parsed_token = Token::parse(type_path)?;
144
145 if let Token::Composite(ref c) = parsed_token {
149 match all_composites.get(&c.type_path_no_generic()) {
150 Some(e) => Ok(Token::Composite(e.clone())),
151 None => Ok(parsed_token),
152 }
153 } else {
154 Ok(parsed_token)
155 }
156 }
157
158 match entry {
161 AbiEntry::Function(f) => {
162 let mut func = Function::new(&f.name, f.state_mutability.clone().into());
163
164 for i in &f.inputs {
165 let token = get_existing_token_or_parsed(&i.r#type, all_composites)?;
166 func.inputs.push((i.name.clone(), token));
167 }
168
169 for o in &f.outputs {
170 let token = get_existing_token_or_parsed(&o.r#type, all_composites)?;
171 func.outputs.push(token);
172 }
173
174 if let Some(name) = interface_name {
175 interfaces
176 .entry(name)
177 .or_default()
178 .push(Token::Function(func));
179 } else {
180 functions.push(Token::Function(func));
181 }
182 }
183 AbiEntry::Interface(interface) => {
184 for entry in &interface.items {
185 Self::collect_entry_function(
186 entry,
187 all_composites,
188 functions,
189 interfaces,
190 Some(interface.name.clone()),
191 )?;
192 }
193 }
194 _ => (),
195 }
196
197 Ok(())
198 }
199
200 fn collect_entry_token(
207 entry: &AbiEntry,
208 tokens: &mut HashMap<String, Vec<Token>>,
209 ) -> CainomeResult<()> {
210 match entry {
211 AbiEntry::Struct(s) => {
212 if Array::parse(&s.name).is_ok() {
213 return Ok(());
216 };
217
218 if CoreBasic::parse(&s.name).is_ok() {
220 return Ok(());
221 };
222
223 let token: Token = s.try_into()?;
224 let entry = tokens.entry(token.type_path()).or_default();
225 entry.push(token);
226 }
227 AbiEntry::Enum(e) => {
228 if CoreBasic::parse(&e.name).is_ok() {
230 return Ok(());
231 };
232
233 let token: Token = e.try_into()?;
234 let entry = tokens.entry(token.type_path()).or_default();
235 entry.push(token);
236 }
237 AbiEntry::Event(ev) => {
238 let mut token: Token;
239 match ev {
240 AbiEvent::Typed(typed_e) => match typed_e {
241 TypedAbiEvent::Struct(s) => {
242 if CoreBasic::parse(&s.name).is_ok() {
244 return Ok(());
245 };
246
247 token = s.try_into()?;
248 }
249 TypedAbiEvent::Enum(e) => {
250 if CoreBasic::parse(&e.name).is_ok() {
252 return Ok(());
253 };
254
255 token = e.try_into()?;
256
257 if let Token::Composite(ref mut c) = token {
268 for i in &mut c.inners {
269 if let Token::Composite(ref mut ic) = i.token {
270 ic.is_event = true;
271 }
272 }
273 }
274 }
275 },
276 AbiEvent::Untyped(_) => {
277 return Ok(());
279 }
280 };
281
282 let entry = tokens.entry(token.type_path()).or_default();
283 entry.push(token);
284 }
285 AbiEntry::Interface(interface) => {
286 for entry in &interface.items {
287 Self::collect_entry_token(entry, tokens)?;
288 }
289 }
290 _ => (),
291 };
292
293 Ok(())
294 }
295
296 fn filter_struct_enum_tokens(
297 token_candidates: HashMap<String, Vec<Token>>,
298 ) -> HashMap<String, Token> {
299 let tokens_filtered = Self::filter_token_candidates(token_candidates);
300
301 let filtered = tokens_filtered.clone();
304
305 Self::hydrate_composites(tokens_filtered, filtered)
308 }
309
310 fn filter_token_candidates(
315 token_candidates: HashMap<String, Vec<Token>>,
316 ) -> HashMap<String, Token> {
317 token_candidates
318 .into_iter()
319 .filter_map(|(name, tokens)| {
320 if tokens.is_empty() {
321 return None;
322 }
323
324 if tokens.len() == 1 {
325 return Some((name, tokens[0].clone()));
327 }
328
329 if let Token::Composite(composite_0) = &tokens[0] {
330 let unique_composite = composite_0.clone();
331 let inners = composite_0
332 .inners
333 .iter()
334 .map(|inner| {
335 let inner_tokens = tokens
336 .iter()
337 .filter_map(|__t| {
338 __t.to_composite().ok().and_then(|comp| {
339 comp.inners
340 .iter()
341 .find(|__t_inner| __t_inner.name == inner.name)
342 })
343 })
344 .fold(HashMap::new(), |mut acc, __t_inner| {
345 let type_path = __t_inner.token.type_path();
346 let counter = acc
347 .entry(type_path.clone())
348 .or_insert((0, __t_inner.clone()));
349 counter.0 += 1;
350 acc
351 });
352
353 inner_tokens
355 .into_iter()
356 .max_by_key(|(_, (count, _))| *count)
357 .map(|(_, (_, inner))| inner)
358 .unwrap()
359 })
360 .collect();
361
362 let mut unique_composite = unique_composite;
363 unique_composite.inners = inners;
364
365 return Some((name, Token::Composite(unique_composite)));
366 }
367
368 None
369 })
370 .collect()
371 }
372
373 fn hydrate_composites(
374 tokens_filtered: HashMap<String, Token>,
375 filtered: HashMap<String, Token>,
376 ) -> HashMap<String, Token> {
377 tokens_filtered
378 .into_iter()
379 .fold(HashMap::new(), |mut acc, (name, token)| {
380 acc.insert(name, Token::hydrate(token, &filtered, 10, 0));
381 acc
382 })
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use crate::tokens::{CompositeInner, CompositeInnerKind, CompositeType};
390
391 #[test]
392 fn test_filter_token_candidates_single_inner() {
393 let mut input: HashMap<String, Vec<Token>> = HashMap::new();
394 input.insert(
395 "dojo_starter::models::Direction".to_owned(),
396 vec![Token::Composite(Composite {
397 type_path: "dojo_starter::models::Direction".to_owned(),
398 inners: vec![
399 CompositeInner {
400 index: 0,
401 name: "None".to_owned(),
402 kind: CompositeInnerKind::NotUsed,
403 token: Token::CoreBasic(CoreBasic {
404 type_path: "()".to_owned(),
405 }),
406 },
407 CompositeInner {
408 index: 1,
409 name: "North".to_owned(),
410 kind: CompositeInnerKind::NotUsed,
411 token: Token::CoreBasic(CoreBasic {
412 type_path: "()".to_owned(),
413 }),
414 },
415 CompositeInner {
416 index: 2,
417 name: "South".to_owned(),
418 kind: CompositeInnerKind::NotUsed,
419 token: Token::CoreBasic(CoreBasic {
420 type_path: "()".to_owned(),
421 }),
422 },
423 CompositeInner {
424 index: 3,
425 name: "West".to_owned(),
426 kind: CompositeInnerKind::NotUsed,
427 token: Token::CoreBasic(CoreBasic {
428 type_path: "()".to_owned(),
429 }),
430 },
431 CompositeInner {
432 index: 4,
433 name: "East".to_owned(),
434 kind: CompositeInnerKind::NotUsed,
435 token: Token::CoreBasic(CoreBasic {
436 type_path: "()".to_owned(),
437 }),
438 },
439 ],
440 generic_args: vec![],
441 r#type: CompositeType::Enum,
442 is_event: false,
443 alias: None,
444 })],
445 );
446 input.insert(
447 "dojo_starter::models::DirectionsAvailable".to_owned(),
448 vec![Token::Composite(Composite {
449 type_path: "dojo_starter::models::DirectionsAvailable".to_owned(),
450 inners: vec![
451 CompositeInner {
452 index: 0,
453 name: "player".to_owned(),
454 kind: CompositeInnerKind::NotUsed,
455 token: Token::CoreBasic(CoreBasic {
456 type_path: "core::starknet::contract_address::ContractAddress"
457 .to_owned(),
458 }),
459 },
460 CompositeInner {
461 index: 1,
462 name: "directions".to_owned(),
463 kind: CompositeInnerKind::NotUsed,
464 token: Token::Array(Array {
465 is_legacy: false,
466 type_path: "core::array::Array::<dojo_starter::models::Direction>"
467 .to_owned(),
468 inner: Box::new(Token::Composite(Composite {
469 type_path: "dojo_starter::models::Direction".to_owned(),
470 inners: vec![],
471 generic_args: vec![],
472 r#type: CompositeType::Unknown,
473 is_event: false,
474 alias: None,
475 })),
476 }),
477 },
478 ],
479 generic_args: vec![],
480 r#type: CompositeType::Struct,
481 is_event: false,
482 alias: None,
483 })],
484 );
485 let filtered = AbiParser::filter_token_candidates(input);
486 assert_eq!(2, filtered.len());
487 assert!(filtered.contains_key("dojo_starter::models::Direction"));
488 assert!(filtered.contains_key("dojo_starter::models::DirectionsAvailable"));
489 }
490
491 #[test]
492 fn test_filter_token_candidates_multiple_composites() {
493 let mut input = HashMap::new();
494
495 input.insert(
497 "game::models::ItemType".to_owned(),
498 vec![
499 Token::Composite(Composite {
500 type_path: "game::models::ItemType".to_owned(),
501 inners: vec![
502 CompositeInner {
503 index: 0,
504 name: "Weapon".to_owned(),
505 kind: CompositeInnerKind::NotUsed,
506 token: Token::CoreBasic(CoreBasic {
507 type_path: "core::felt252".to_owned(),
508 }),
509 },
510 CompositeInner {
511 index: 1,
512 name: "Armor".to_owned(),
513 kind: CompositeInnerKind::NotUsed,
514 token: Token::CoreBasic(CoreBasic {
515 type_path: "core::felt252".to_owned(),
516 }),
517 },
518 ],
519 generic_args: vec![],
520 r#type: CompositeType::Enum,
521 is_event: false,
522 alias: None,
523 }),
524 Token::Composite(Composite {
525 type_path: "game::models::ItemType".to_owned(),
526 inners: vec![
527 CompositeInner {
528 index: 0,
529 name: "Weapon".to_owned(),
530 kind: CompositeInnerKind::NotUsed,
531 token: Token::CoreBasic(CoreBasic {
532 type_path: "core::integer::u8".to_owned(),
533 }),
534 },
535 CompositeInner {
536 index: 1,
537 name: "Armor".to_owned(),
538 kind: CompositeInnerKind::NotUsed,
539 token: Token::CoreBasic(CoreBasic {
540 type_path: "core::integer::u8".to_owned(),
541 }),
542 },
543 ],
544 generic_args: vec![],
545 r#type: CompositeType::Enum,
546 is_event: false,
547 alias: None,
548 }),
549 Token::Composite(Composite {
550 type_path: "game::models::ItemType".to_owned(),
551 inners: vec![
552 CompositeInner {
553 index: 0,
554 name: "Weapon".to_owned(),
555 kind: CompositeInnerKind::NotUsed,
556 token: Token::CoreBasic(CoreBasic {
557 type_path: "core::felt252".to_owned(),
558 }),
559 },
560 CompositeInner {
561 index: 1,
562 name: "Armor".to_owned(),
563 kind: CompositeInnerKind::NotUsed,
564 token: Token::CoreBasic(CoreBasic {
565 type_path: "core::felt252".to_owned(),
566 }),
567 },
568 ],
569 generic_args: vec![],
570 r#type: CompositeType::Enum,
571 is_event: false,
572 alias: None,
573 }),
574 ],
575 );
576
577 input.insert(
579 "game::models::Player".to_owned(),
580 vec![
581 Token::Composite(Composite {
582 type_path: "game::models::Player".to_owned(),
583 inners: vec![
584 CompositeInner {
585 index: 0,
586 name: "id".to_owned(),
587 kind: CompositeInnerKind::NotUsed,
588 token: Token::CoreBasic(CoreBasic {
589 type_path: "core::integer::u64".to_owned(),
590 }),
591 },
592 CompositeInner {
593 index: 1,
594 name: "name".to_owned(),
595 kind: CompositeInnerKind::NotUsed,
596 token: Token::CoreBasic(CoreBasic {
597 type_path: "core::felt252".to_owned(),
598 }),
599 },
600 ],
601 generic_args: vec![],
602 r#type: CompositeType::Struct,
603 is_event: false,
604 alias: None,
605 }),
606 Token::Composite(Composite {
607 type_path: "game::models::Player".to_owned(),
608 inners: vec![
609 CompositeInner {
610 index: 0,
611 name: "id".to_owned(),
612 kind: CompositeInnerKind::NotUsed,
613 token: Token::CoreBasic(CoreBasic {
614 type_path: "core::integer::u128".to_owned(),
615 }),
616 },
617 CompositeInner {
618 index: 1,
619 name: "name".to_owned(),
620 kind: CompositeInnerKind::NotUsed,
621 token: Token::CoreBasic(CoreBasic {
622 type_path: "core::felt252".to_owned(),
623 }),
624 },
625 ],
626 generic_args: vec![],
627 r#type: CompositeType::Struct,
628 is_event: false,
629 alias: None,
630 }),
631 Token::Composite(Composite {
632 type_path: "game::models::Player".to_owned(),
633 inners: vec![
634 CompositeInner {
635 index: 0,
636 name: "id".to_owned(),
637 kind: CompositeInnerKind::NotUsed,
638 token: Token::CoreBasic(CoreBasic {
639 type_path: "core::integer::u64".to_owned(),
640 }),
641 },
642 CompositeInner {
643 index: 1,
644 name: "name".to_owned(),
645 kind: CompositeInnerKind::NotUsed,
646 token: Token::CoreBasic(CoreBasic {
647 type_path: "core::felt252".to_owned(),
648 }),
649 },
650 ],
651 generic_args: vec![],
652 r#type: CompositeType::Struct,
653 is_event: false,
654 alias: None,
655 }),
656 ],
657 );
658
659 let filtered = AbiParser::filter_token_candidates(input);
660
661 assert_eq!(2, filtered.len());
662 assert!(filtered.contains_key("game::models::ItemType"));
663 assert!(filtered.contains_key("game::models::Player"));
664
665 let item_type = filtered
667 .get("game::models::ItemType")
668 .unwrap()
669 .to_composite()
670 .unwrap();
671 assert_eq!(item_type.inners.len(), 2);
672 assert_eq!(item_type.inners[0].name, "Weapon");
673 assert_eq!(item_type.inners[1].name, "Armor");
674 assert_eq!(item_type.inners[0].token.type_path(), "core::felt252");
676 assert_eq!(item_type.inners[1].token.type_path(), "core::felt252");
677
678 let player = filtered
680 .get("game::models::Player")
681 .unwrap()
682 .to_composite()
683 .unwrap();
684 assert_eq!(player.inners.len(), 2);
685 assert_eq!(player.inners[0].name, "id");
686 assert_eq!(player.inners[1].name, "name");
687 assert_eq!(player.inners[0].token.type_path(), "core::integer::u64");
689 assert_eq!(player.inners[1].token.type_path(), "core::felt252");
690 }
691
692 #[test]
693 fn test_parse_abi_struct() {
694 let abi_json = r#"
695 [
696 {
697 "type": "struct",
698 "name": "package::StructOne",
699 "members": [
700 {
701 "name": "a",
702 "type": "core::integer::u64"
703 },
704 {
705 "name": "b",
706 "type": "core::zeroable::NonZero"
707 },
708 {
709 "name": "c",
710 "type": "core::integer::u256"
711 }
712 ]
713 }
714 ]
715 "#;
716
717 let result = AbiParser::tokens_from_abi_string(abi_json, &HashMap::new()).unwrap();
718
719 assert_eq!(result.structs.len(), 1);
720 assert_eq!(result.interfaces.len(), 0);
721 assert_eq!(result.functions.len(), 0);
722 assert_eq!(result.enums.len(), 0);
723
724 let s = result.structs[0].to_composite().unwrap();
725 assert_eq!(s.type_path, "package::StructOne");
726 assert_eq!(s.r#type, CompositeType::Struct);
727 assert_eq!(s.inners.len(), 3);
728 assert_eq!(s.inners[0].name, "a");
729 assert_eq!(s.inners[1].name, "b");
730 assert_eq!(s.inners[2].name, "c");
731 }
732
733 #[test]
734 fn test_dojo_starter_direction_available_abi() {
735 let abi = AbiParser::tokens_from_abi_string(
736 include_str!("../../test_data/dojo_starter-directions_available.abi.json"),
737 &HashMap::new(),
738 )
739 .unwrap();
740
741 assert_eq!(abi.structs.len(), 1);
742 let s = abi.structs[0].to_composite().unwrap();
743 if let Token::Array(a) = &s.inners[1].token {
744 let inner_array = a.inner.to_composite().unwrap();
745 assert_eq!(5, inner_array.inners.len());
746 let src_enum = abi.enums[0].to_composite().unwrap();
748 assert_eq!(inner_array, src_enum);
749 } else {
750 panic!("Expected array");
751 }
752 }
753
754 #[test]
755 fn test_nested_tuple() {
756 let abi = AbiParser::tokens_from_abi_string(
757 include_str!("../../test_data/struct_tuple.abi.json"),
758 &HashMap::new(),
759 )
760 .unwrap();
761
762 assert_eq!(abi.structs.len(), 1);
763 let s = abi.structs[0].to_composite().unwrap();
764 if let Token::Array(a) = &s.inners[1].token {
765 if let Token::Tuple(t) = *a.inner.to_owned() {
766 let inner_array = t.inners[0].to_composite().unwrap();
767 assert_eq!(5, inner_array.inners.len());
768 let src_enum = abi.enums[0].to_composite().unwrap();
770 assert_eq!(inner_array, src_enum);
771 } else {
772 panic!("Expected tuple");
773 }
774 } else {
775 panic!("Expected array");
776 }
777 }
778
779 #[test]
780 fn test_composite_generic_args_hydratation() {
781 let mut input: HashMap<String, Vec<Token>> = HashMap::new();
782 input.insert(
783 "tournament::ls15_components::models::tournament::GatedEntryType".to_owned(),
784 vec![Token::Composite(Composite {
785 type_path: "tournament::ls15_components::models::tournament::GatedEntryType"
786 .to_owned(),
787 inners: vec![
788 CompositeInner {
789 index: 0,
790 name: "criteria".to_owned(),
791 kind: CompositeInnerKind::NotUsed,
792 token: Token::Composite(Composite {
793 type_path:
794 "tournament::ls15_components::models::tournament::EntryCriteria"
795 .to_owned(),
796 inners: vec![
797 CompositeInner {
798 index: 0,
799 name: "token_id".to_owned(),
800 kind: CompositeInnerKind::NotUsed,
801 token: Token::CoreBasic(CoreBasic {
802 type_path: "core::integer::u128".to_owned(),
803 }),
804 },
805 CompositeInner {
806 index: 1,
807 name: "entry_count".to_owned(),
808 kind: CompositeInnerKind::NotUsed,
809 token: Token::CoreBasic(CoreBasic {
810 type_path: "core::integer::u64".to_owned(),
811 }),
812 },
813 ],
814 generic_args: vec![],
815 r#type: CompositeType::Struct,
816 is_event: false,
817 alias: None,
818 }),
819 },
820 CompositeInner {
821 index: 1,
822 name: "uniform".to_owned(),
823 kind: CompositeInnerKind::NotUsed,
824 token: Token::CoreBasic(CoreBasic {
825 type_path: "core::integer::u64".to_owned(),
826 }),
827 },
828 ],
829 generic_args: vec![],
830 r#type: CompositeType::Enum,
831 is_event: false,
832 alias: None,
833 })],
834 );
835
836 input.insert(
837 "tournament::ls15_components::models::tournament::GatedToken".to_owned(),
838 vec![Token::Composite(Composite {
839 type_path: "tournament::ls15_components::models::tournament::GatedToken".to_owned(),
840 inners: vec![
841 CompositeInner {
842 index: 0,
843 name: "token".to_owned(),
844 kind: CompositeInnerKind::NotUsed,
845 token: Token::CoreBasic(CoreBasic {
846 type_path: "core::starknet::contract_address::ContractAddress"
847 .to_owned(),
848 }),
849 },
850 CompositeInner {
851 index: 1,
852 name: "entry_type".to_owned(),
853 kind: CompositeInnerKind::NotUsed,
854 token: Token::Composite(Composite {
855 type_path:
856 "tournament::ls15_components::models::tournament::GatedEntryType"
857 .to_owned(),
858 inners: vec![],
859 generic_args: vec![],
860 r#type: CompositeType::Unknown,
861 is_event: false,
862 alias: None,
863 }),
864 },
865 ],
866 generic_args: vec![],
867 r#type: CompositeType::Struct,
868 is_event: false,
869 alias: None,
870 })],
871 );
872 input.insert(
873 "tournament::ls15_components::models::tournament::GatedType".to_owned(),
874 vec![Token::Composite(
875Composite {
876 type_path: "tournament::ls15_components::models::tournament::GatedType".to_owned(),
877 inners: vec![
878 CompositeInner {
879 index: 0,
880 name: "token".to_owned(),
881 kind: CompositeInnerKind::NotUsed,
882 token: Token::Composite(
883 Composite {
884 type_path: "tournament::ls15_components::models::tournament::GatedToken".to_owned(),
885 inners: vec![
886 CompositeInner {
887 index: 0,
888 name: "token".to_owned(),
889 kind: CompositeInnerKind::NotUsed,
890 token: Token::CoreBasic(
891 CoreBasic {
892 type_path: "core::starknet::contract_address::ContractAddress".to_owned(),
893 },
894 ),
895 },
896 CompositeInner {
897 index: 1,
898 name: "entry_type".to_owned(),
899 kind: CompositeInnerKind::NotUsed,
900 token: Token::Composite(
901 Composite {
902 type_path: "tournament::ls15_components::models::tournament::GatedEntryType".to_owned(),
903 inners: vec![],
904 generic_args: vec![],
905 r#type: CompositeType::Unknown,
906 is_event: false,
907 alias: None,
908 },
909 ),
910 },
911 ],
912 generic_args: vec![],
913 r#type: CompositeType::Struct,
914 is_event: false,
915 alias: None,
916 },
917 ),
918 },
919 CompositeInner {
920 index: 1,
921 name: "tournament".to_owned(),
922 kind: CompositeInnerKind::NotUsed,
923 token: Token::Array(
924 Array {
925 type_path: "core::array::Span::<core::integer::u64>".to_owned(),
926 inner: Box::new(Token::CoreBasic(
927 CoreBasic {
928 type_path: "core::integer::u64".to_owned(),
929 },
930 )),
931 is_legacy: false,
932 },
933 ),
934 },
935 CompositeInner {
936 index: 2,
937 name: "address".to_owned(),
938 kind: CompositeInnerKind::NotUsed,
939 token: Token::Array(
940 Array {
941 type_path: "core::array::Span::<core::starknet::contract_address::ContractAddress>".to_owned(),
942 inner: Box::new(
943 Token::CoreBasic(
944 CoreBasic {
945 type_path: "core::starknet::contract_address::ContractAddress".to_owned(),
946 },
947 )
948 ),
949 is_legacy: false,
950 },
951 ),
952 },
953 ],
954 generic_args: vec![],
955 r#type: CompositeType::Enum,
956 is_event: false,
957 alias: None,
958} )],
959 );
960 input.insert(
961 "tournament::ls15_components::models::tournament::TournamentModelValue".to_owned(),
962 vec![Token::Composite(Composite {
963 type_path: "tournament::ls15_components::models::tournament::TournamentModelValue"
964 .to_owned(),
965 inners: vec![CompositeInner {
966 index: 0,
967 name: "gated_type".to_owned(),
968 kind: CompositeInnerKind::NotUsed,
969 token: Token::Composite(Composite { type_path: "core::option::Option::<tournament::ls15_components::models::tournament::GatedType>".to_owned(), inners: vec![], generic_args: vec![
970 ("A".to_owned(), Token::Composite(Composite { type_path: "tournament::ls15_components::models::tournament::GatedType".to_owned(), inners: vec![], generic_args: vec![], r#type: CompositeType::Unknown, is_event: false, alias: None })),
971 ], r#type: CompositeType::Unknown, is_event: false, alias: None }),
972 }],
973 generic_args: vec![],
974 r#type: CompositeType::Struct,
975 is_event: false,
976 alias: None,
977 })],
978 );
979
980 let filtered = AbiParser::filter_struct_enum_tokens(input);
981 let tmv = filtered
982 .get("tournament::ls15_components::models::tournament::TournamentModelValue")
983 .unwrap()
984 .to_composite()
985 .unwrap();
986 if let Token::Composite(c) = &tmv.inners[0].token {
987 if let Token::Composite(cc) = &c.generic_args[0].1 {
988 assert_ne!(0, cc.inners.len());
991 } else {
992 panic!("Expected composite");
993 }
994 } else {
995 panic!("Expected composite");
996 }
997 }
998 #[test]
999 fn test_deep_nested_hydration() {
1000 let mut input: HashMap<String, Vec<Token>> = HashMap::new();
1001 input.insert(
1002 "tournament::ls15_components::models::loot_survivor::Item".to_owned(),
1003 vec![Token::Composite(Composite {
1004 type_path: "tournament::ls15_components::models::loot_survivor::Item".to_owned(),
1005 inners: vec![
1006 CompositeInner {
1007 index: 0,
1008 name: "id".to_owned(),
1009 kind: CompositeInnerKind::NotUsed,
1010 token: Token::CoreBasic(CoreBasic {
1011 type_path: "core::integer::u8".to_owned(),
1012 }),
1013 },
1014 CompositeInner {
1015 index: 1,
1016 name: "name".to_owned(),
1017 kind: CompositeInnerKind::NotUsed,
1018 token: Token::CoreBasic(CoreBasic {
1019 type_path: "core::integer::u16".to_owned(),
1020 }),
1021 },
1022 ],
1023 generic_args: vec![],
1024 r#type: CompositeType::Struct,
1025 is_event: false,
1026 alias: None,
1027 })],
1028 );
1029 input.insert(
1030 "tournament::ls15_components::models::loot_survivor::Equipment".to_owned(),
1031 vec![Token::Composite(Composite {
1032 type_path: "tournament::ls15_components::models::loot_survivor::Equipment"
1033 .to_owned(),
1034 inners: vec![
1035 CompositeInner {
1036 index: 0,
1037 name: "weapon".to_owned(),
1038 kind: CompositeInnerKind::NotUsed,
1039 token: Token::Composite(Composite {
1040 type_path: "tournament::ls15_components::models::loot_survivor::Item"
1041 .to_owned(),
1042 inners: vec![],
1043 generic_args: vec![],
1044 r#type: CompositeType::Unknown,
1045 is_event: false,
1046 alias: None,
1047 }),
1048 },
1049 CompositeInner {
1050 index: 1,
1051 name: "chest".to_owned(),
1052 kind: CompositeInnerKind::NotUsed,
1053 token: Token::Composite(Composite {
1054 type_path: "tournament::ls15_components::models::loot_survivor::Item"
1055 .to_owned(),
1056 inners: vec![],
1057 generic_args: vec![],
1058 r#type: CompositeType::Unknown,
1059 is_event: false,
1060 alias: None,
1061 }),
1062 },
1063 ],
1064 generic_args: vec![],
1065 r#type: CompositeType::Struct,
1066 is_event: false,
1067 alias: None,
1068 })],
1069 );
1070 input.insert(
1071 "tournament::ls15_components::models::loot_survivor::Adventurer".to_owned(),
1072 vec![Token::Composite(Composite {
1073 type_path: "tournament::ls15_components::models::loot_survivor::Adventurer"
1074 .to_owned(),
1075 inners: vec![CompositeInner {
1076 index: 0,
1077 name: "equipment".to_owned(),
1078 kind: CompositeInnerKind::NotUsed,
1079 token: Token::Composite(Composite {
1080 type_path: "tournament::ls15_components::models::loot_survivor::Equipment"
1081 .to_owned(),
1082 inners: vec![],
1083 generic_args: vec![],
1084 r#type: CompositeType::Unknown,
1085 is_event: false,
1086 alias: None,
1087 }),
1088 }],
1089 generic_args: vec![],
1090 r#type: CompositeType::Struct,
1091 is_event: false,
1092 alias: None,
1093 })],
1094 );
1095 input.insert(
1096 "tournament::ls15_components::models::loot_survivor::AdventurerModel".to_owned(),
1097 vec![Token::Composite(Composite {
1098 type_path: "tournament::ls15_components::models::loot_survivor::AdventurerModel"
1099 .to_owned(),
1100 inners: vec![
1101 CompositeInner {
1102 index: 0,
1103 name: "adventurer_id".to_owned(),
1104 kind: CompositeInnerKind::NotUsed,
1105 token: Token::CoreBasic(CoreBasic {
1106 type_path: "core::felt252".to_owned(),
1107 }),
1108 },
1109 CompositeInner {
1110 index: 1,
1111 name: "adventurer".to_owned(),
1112 kind: CompositeInnerKind::NotUsed,
1113 token: Token::Composite(Composite {
1114 type_path:
1115 "tournament::ls15_components::models::loot_survivor::Adventurer"
1116 .to_owned(),
1117 inners: vec![],
1118 generic_args: vec![],
1119 r#type: CompositeType::Unknown,
1120 is_event: false,
1121 alias: None,
1122 }),
1123 },
1124 ],
1125 generic_args: vec![],
1126 r#type: CompositeType::Struct,
1127 is_event: false,
1128 alias: None,
1129 })],
1130 );
1131
1132 let filtered = AbiParser::filter_struct_enum_tokens(input);
1133 fn check_token_inners(token: &Token) {
1134 if let Token::Composite(c) = token {
1137 assert_ne!(0, c.inners.len());
1138 c.inners.iter().for_each(|i| check_token_inners(&i.token));
1140 }
1141 }
1142 filtered.iter().for_each(|(_, t)| check_token_inners(t));
1143 }
1144
1145 #[test]
1146 fn test_collect_tokens() {
1147 let sierra_abi = include_str!("../../test_data/cairo_ls_abi.json");
1148 let sierra = serde_json::from_str::<SierraClass>(sierra_abi).unwrap();
1149 let tokens = AbiParser::collect_tokens(&sierra.abi, &HashMap::new()).unwrap();
1150 assert_ne!(tokens.enums.len(), 0);
1151 assert_ne!(tokens.functions.len(), 0);
1152 assert_ne!(tokens.interfaces.len(), 0);
1153 assert_ne!(tokens.structs.len(), 0);
1154 }
1155}