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