solana_transaction_status_wasm/
parse_bpf_loader.rs

1use base64::Engine;
2use base64::prelude::BASE64_STANDARD;
3use bincode::deserialize;
4use serde_json::json;
5use solana_loader_v2_interface::LoaderInstruction;
6use solana_loader_v3_interface::instruction::UpgradeableLoaderInstruction;
7use solana_message::AccountKeys;
8use solana_message::compiled_instruction::CompiledInstruction;
9
10use crate::parse_instruction::ParsableProgram;
11use crate::parse_instruction::ParseInstructionError;
12use crate::parse_instruction::ParsedInstructionEnum;
13use crate::parse_instruction::check_num_accounts;
14
15pub fn parse_bpf_loader(
16	instruction: &CompiledInstruction,
17	account_keys: &AccountKeys,
18) -> Result<ParsedInstructionEnum, ParseInstructionError> {
19	let bpf_loader_instruction: LoaderInstruction = deserialize(&instruction.data)
20		.map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::BpfLoader))?;
21	if instruction.accounts.is_empty() || instruction.accounts[0] as usize >= account_keys.len() {
22		return Err(ParseInstructionError::InstructionKeyMismatch(
23			ParsableProgram::BpfLoader,
24		));
25	}
26	match bpf_loader_instruction {
27		LoaderInstruction::Write { offset, bytes } => {
28			check_num_bpf_loader_accounts(&instruction.accounts, 1)?;
29			Ok(ParsedInstructionEnum {
30				instruction_type: "write".to_string(),
31				info: json!({
32					"offset": offset,
33					"bytes": BASE64_STANDARD.encode(bytes),
34					"account": account_keys[instruction.accounts[0] as usize].to_string(),
35				}),
36			})
37		}
38		LoaderInstruction::Finalize => {
39			check_num_bpf_loader_accounts(&instruction.accounts, 2)?;
40			Ok(ParsedInstructionEnum {
41				instruction_type: "finalize".to_string(),
42				info: json!({
43					"account": account_keys[instruction.accounts[0] as usize].to_string(),
44				}),
45			})
46		}
47	}
48}
49
50pub fn parse_bpf_upgradeable_loader(
51	instruction: &CompiledInstruction,
52	account_keys: &AccountKeys,
53) -> Result<ParsedInstructionEnum, ParseInstructionError> {
54	let bpf_upgradeable_loader_instruction: UpgradeableLoaderInstruction =
55		deserialize(&instruction.data).map_err(|_| {
56			ParseInstructionError::InstructionNotParsable(ParsableProgram::BpfUpgradeableLoader)
57		})?;
58	match instruction.accounts.iter().max() {
59		Some(index) if (*index as usize) < account_keys.len() => {}
60		_ => {
61			// Runtime should prevent this from ever happening
62			return Err(ParseInstructionError::InstructionKeyMismatch(
63				ParsableProgram::BpfUpgradeableLoader,
64			));
65		}
66	}
67	match bpf_upgradeable_loader_instruction {
68		UpgradeableLoaderInstruction::InitializeBuffer => {
69			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 1)?;
70			let mut value = json!({
71				"account": account_keys[instruction.accounts[0] as usize].to_string(),
72			});
73			let map = value.as_object_mut().unwrap();
74			if instruction.accounts.len() > 1 {
75				map.insert(
76					"authority".to_string(),
77					json!(account_keys[instruction.accounts[1] as usize].to_string()),
78				);
79			}
80			Ok(ParsedInstructionEnum {
81				instruction_type: "initializeBuffer".to_string(),
82				info: value,
83			})
84		}
85		UpgradeableLoaderInstruction::Write { offset, bytes } => {
86			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 2)?;
87			Ok(ParsedInstructionEnum {
88				instruction_type: "write".to_string(),
89				info: json!({
90					"offset": offset,
91					"bytes": BASE64_STANDARD.encode(bytes),
92					"account": account_keys[instruction.accounts[0] as usize].to_string(),
93					"authority": account_keys[instruction.accounts[1] as usize].to_string(),
94				}),
95			})
96		}
97		UpgradeableLoaderInstruction::DeployWithMaxDataLen { max_data_len } => {
98			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 8)?;
99			Ok(ParsedInstructionEnum {
100				instruction_type: "deployWithMaxDataLen".to_string(),
101				info: json!({
102					"maxDataLen": max_data_len,
103					"payerAccount": account_keys[instruction.accounts[0] as usize].to_string(),
104					"programDataAccount": account_keys[instruction.accounts[1] as usize].to_string(),
105					"programAccount": account_keys[instruction.accounts[2] as usize].to_string(),
106					"bufferAccount": account_keys[instruction.accounts[3] as usize].to_string(),
107					"rentSysvar": account_keys[instruction.accounts[4] as usize].to_string(),
108					"clockSysvar": account_keys[instruction.accounts[5] as usize].to_string(),
109					"systemProgram": account_keys[instruction.accounts[6] as usize].to_string(),
110					"authority": account_keys[instruction.accounts[7] as usize].to_string(),
111				}),
112			})
113		}
114		UpgradeableLoaderInstruction::Upgrade => {
115			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 7)?;
116			Ok(ParsedInstructionEnum {
117				instruction_type: "upgrade".to_string(),
118				info: json!({
119					"programDataAccount": account_keys[instruction.accounts[0] as usize].to_string(),
120					"programAccount": account_keys[instruction.accounts[1] as usize].to_string(),
121					"bufferAccount": account_keys[instruction.accounts[2] as usize].to_string(),
122					"spillAccount": account_keys[instruction.accounts[3] as usize].to_string(),
123					"rentSysvar": account_keys[instruction.accounts[4] as usize].to_string(),
124					"clockSysvar": account_keys[instruction.accounts[5] as usize].to_string(),
125					"authority": account_keys[instruction.accounts[6] as usize].to_string(),
126				}),
127			})
128		}
129		UpgradeableLoaderInstruction::SetAuthority => {
130			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 2)?;
131			Ok(ParsedInstructionEnum {
132				instruction_type: "setAuthority".to_string(),
133				info: json!({
134					"account": account_keys[instruction.accounts[0] as usize].to_string(),
135					"authority": account_keys[instruction.accounts[1] as usize].to_string(),
136					"newAuthority": if instruction.accounts.len() > 2 {
137						Some(account_keys[instruction.accounts[2] as usize].to_string())
138					} else {
139						None
140					},
141				}),
142			})
143		}
144		UpgradeableLoaderInstruction::SetAuthorityChecked => {
145			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 3)?;
146			Ok(ParsedInstructionEnum {
147				instruction_type: "setAuthorityChecked".to_string(),
148				info: json!({
149					"account": account_keys[instruction.accounts[0] as usize].to_string(),
150					"authority": account_keys[instruction.accounts[1] as usize].to_string(),
151					"newAuthority": account_keys[instruction.accounts[2] as usize].to_string(),
152				}),
153			})
154		}
155		UpgradeableLoaderInstruction::Close => {
156			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 3)?;
157			Ok(ParsedInstructionEnum {
158				instruction_type: "close".to_string(),
159				info: json!({
160					"account": account_keys[instruction.accounts[0] as usize].to_string(),
161					"recipient": account_keys[instruction.accounts[1] as usize].to_string(),
162					"authority": account_keys[instruction.accounts[2] as usize].to_string(),
163					"programAccount": if instruction.accounts.len() > 3 {
164						Some(account_keys[instruction.accounts[3] as usize].to_string())
165					} else {
166						None
167					}
168				}),
169			})
170		}
171		UpgradeableLoaderInstruction::ExtendProgram { additional_bytes } => {
172			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 2)?;
173			Ok(ParsedInstructionEnum {
174				instruction_type: "extendProgram".to_string(),
175				info: json!({
176					"additionalBytes": additional_bytes,
177					"programDataAccount": account_keys[instruction.accounts[0] as usize].to_string(),
178					"programAccount": account_keys[instruction.accounts[1] as usize].to_string(),
179					"systemProgram": if instruction.accounts.len() > 2 {
180						Some(account_keys[instruction.accounts[2] as usize].to_string())
181					} else {
182						None
183					},
184					"payerAccount": if instruction.accounts.len() > 3 {
185						Some(account_keys[instruction.accounts[3] as usize].to_string())
186					} else {
187						None
188					},
189				}),
190			})
191		}
192		UpgradeableLoaderInstruction::Migrate => {
193			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 3)?;
194			Ok(ParsedInstructionEnum {
195				instruction_type: "migrate".to_string(),
196				info: json!({
197					"programDataAccount": account_keys[instruction.accounts[0] as usize].to_string(),
198					"programAccount": account_keys[instruction.accounts[1] as usize].to_string(),
199					"authority": account_keys[instruction.accounts[2] as usize].to_string(),
200				}),
201			})
202		}
203		UpgradeableLoaderInstruction::ExtendProgramChecked { additional_bytes } => {
204			check_num_bpf_upgradeable_loader_accounts(&instruction.accounts, 3)?;
205			Ok(ParsedInstructionEnum {
206				instruction_type: "extendProgramChecked".to_string(),
207				info: json!({
208					"additionalBytes": additional_bytes,
209					"programDataAccount": account_keys[instruction.accounts[0] as usize].to_string(),
210					"programAccount": account_keys[instruction.accounts[1] as usize].to_string(),
211					"authority": account_keys[instruction.accounts[2] as usize].to_string(),
212					"systemProgram": if instruction.accounts.len() > 3 {
213						Some(account_keys[instruction.accounts[3] as usize].to_string())
214					} else {
215						None
216					},
217					"payerAccount": if instruction.accounts.len() > 4 {
218						Some(account_keys[instruction.accounts[4] as usize].to_string())
219					} else {
220						None
221					},
222				}),
223			})
224		}
225	}
226}
227
228fn check_num_bpf_loader_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInstructionError> {
229	check_num_accounts(accounts, num, ParsableProgram::BpfLoader)
230}
231
232fn check_num_bpf_upgradeable_loader_accounts(
233	accounts: &[u8],
234	num: usize,
235) -> Result<(), ParseInstructionError> {
236	check_num_accounts(accounts, num, ParsableProgram::BpfUpgradeableLoader)
237}
238
239#[cfg(test)]
240mod test {
241	use serde_json::Value;
242	use solana_loader_v3_interface::instruction as bpf_loader_upgradeable;
243	use solana_message::Message;
244	use solana_pubkey::Pubkey;
245	use solana_pubkey::{self as pubkey};
246	use solana_sdk_ids::system_program;
247	use solana_sdk_ids::sysvar;
248
249	use super::*;
250
251	#[test]
252	fn test_parse_bpf_loader_instructions() {
253		let account_pubkey = pubkey::new_rand();
254		let program_id = pubkey::new_rand();
255		let offset = 4242;
256		let bytes = vec![8; 99];
257		let fee_payer = pubkey::new_rand();
258		let account_keys = vec![fee_payer, account_pubkey];
259		let missing_account_keys = vec![account_pubkey];
260
261		#[allow(deprecated)]
262		let instruction =
263			solana_loader_v2_interface::write(&account_pubkey, &program_id, offset, bytes.clone());
264		let mut message = Message::new(&[instruction], Some(&fee_payer));
265		assert_eq!(
266			parse_bpf_loader(
267				&message.instructions[0],
268				&AccountKeys::new(&account_keys, None)
269			)
270			.unwrap(),
271			ParsedInstructionEnum {
272				instruction_type: "write".to_string(),
273				info: json!({
274					"offset": offset,
275					"bytes": BASE64_STANDARD.encode(&bytes),
276					"account": account_pubkey.to_string(),
277				}),
278			}
279		);
280		assert!(
281			parse_bpf_loader(
282				&message.instructions[0],
283				&AccountKeys::new(&missing_account_keys, None)
284			)
285			.is_err()
286		);
287		message.instructions[0].accounts.pop();
288		assert!(
289			parse_bpf_loader(
290				&message.instructions[0],
291				&AccountKeys::new(&account_keys, None)
292			)
293			.is_err()
294		);
295
296		#[allow(deprecated)]
297		let instruction = solana_loader_v2_interface::finalize(&account_pubkey, &program_id);
298		let mut message = Message::new(&[instruction], Some(&fee_payer));
299		assert_eq!(
300			parse_bpf_loader(
301				&message.instructions[0],
302				&AccountKeys::new(&account_keys, None)
303			)
304			.unwrap(),
305			ParsedInstructionEnum {
306				instruction_type: "finalize".to_string(),
307				info: json!({
308					"account": account_pubkey.to_string(),
309				}),
310			}
311		);
312		assert!(
313			parse_bpf_loader(
314				&message.instructions[0],
315				&AccountKeys::new(&missing_account_keys, None)
316			)
317			.is_err()
318		);
319		message.instructions[0].accounts.pop();
320		assert!(
321			parse_bpf_loader(
322				&message.instructions[0],
323				&AccountKeys::new(&account_keys, None)
324			)
325			.is_err()
326		);
327
328		let bad_compiled_instruction = CompiledInstruction {
329			program_id_index: 3,
330			accounts: vec![1, 2],
331			data: vec![2, 0, 0, 0], // LoaderInstruction enum only has 2 variants
332		};
333		assert!(
334			parse_bpf_loader(
335				&bad_compiled_instruction,
336				&AccountKeys::new(&account_keys, None)
337			)
338			.is_err()
339		);
340
341		let bad_compiled_instruction = CompiledInstruction {
342			program_id_index: 3,
343			accounts: vec![],
344			data: vec![1, 0, 0, 0],
345		};
346		assert!(
347			parse_bpf_loader(
348				&bad_compiled_instruction,
349				&AccountKeys::new(&account_keys, None)
350			)
351			.is_err()
352		);
353	}
354
355	#[test]
356	fn test_parse_bpf_upgradeable_loader_create_buffer_ix() {
357		let max_data_len = 54321;
358
359		let payer_address = Pubkey::new_unique();
360		let buffer_address = Pubkey::new_unique();
361		let authority_address = Pubkey::new_unique();
362		let instructions = bpf_loader_upgradeable::create_buffer(
363			&payer_address,
364			&buffer_address,
365			&authority_address,
366			55,
367			max_data_len,
368		)
369		.unwrap();
370		let mut message = Message::new(&instructions, None);
371		assert_eq!(
372			parse_bpf_upgradeable_loader(
373				&message.instructions[1],
374				&AccountKeys::new(&message.account_keys, None)
375			)
376			.unwrap(),
377			ParsedInstructionEnum {
378				instruction_type: "initializeBuffer".to_string(),
379				info: json!({
380					"account": buffer_address.to_string(),
381					"authority": authority_address.to_string(),
382				}),
383			}
384		);
385		assert!(
386			parse_bpf_upgradeable_loader(
387				&message.instructions[1],
388				&AccountKeys::new(&message.account_keys[0..2], None)
389			)
390			.is_err()
391		);
392		let keys = message.account_keys.clone();
393		message.instructions[1].accounts.pop();
394		message.instructions[1].accounts.pop();
395		assert!(
396			parse_bpf_upgradeable_loader(&message.instructions[1], &AccountKeys::new(&keys, None))
397				.is_err()
398		);
399	}
400
401	#[test]
402	fn test_parse_bpf_upgradeable_loader_write_ix() {
403		let offset = 4242;
404		let bytes = vec![8; 99];
405
406		let buffer_address = Pubkey::new_unique();
407		let authority_address = Pubkey::new_unique();
408		let instruction = bpf_loader_upgradeable::write(
409			&buffer_address,
410			&authority_address,
411			offset,
412			bytes.clone(),
413		);
414		let mut message = Message::new(&[instruction], None);
415		assert_eq!(
416			parse_bpf_upgradeable_loader(
417				&message.instructions[0],
418				&AccountKeys::new(&message.account_keys, None)
419			)
420			.unwrap(),
421			ParsedInstructionEnum {
422				instruction_type: "write".to_string(),
423				info: json!({
424					"offset": offset,
425					"bytes": BASE64_STANDARD.encode(&bytes),
426					"account": buffer_address.to_string(),
427					"authority": authority_address.to_string(),
428				}),
429			}
430		);
431		assert!(
432			parse_bpf_upgradeable_loader(
433				&message.instructions[0],
434				&AccountKeys::new(&message.account_keys[0..1], None)
435			)
436			.is_err()
437		);
438		let keys = message.account_keys.clone();
439		message.instructions[0].accounts.pop();
440		assert!(
441			parse_bpf_upgradeable_loader(&message.instructions[0], &AccountKeys::new(&keys, None))
442				.is_err()
443		);
444	}
445
446	#[test]
447	fn test_parse_bpf_upgradeable_loader_deploy_ix() {
448		let max_data_len = 54321;
449
450		let payer_address = Pubkey::new_unique();
451		let program_address = Pubkey::new_unique();
452		let buffer_address = Pubkey::new_unique();
453		let upgrade_authority_address = Pubkey::new_unique();
454		let programdata_address = Pubkey::find_program_address(
455			&[program_address.as_ref()],
456			&solana_sdk_ids::bpf_loader_upgradeable::id(),
457		)
458		.0;
459		#[allow(deprecated)]
460		let instructions = bpf_loader_upgradeable::deploy_with_max_program_len(
461			&payer_address,
462			&program_address,
463			&buffer_address,
464			&upgrade_authority_address,
465			55,
466			max_data_len,
467		)
468		.unwrap();
469		let mut message = Message::new(&instructions, None);
470		assert_eq!(
471			parse_bpf_upgradeable_loader(
472				&message.instructions[1],
473				&AccountKeys::new(&message.account_keys, None)
474			)
475			.unwrap(),
476			ParsedInstructionEnum {
477				instruction_type: "deployWithMaxDataLen".to_string(),
478				info: json!({
479					"maxDataLen": max_data_len,
480					"payerAccount": payer_address.to_string(),
481					"programAccount": program_address.to_string(),
482					"authority": upgrade_authority_address.to_string(),
483					"programDataAccount": programdata_address.to_string(),
484					"bufferAccount": buffer_address.to_string(),
485					"rentSysvar": sysvar::rent::ID.to_string(),
486					"clockSysvar": sysvar::clock::ID.to_string(),
487					"systemProgram": system_program::ID.to_string(),
488				}),
489			}
490		);
491		assert!(
492			parse_bpf_upgradeable_loader(
493				&message.instructions[1],
494				&AccountKeys::new(&message.account_keys[0..7], None)
495			)
496			.is_err()
497		);
498		let keys = message.account_keys.clone();
499		message.instructions[1].accounts.pop();
500		assert!(
501			parse_bpf_upgradeable_loader(&message.instructions[1], &AccountKeys::new(&keys, None))
502				.is_err()
503		);
504	}
505
506	#[test]
507	fn test_parse_bpf_upgradeable_loader_upgrade_ix() {
508		let program_address = Pubkey::new_unique();
509		let buffer_address = Pubkey::new_unique();
510		let authority_address = Pubkey::new_unique();
511		let spill_address = Pubkey::new_unique();
512		let programdata_address = Pubkey::find_program_address(
513			&[program_address.as_ref()],
514			&solana_sdk_ids::bpf_loader_upgradeable::id(),
515		)
516		.0;
517		let instruction = bpf_loader_upgradeable::upgrade(
518			&program_address,
519			&buffer_address,
520			&authority_address,
521			&spill_address,
522		);
523		let mut message = Message::new(&[instruction], None);
524		assert_eq!(
525			parse_bpf_upgradeable_loader(
526				&message.instructions[0],
527				&AccountKeys::new(&message.account_keys, None)
528			)
529			.unwrap(),
530			ParsedInstructionEnum {
531				instruction_type: "upgrade".to_string(),
532				info: json!({
533					"authority": authority_address.to_string(),
534					"programDataAccount": programdata_address.to_string(),
535					"programAccount": program_address.to_string(),
536					"bufferAccount": buffer_address.to_string(),
537					"spillAccount": spill_address.to_string(),
538					"rentSysvar": sysvar::rent::ID.to_string(),
539					"clockSysvar": sysvar::clock::ID.to_string(),
540				}),
541			}
542		);
543		assert!(
544			parse_bpf_upgradeable_loader(
545				&message.instructions[0],
546				&AccountKeys::new(&message.account_keys[0..6], None)
547			)
548			.is_err()
549		);
550		let keys = message.account_keys.clone();
551		message.instructions[0].accounts.pop();
552		assert!(
553			parse_bpf_upgradeable_loader(&message.instructions[0], &AccountKeys::new(&keys, None))
554				.is_err()
555		);
556	}
557
558	#[test]
559	fn test_parse_bpf_upgradeable_loader_set_buffer_authority_ix() {
560		let buffer_address = Pubkey::new_unique();
561		let current_authority_address = Pubkey::new_unique();
562		let new_authority_address = Pubkey::new_unique();
563		let instruction = bpf_loader_upgradeable::set_buffer_authority(
564			&buffer_address,
565			&current_authority_address,
566			&new_authority_address,
567		);
568		let mut message = Message::new(&[instruction], None);
569		assert_eq!(
570			parse_bpf_upgradeable_loader(
571				&message.instructions[0],
572				&AccountKeys::new(&message.account_keys, None)
573			)
574			.unwrap(),
575			ParsedInstructionEnum {
576				instruction_type: "setAuthority".to_string(),
577				info: json!({
578					"account": buffer_address.to_string(),
579					"authority": current_authority_address.to_string(),
580					"newAuthority": new_authority_address.to_string(),
581				}),
582			}
583		);
584		assert!(
585			parse_bpf_upgradeable_loader(
586				&message.instructions[0],
587				&AccountKeys::new(&message.account_keys[0..1], None)
588			)
589			.is_err()
590		);
591		let keys = message.account_keys.clone();
592		message.instructions[0].accounts.pop();
593		message.instructions[0].accounts.pop();
594		assert!(
595			parse_bpf_upgradeable_loader(&message.instructions[0], &AccountKeys::new(&keys, None))
596				.is_err()
597		);
598	}
599
600	#[test]
601	fn test_parse_bpf_upgradeable_loader_set_buffer_authority_checked_ix() {
602		let buffer_address = Pubkey::new_unique();
603		let current_authority_address = Pubkey::new_unique();
604		let new_authority_address = Pubkey::new_unique();
605		let instruction = bpf_loader_upgradeable::set_buffer_authority_checked(
606			&buffer_address,
607			&current_authority_address,
608			&new_authority_address,
609		);
610		let message = Message::new(&[instruction], None);
611		assert_eq!(
612			parse_bpf_upgradeable_loader(
613				&message.instructions[0],
614				&AccountKeys::new(&message.account_keys, None)
615			)
616			.unwrap(),
617			ParsedInstructionEnum {
618				instruction_type: "setAuthorityChecked".to_string(),
619				info: json!({
620					"account": buffer_address.to_string(),
621					"authority": current_authority_address.to_string(),
622					"newAuthority": new_authority_address.to_string(),
623				}),
624			}
625		);
626		assert!(
627			parse_bpf_upgradeable_loader(
628				&message.instructions[0],
629				&AccountKeys::new(&message.account_keys[0..2], None)
630			)
631			.is_err()
632		);
633	}
634
635	#[test]
636	fn test_parse_bpf_upgradeable_loader_set_upgrade_authority_ix() {
637		let program_address = Pubkey::new_unique();
638		let current_authority_address = Pubkey::new_unique();
639		let new_authority_address = Pubkey::new_unique();
640		let (programdata_address, _) = Pubkey::find_program_address(
641			&[program_address.as_ref()],
642			&solana_sdk_ids::bpf_loader_upgradeable::id(),
643		);
644		let instruction = bpf_loader_upgradeable::set_upgrade_authority(
645			&program_address,
646			&current_authority_address,
647			Some(&new_authority_address),
648		);
649		let mut message = Message::new(&[instruction], None);
650		assert_eq!(
651			parse_bpf_upgradeable_loader(
652				&message.instructions[0],
653				&AccountKeys::new(&message.account_keys, None)
654			)
655			.unwrap(),
656			ParsedInstructionEnum {
657				instruction_type: "setAuthority".to_string(),
658				info: json!({
659					"account": programdata_address.to_string(),
660					"authority": current_authority_address.to_string(),
661					"newAuthority": new_authority_address.to_string(),
662				}),
663			}
664		);
665		assert!(
666			parse_bpf_upgradeable_loader(
667				&message.instructions[0],
668				&AccountKeys::new(&message.account_keys[0..1], None)
669			)
670			.is_err()
671		);
672		let keys = message.account_keys.clone();
673		message.instructions[0].accounts.pop();
674		message.instructions[0].accounts.pop();
675		assert!(
676			parse_bpf_upgradeable_loader(&message.instructions[0], &AccountKeys::new(&keys, None))
677				.is_err()
678		);
679
680		let instruction = bpf_loader_upgradeable::set_upgrade_authority(
681			&program_address,
682			&current_authority_address,
683			None,
684		);
685		let mut message = Message::new(&[instruction], None);
686		assert_eq!(
687			parse_bpf_upgradeable_loader(
688				&message.instructions[0],
689				&AccountKeys::new(&message.account_keys, None)
690			)
691			.unwrap(),
692			ParsedInstructionEnum {
693				instruction_type: "setAuthority".to_string(),
694				info: json!({
695					"account": programdata_address.to_string(),
696					"authority": current_authority_address.to_string(),
697					"newAuthority": Value::Null,
698				}),
699			}
700		);
701		assert!(
702			parse_bpf_upgradeable_loader(
703				&message.instructions[0],
704				&AccountKeys::new(&message.account_keys[0..1], None)
705			)
706			.is_err()
707		);
708		let keys = message.account_keys.clone();
709		message.instructions[0].accounts.pop();
710		assert!(
711			parse_bpf_upgradeable_loader(&message.instructions[0], &AccountKeys::new(&keys, None))
712				.is_err()
713		);
714	}
715
716	#[test]
717	fn test_parse_bpf_upgradeable_loader_set_upgrade_authority_checked_ix() {
718		let program_address = Pubkey::new_unique();
719		let current_authority_address = Pubkey::new_unique();
720		let new_authority_address = Pubkey::new_unique();
721		let (programdata_address, _) = Pubkey::find_program_address(
722			&[program_address.as_ref()],
723			&solana_sdk_ids::bpf_loader_upgradeable::id(),
724		);
725		let instruction = bpf_loader_upgradeable::set_upgrade_authority_checked(
726			&program_address,
727			&current_authority_address,
728			&new_authority_address,
729		);
730		let message = Message::new(&[instruction], None);
731		assert_eq!(
732			parse_bpf_upgradeable_loader(
733				&message.instructions[0],
734				&AccountKeys::new(&message.account_keys, None)
735			)
736			.unwrap(),
737			ParsedInstructionEnum {
738				instruction_type: "setAuthorityChecked".to_string(),
739				info: json!({
740					"account": programdata_address.to_string(),
741					"authority": current_authority_address.to_string(),
742					"newAuthority": new_authority_address.to_string(),
743				}),
744			}
745		);
746
747		assert!(
748			parse_bpf_upgradeable_loader(
749				&message.instructions[0],
750				&AccountKeys::new(&message.account_keys[0..2], None)
751			)
752			.is_err()
753		);
754	}
755
756	#[test]
757	fn test_parse_bpf_upgradeable_loader_close_buffer_ix() {
758		let close_address = Pubkey::new_unique();
759		let recipient_address = Pubkey::new_unique();
760		let authority_address = Pubkey::new_unique();
761		let instruction =
762			bpf_loader_upgradeable::close(&close_address, &recipient_address, &authority_address);
763		let mut message = Message::new(&[instruction], None);
764		assert_eq!(
765			parse_bpf_upgradeable_loader(
766				&message.instructions[0],
767				&AccountKeys::new(&message.account_keys, None)
768			)
769			.unwrap(),
770			ParsedInstructionEnum {
771				instruction_type: "close".to_string(),
772				info: json!({
773					"account": close_address.to_string(),
774					"recipient": recipient_address.to_string(),
775					"authority": authority_address.to_string(),
776					"programAccount": Value::Null
777				}),
778			}
779		);
780		assert!(
781			parse_bpf_upgradeable_loader(
782				&message.instructions[0],
783				&AccountKeys::new(&message.account_keys[0..1], None)
784			)
785			.is_err()
786		);
787		let keys = message.account_keys.clone();
788		message.instructions[0].accounts.pop();
789		assert!(
790			parse_bpf_upgradeable_loader(&message.instructions[0], &AccountKeys::new(&keys, None))
791				.is_err()
792		);
793	}
794
795	#[test]
796	fn test_parse_bpf_upgradeable_loader_close_program_ix() {
797		let close_address = Pubkey::new_unique();
798		let recipient_address = Pubkey::new_unique();
799		let authority_address = Pubkey::new_unique();
800		let program_address = Pubkey::new_unique();
801		let instruction = bpf_loader_upgradeable::close_any(
802			&close_address,
803			&recipient_address,
804			Some(&authority_address),
805			Some(&program_address),
806		);
807		let mut message = Message::new(&[instruction], None);
808		assert_eq!(
809			parse_bpf_upgradeable_loader(
810				&message.instructions[0],
811				&AccountKeys::new(&message.account_keys, None)
812			)
813			.unwrap(),
814			ParsedInstructionEnum {
815				instruction_type: "close".to_string(),
816				info: json!({
817					"account": close_address.to_string(),
818					"recipient": recipient_address.to_string(),
819					"authority": authority_address.to_string(),
820					"programAccount": program_address.to_string()
821				}),
822			}
823		);
824		assert!(
825			parse_bpf_upgradeable_loader(
826				&message.instructions[0],
827				&AccountKeys::new(&message.account_keys[0..1], None)
828			)
829			.is_err()
830		);
831		let keys = message.account_keys.clone();
832		message.instructions[0].accounts.pop();
833		message.instructions[0].accounts.pop();
834		assert!(
835			parse_bpf_upgradeable_loader(&message.instructions[0], &AccountKeys::new(&keys, None))
836				.is_err()
837		);
838	}
839}