solana_block_decoder/instruction/
inner_instruction.rs

1
2use {
3    crate::{
4        instruction::CompiledInstruction,
5        errors::{
6            conversion_error::ConversionError,
7        }
8    },
9    serde_derive::{Deserialize, Serialize},
10    solana_transaction_status_client_types::{
11        UiCompiledInstruction,
12        UiInnerInstructions,
13        UiInstruction,
14    },
15    std::{
16        convert::TryFrom,
17    },
18};
19
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21pub struct InnerInstructions {
22    /// Transaction instruction index
23    pub index: u8,
24    /// List of inner instructions
25    pub instructions: Vec<InnerInstruction>,
26}
27
28impl TryFrom<UiInnerInstructions> for InnerInstructions {
29    type Error = ConversionError;
30
31    fn try_from(ui_inner_instructions: UiInnerInstructions) -> Result<Self, Self::Error> {
32        let instructions_result: Result<Vec<_>, _> = ui_inner_instructions
33            .instructions
34            .into_iter()
35            .map(|ix| match ix {
36                UiInstruction::Compiled(ui_compiled) => Ok(InnerInstruction::from(ui_compiled)),
37                _ => Err(ConversionError::UnsupportedInstructionFormat),
38            })
39            .collect();
40
41        match instructions_result {
42            Ok(instructions) => Ok(Self {
43                index: ui_inner_instructions.index,
44                instructions,
45            }),
46            Err(e) => Err(e),
47        }
48    }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
52pub struct InnerInstruction {
53    /// Compiled instruction
54    pub instruction: CompiledInstruction,
55    /// Invocation stack height of the instruction,
56    pub stack_height: Option<u32>,
57}
58
59impl From<UiCompiledInstruction> for InnerInstruction {
60    fn from(ui_compiled_instruction: UiCompiledInstruction) -> Self {
61        Self {
62            instruction: CompiledInstruction::from(ui_compiled_instruction.clone()), // Clone is needed if CompiledInstruction::from consumes its argument
63            stack_height: ui_compiled_instruction.stack_height,
64        }
65    }
66}
67
68impl From<InnerInstructions> for solana_transaction_status_client_types::InnerInstructions {
69    fn from(instr: InnerInstructions) -> Self {
70        Self {
71            index: instr.index,
72            instructions: instr.instructions.into_iter().map(Into::into).collect(),
73        }
74    }
75}
76
77impl From<InnerInstruction> for solana_transaction_status_client_types::InnerInstruction {
78    fn from(instr: InnerInstruction) -> Self {
79        Self {
80            instruction: instr.instruction.into(),
81            stack_height: instr.stack_height,
82        }
83    }
84}
85
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use solana_transaction_status_client_types::{
91        UiCompiledInstruction, UiInnerInstructions, UiInstruction, UiParsedInstruction
92    };
93    use crate::{instruction::CompiledInstruction, errors::conversion_error::ConversionError};
94
95    #[test]
96    fn test_ui_inner_instructions_to_inner_instructions_success() {
97        let ui_compiled_instruction = UiCompiledInstruction {
98            program_id_index: 1,
99            accounts: vec![2, 3],
100            data: "abcd".to_string(),
101            stack_height: Some(5),
102        };
103        let ui_inner_instructions = UiInnerInstructions {
104            index: 0,
105            instructions: vec![UiInstruction::Compiled(ui_compiled_instruction.clone())],
106        };
107
108        let result = InnerInstructions::try_from(ui_inner_instructions);
109        assert!(result.is_ok());
110        let inner_instructions = result.unwrap();
111        assert_eq!(inner_instructions.index, 0);
112        assert_eq!(inner_instructions.instructions.len(), 1);
113        assert_eq!(inner_instructions.instructions[0].stack_height, Some(5));
114        assert_eq!(inner_instructions.instructions[0].instruction.program_id_index, 1);
115    }
116
117    #[test]
118    fn test_ui_inner_instructions_to_inner_instructions_error() {
119        use solana_transaction_status_client_types::{UiParsedInstruction, ParsedInstruction};
120
121        let ui_inner_instructions = UiInnerInstructions {
122            index: 1,
123            instructions: vec![UiInstruction::Parsed(UiParsedInstruction::Parsed(ParsedInstruction {
124                program: "Unsupported".to_string(),
125                parsed: serde_json::Value::Null,
126                program_id: "unsupported_program".to_string(),
127                stack_height: None,
128            }))],
129        };
130
131        let result = InnerInstructions::try_from(ui_inner_instructions);
132        assert!(result.is_err());
133        assert!(matches!(result.unwrap_err(), ConversionError::UnsupportedInstructionFormat));
134    }
135
136    #[test]
137    fn test_ui_compiled_instruction_to_inner_instruction() {
138        use bs58;
139
140        let encoded_data = bs58::encode("data").into_string();
141
142        let ui_compiled_instruction = UiCompiledInstruction {
143            program_id_index: 2,
144            accounts: vec![4, 5, 6],
145            data: encoded_data,
146            stack_height: Some(3),
147        };
148
149        let inner_instruction = InnerInstruction::from(ui_compiled_instruction.clone());
150
151        assert_eq!(inner_instruction.stack_height, Some(3));
152        assert_eq!(inner_instruction.instruction.program_id_index, 2);
153        assert_eq!(inner_instruction.instruction.accounts, vec![4, 5, 6]);
154        assert_eq!(inner_instruction.instruction.data, b"data".to_vec());
155    }
156
157    #[test]
158    fn test_inner_instructions_to_solana_inner_instructions() {
159        let compiled_instruction = CompiledInstruction {
160            program_id_index: 3,
161            accounts: vec![7, 8],
162            data: vec![1, 2, 3],
163        };
164        let inner_instruction = InnerInstruction {
165            instruction: compiled_instruction.clone(),
166            stack_height: Some(10),
167        };
168        let inner_instructions = InnerInstructions {
169            index: 2,
170            instructions: vec![inner_instruction.clone()],
171        };
172
173        let solana_inner_instructions: solana_transaction_status_client_types::InnerInstructions = inner_instructions.into();
174        assert_eq!(solana_inner_instructions.index, 2);
175        assert_eq!(solana_inner_instructions.instructions.len(), 1);
176        assert_eq!(solana_inner_instructions.instructions[0].stack_height, Some(10));
177    }
178
179    #[test]
180    fn test_inner_instruction_to_solana_inner_instruction() {
181        let compiled_instruction = CompiledInstruction {
182            program_id_index: 4,
183            accounts: vec![9, 10],
184            data: vec![4, 5, 6],
185        };
186        let inner_instruction = InnerInstruction {
187            instruction: compiled_instruction.clone(),
188            stack_height: Some(7),
189        };
190
191        let solana_inner_instruction: solana_transaction_status_client_types::InnerInstruction = inner_instruction.into();
192        assert_eq!(solana_inner_instruction.stack_height, Some(7));
193        assert_eq!(solana_inner_instruction.instruction.program_id_index, 4);
194    }
195}
196
197
198