1use {
2 crate::parse_instruction::{
3 check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
4 },
5 bincode::deserialize,
6 serde_json::json,
7 solana_address_lookup_table_interface::instruction::ProgramInstruction,
8 solana_message::{compiled_instruction::CompiledInstruction, AccountKeys},
9};
10
11pub fn parse_address_lookup_table(
12 instruction: &CompiledInstruction,
13 account_keys: &AccountKeys,
14) -> Result<ParsedInstructionEnum, ParseInstructionError> {
15 let address_lookup_table_instruction: ProgramInstruction = deserialize(&instruction.data)
16 .map_err(|_| {
17 ParseInstructionError::InstructionNotParsable(ParsableProgram::AddressLookupTable)
18 })?;
19 match instruction.accounts.iter().max() {
20 Some(index) if (*index as usize) < account_keys.len() => {}
21 _ => {
22 return Err(ParseInstructionError::InstructionKeyMismatch(
24 ParsableProgram::AddressLookupTable,
25 ));
26 }
27 }
28 match address_lookup_table_instruction {
29 ProgramInstruction::CreateLookupTable {
30 recent_slot,
31 bump_seed,
32 } => {
33 check_num_address_lookup_table_accounts(&instruction.accounts, 4)?;
34 Ok(ParsedInstructionEnum {
35 instruction_type: "createLookupTable".to_string(),
36 info: json!({
37 "lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
38 "lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
39 "payerAccount": account_keys[instruction.accounts[2] as usize].to_string(),
40 "systemProgram": account_keys[instruction.accounts[3] as usize].to_string(),
41 "recentSlot": recent_slot,
42 "bumpSeed": bump_seed,
43 }),
44 })
45 }
46 ProgramInstruction::FreezeLookupTable => {
47 check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
48 Ok(ParsedInstructionEnum {
49 instruction_type: "freezeLookupTable".to_string(),
50 info: json!({
51 "lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
52 "lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
53 }),
54 })
55 }
56 ProgramInstruction::ExtendLookupTable { new_addresses } => {
57 check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
58 let new_addresses: Vec<String> = new_addresses
59 .into_iter()
60 .map(|address| address.to_string())
61 .collect();
62 let mut value = json!({
63 "lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
64 "lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
65 "newAddresses": new_addresses,
66 });
67 let map = value.as_object_mut().unwrap();
68 if instruction.accounts.len() >= 4 {
69 map.insert(
70 "payerAccount".to_string(),
71 json!(account_keys[instruction.accounts[2] as usize].to_string()),
72 );
73 map.insert(
74 "systemProgram".to_string(),
75 json!(account_keys[instruction.accounts[3] as usize].to_string()),
76 );
77 }
78 Ok(ParsedInstructionEnum {
79 instruction_type: "extendLookupTable".to_string(),
80 info: value,
81 })
82 }
83 ProgramInstruction::DeactivateLookupTable => {
84 check_num_address_lookup_table_accounts(&instruction.accounts, 2)?;
85 Ok(ParsedInstructionEnum {
86 instruction_type: "deactivateLookupTable".to_string(),
87 info: json!({
88 "lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
89 "lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
90 }),
91 })
92 }
93 ProgramInstruction::CloseLookupTable => {
94 check_num_address_lookup_table_accounts(&instruction.accounts, 3)?;
95 Ok(ParsedInstructionEnum {
96 instruction_type: "closeLookupTable".to_string(),
97 info: json!({
98 "lookupTableAccount": account_keys[instruction.accounts[0] as usize].to_string(),
99 "lookupTableAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
100 "recipient": account_keys[instruction.accounts[2] as usize].to_string(),
101 }),
102 })
103 }
104 }
105}
106
107fn check_num_address_lookup_table_accounts(
108 accounts: &[u8],
109 num: usize,
110) -> Result<(), ParseInstructionError> {
111 check_num_accounts(accounts, num, ParsableProgram::AddressLookupTable)
112}
113
114#[cfg(test)]
115mod test {
116 use {
117 super::*, solana_address_lookup_table_interface::instruction, solana_message::Message,
118 solana_pubkey::Pubkey, solana_sdk_ids::system_program, std::str::FromStr,
119 };
120
121 #[test]
122 fn test_parse_create_address_lookup_table_ix() {
123 let from_pubkey = Pubkey::new_unique();
124 let authority = Pubkey::from_str("HkxY6vXdrKzoCQLmdJ3cYo9534FdZQxzBNWTyrJzzqJM").unwrap();
126 let slot = 42;
127
128 let (instruction, lookup_table_pubkey) =
129 instruction::create_lookup_table(authority, from_pubkey, slot);
130 let mut message = Message::new(&[instruction], None);
131 assert_eq!(
132 parse_address_lookup_table(
133 &message.instructions[0],
134 &AccountKeys::new(&message.account_keys, None)
135 )
136 .unwrap(),
137 ParsedInstructionEnum {
138 instruction_type: "createLookupTable".to_string(),
139 info: json!({
140 "lookupTableAccount": lookup_table_pubkey.to_string(),
141 "lookupTableAuthority": authority.to_string(),
142 "payerAccount": from_pubkey.to_string(),
143 "systemProgram": system_program::id().to_string(),
144 "recentSlot": slot,
145 "bumpSeed": 254,
146 }),
147 }
148 );
149 assert!(parse_address_lookup_table(
150 &message.instructions[0],
151 &AccountKeys::new(&message.account_keys[0..3], None)
152 )
153 .is_err());
154 let keys = message.account_keys.clone();
155 message.instructions[0].accounts.pop();
156 assert!(parse_address_lookup_table(
157 &message.instructions[0],
158 &AccountKeys::new(&keys, None)
159 )
160 .is_err());
161 }
162
163 #[test]
164 fn test_parse_freeze_lookup_table_ix() {
165 let lookup_table_pubkey = Pubkey::new_unique();
166 let authority = Pubkey::new_unique();
167
168 let instruction = instruction::freeze_lookup_table(lookup_table_pubkey, authority);
169 let mut message = Message::new(&[instruction], None);
170 assert_eq!(
171 parse_address_lookup_table(
172 &message.instructions[0],
173 &AccountKeys::new(&message.account_keys, None)
174 )
175 .unwrap(),
176 ParsedInstructionEnum {
177 instruction_type: "freezeLookupTable".to_string(),
178 info: json!({
179 "lookupTableAccount": lookup_table_pubkey.to_string(),
180 "lookupTableAuthority": authority.to_string(),
181 }),
182 }
183 );
184 assert!(parse_address_lookup_table(
185 &message.instructions[0],
186 &AccountKeys::new(&message.account_keys[0..1], None)
187 )
188 .is_err());
189 let keys = message.account_keys.clone();
190 message.instructions[0].accounts.pop();
191 assert!(parse_address_lookup_table(
192 &message.instructions[0],
193 &AccountKeys::new(&keys, None)
194 )
195 .is_err());
196 }
197
198 #[test]
199 fn test_parse_extend_lookup_table_ix() {
200 let lookup_table_pubkey = Pubkey::new_unique();
201 let authority = Pubkey::new_unique();
202 let from_pubkey = Pubkey::new_unique();
203 let no_addresses = vec![];
204 let address0 = Pubkey::new_unique();
205 let address1 = Pubkey::new_unique();
206 let some_addresses = vec![address0, address1];
207
208 let instruction =
210 instruction::extend_lookup_table(lookup_table_pubkey, authority, None, no_addresses);
211 let mut message = Message::new(&[instruction], None);
212 assert_eq!(
213 parse_address_lookup_table(
214 &message.instructions[0],
215 &AccountKeys::new(&message.account_keys, None)
216 )
217 .unwrap(),
218 ParsedInstructionEnum {
219 instruction_type: "extendLookupTable".to_string(),
220 info: json!({
221 "lookupTableAccount": lookup_table_pubkey.to_string(),
222 "lookupTableAuthority": authority.to_string(),
223 "newAddresses": [],
224 }),
225 }
226 );
227 assert!(parse_address_lookup_table(
228 &message.instructions[0],
229 &AccountKeys::new(&message.account_keys[0..1], None)
230 )
231 .is_err());
232 let keys = message.account_keys.clone();
233 message.instructions[0].accounts.pop();
234 assert!(parse_address_lookup_table(
235 &message.instructions[0],
236 &AccountKeys::new(&keys, None)
237 )
238 .is_err());
239
240 let instruction = instruction::extend_lookup_table(
242 lookup_table_pubkey,
243 authority,
244 Some(from_pubkey),
245 some_addresses,
246 );
247 let mut message = Message::new(&[instruction], None);
248 assert_eq!(
249 parse_address_lookup_table(
250 &message.instructions[0],
251 &AccountKeys::new(&message.account_keys, None)
252 )
253 .unwrap(),
254 ParsedInstructionEnum {
255 instruction_type: "extendLookupTable".to_string(),
256 info: json!({
257 "lookupTableAccount": lookup_table_pubkey.to_string(),
258 "lookupTableAuthority": authority.to_string(),
259 "payerAccount": from_pubkey.to_string(),
260 "systemProgram": system_program::id().to_string(),
261 "newAddresses": [
262 address0.to_string(),
263 address1.to_string(),
264 ],
265 }),
266 }
267 );
268 assert!(parse_address_lookup_table(
269 &message.instructions[0],
270 &AccountKeys::new(&message.account_keys[0..1], None)
271 )
272 .is_err());
273 let keys = message.account_keys.clone();
274 message.instructions[0].accounts.pop();
275 message.instructions[0].accounts.pop();
276 message.instructions[0].accounts.pop();
277 assert!(parse_address_lookup_table(
278 &message.instructions[0],
279 &AccountKeys::new(&keys, None)
280 )
281 .is_err());
282 }
283
284 #[test]
285 fn test_parse_deactivate_lookup_table_ix() {
286 let lookup_table_pubkey = Pubkey::new_unique();
287 let authority = Pubkey::new_unique();
288
289 let instruction = instruction::deactivate_lookup_table(lookup_table_pubkey, authority);
290 let mut message = Message::new(&[instruction], None);
291 assert_eq!(
292 parse_address_lookup_table(
293 &message.instructions[0],
294 &AccountKeys::new(&message.account_keys, None)
295 )
296 .unwrap(),
297 ParsedInstructionEnum {
298 instruction_type: "deactivateLookupTable".to_string(),
299 info: json!({
300 "lookupTableAccount": lookup_table_pubkey.to_string(),
301 "lookupTableAuthority": authority.to_string(),
302 }),
303 }
304 );
305 assert!(parse_address_lookup_table(
306 &message.instructions[0],
307 &AccountKeys::new(&message.account_keys[0..1], None)
308 )
309 .is_err());
310 let keys = message.account_keys.clone();
311 message.instructions[0].accounts.pop();
312 assert!(parse_address_lookup_table(
313 &message.instructions[0],
314 &AccountKeys::new(&keys, None)
315 )
316 .is_err());
317 }
318
319 #[test]
320 fn test_parse_close_lookup_table_ix() {
321 let lookup_table_pubkey = Pubkey::new_unique();
322 let authority = Pubkey::new_unique();
323 let recipient = Pubkey::new_unique();
324
325 let instruction =
326 instruction::close_lookup_table(lookup_table_pubkey, authority, recipient);
327 let mut message = Message::new(&[instruction], None);
328 assert_eq!(
329 parse_address_lookup_table(
330 &message.instructions[0],
331 &AccountKeys::new(&message.account_keys, None)
332 )
333 .unwrap(),
334 ParsedInstructionEnum {
335 instruction_type: "closeLookupTable".to_string(),
336 info: json!({
337 "lookupTableAccount": lookup_table_pubkey.to_string(),
338 "lookupTableAuthority": authority.to_string(),
339 "recipient": recipient.to_string(),
340 }),
341 }
342 );
343 assert!(parse_address_lookup_table(
344 &message.instructions[0],
345 &AccountKeys::new(&message.account_keys[0..2], None)
346 )
347 .is_err());
348 let keys = message.account_keys.clone();
349 message.instructions[0].accounts.pop();
350 assert!(parse_address_lookup_table(
351 &message.instructions[0],
352 &AccountKeys::new(&keys, None)
353 )
354 .is_err());
355 }
356}