1use arch_program::account::AccountMeta;
2use arch_program::pubkey::Pubkey;
3use {
4 crate::{collection::InstructionDecoderCollection, instruction::DecodedInstruction},
5 serde::de::DeserializeOwned,
6 std::collections::HashMap,
7};
8
9#[derive(Debug, Clone)]
10pub enum SchemaNode<T: InstructionDecoderCollection> {
11 Instruction(InstructionSchemaNode<T>),
12 Any,
13}
14
15#[derive(Debug, Clone)]
16pub struct InstructionSchemaNode<T: InstructionDecoderCollection> {
17 pub ix_type: T::InstructionType,
18 pub name: String,
19}
20
21#[derive(Debug, Clone)]
22pub struct ParsedInstruction<T: InstructionDecoderCollection> {
23 pub program_id: Pubkey,
24 pub instruction: DecodedInstruction<T>,
25 pub inner_instructions: Vec<ParsedInstruction<T>>,
26}
27
28#[derive(Debug, Clone)]
29pub struct TransactionSchema<T: InstructionDecoderCollection> {
30 pub root: Vec<SchemaNode<T>>,
31}
32
33impl<T: InstructionDecoderCollection> TransactionSchema<T> {
34 pub fn match_schema<U>(&self, instructions: &[ParsedInstruction<T>]) -> Option<U>
35 where
36 U: DeserializeOwned,
37 {
38 log::trace!(
39 "Schema::match_schema(self: {:?}, instructions: {:?})",
40 self,
41 instructions
42 );
43 let value = serde_json::to_value(self.match_nodes(instructions)).ok()?;
44
45 log::trace!("Schema::match_schema: deserializing value: {:?}", value);
46 serde_json::from_value::<U>(value).ok()
47 }
48
49 pub fn match_nodes(
50 &self,
51 instructions: &[ParsedInstruction<T>],
52 ) -> Option<HashMap<String, (T, Vec<AccountMeta>)>> {
53 log::trace!(
54 "Schema::match_nodes(self: {:?}, instructions: {:?})",
55 self,
56 instructions
57 );
58 let mut output = HashMap::<String, (T, Vec<AccountMeta>)>::new();
59
60 let mut node_index = 0;
61 let mut instruction_index = 0;
62
63 let mut any = false;
64
65 while let Some(node) = self.root.get(node_index) {
66 log::trace!(
67 "Schema::match_nodes: current node ({}): {:?}",
68 node_index,
69 node
70 );
71
72 if let SchemaNode::Any = node {
73 log::trace!("Schema::match_nodes: Any node detected, skipping");
74 any = true;
75 node_index += 1;
76 continue;
77 }
78
79 let mut matched = false;
80
81 while let Some(current_instruction) = instructions.get(instruction_index) {
82 log::trace!(
83 "Schema::match_nodes: current instruction ({}): {:?}",
84 instruction_index,
85 current_instruction
86 );
87
88 let SchemaNode::Instruction(instruction_node) = node else {
89 return None;
90 };
91
92 if current_instruction.instruction.data.get_type() != instruction_node.ix_type
93 && !any
94 {
95 log::trace!(
96 "Schema::match_nodes: instruction type mismatch, returning (any = false)"
97 );
98 return None;
99 }
100
101 if current_instruction.instruction.data.get_type() != instruction_node.ix_type
102 && any
103 {
104 log::trace!(
105 "Schema::match_nodes: instruction type mismatch, skipping (any = true)"
106 );
107 instruction_index += 1;
108 continue;
109 }
110
111 output.insert(
112 instruction_node.name.clone(),
113 (
114 current_instruction.instruction.data.clone(),
115 current_instruction.instruction.accounts.clone(),
116 ),
117 );
118
119 log::trace!(
120 "Schema::match_nodes: instruction matched, output: {:?}",
121 output
122 );
123
124 instruction_index += 1;
125 node_index += 1;
126 any = false;
127 matched = true;
128 break;
129 }
130
131 if !matched {
132 log::trace!("Schema::match_nodes: node not matched, returning");
133 return None;
134 }
135 }
136
137 log::trace!("Schema::match_nodes: final output: {:?}", output);
138
139 Some(output)
140 }
141}
142
143pub fn merge_hashmaps<K, V>(
144 a: HashMap<K, (V, Vec<AccountMeta>)>,
145 b: HashMap<K, (V, Vec<AccountMeta>)>,
146) -> HashMap<K, (V, Vec<AccountMeta>)>
147where
148 K: std::cmp::Eq + std::hash::Hash,
149{
150 log::trace!("merge_hashmaps(a, b)");
151 let mut output = a;
152 for (key, value) in b {
153 output.insert(key, value);
154 }
155 output
156}