solana_transaction_status/
parse_bpf_loader.rs

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