solana_transaction_status/
parse_system.rs

1use {
2    crate::parse_instruction::{
3        check_num_accounts, ParsableProgram, ParseInstructionError, ParsedInstructionEnum,
4    },
5    bincode::deserialize,
6    serde_json::json,
7    solana_sdk::{
8        instruction::CompiledInstruction, message::AccountKeys,
9        system_instruction::SystemInstruction,
10    },
11};
12
13pub fn parse_system(
14    instruction: &CompiledInstruction,
15    account_keys: &AccountKeys,
16) -> Result<ParsedInstructionEnum, ParseInstructionError> {
17    let system_instruction: SystemInstruction = deserialize(&instruction.data)
18        .map_err(|_| ParseInstructionError::InstructionNotParsable(ParsableProgram::System))?;
19    match instruction.accounts.iter().max() {
20        Some(index) if (*index as usize) < account_keys.len() => {}
21        _ => {
22            // Runtime should prevent this from ever happening
23            return Err(ParseInstructionError::InstructionKeyMismatch(
24                ParsableProgram::System,
25            ));
26        }
27    }
28    match system_instruction {
29        SystemInstruction::CreateAccount {
30            lamports,
31            space,
32            owner,
33        } => {
34            check_num_system_accounts(&instruction.accounts, 2)?;
35            Ok(ParsedInstructionEnum {
36                instruction_type: "createAccount".to_string(),
37                info: json!({
38                    "source": account_keys[instruction.accounts[0] as usize].to_string(),
39                    "newAccount": account_keys[instruction.accounts[1] as usize].to_string(),
40                    "lamports": lamports,
41                    "space": space,
42                    "owner": owner.to_string(),
43                }),
44            })
45        }
46        SystemInstruction::Assign { owner } => {
47            check_num_system_accounts(&instruction.accounts, 1)?;
48            Ok(ParsedInstructionEnum {
49                instruction_type: "assign".to_string(),
50                info: json!({
51                    "account": account_keys[instruction.accounts[0] as usize].to_string(),
52                    "owner": owner.to_string(),
53                }),
54            })
55        }
56        SystemInstruction::Transfer { lamports } => {
57            check_num_system_accounts(&instruction.accounts, 2)?;
58            Ok(ParsedInstructionEnum {
59                instruction_type: "transfer".to_string(),
60                info: json!({
61                    "source": account_keys[instruction.accounts[0] as usize].to_string(),
62                    "destination": account_keys[instruction.accounts[1] as usize].to_string(),
63                    "lamports": lamports,
64                }),
65            })
66        }
67        SystemInstruction::CreateAccountWithSeed {
68            base,
69            seed,
70            lamports,
71            space,
72            owner,
73        } => {
74            check_num_system_accounts(&instruction.accounts, 2)?;
75            Ok(ParsedInstructionEnum {
76                instruction_type: "createAccountWithSeed".to_string(),
77                info: json!({
78                    "source": account_keys[instruction.accounts[0] as usize].to_string(),
79                    "newAccount": account_keys[instruction.accounts[1] as usize].to_string(),
80                    "base": base.to_string(),
81                    "seed": seed,
82                    "lamports": lamports,
83                    "space": space,
84                    "owner": owner.to_string(),
85                }),
86            })
87        }
88        SystemInstruction::AdvanceNonceAccount => {
89            check_num_system_accounts(&instruction.accounts, 3)?;
90            Ok(ParsedInstructionEnum {
91                instruction_type: "advanceNonce".to_string(),
92                info: json!({
93                    "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(),
94                    "recentBlockhashesSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
95                    "nonceAuthority": account_keys[instruction.accounts[2] as usize].to_string(),
96                }),
97            })
98        }
99        SystemInstruction::WithdrawNonceAccount(lamports) => {
100            check_num_system_accounts(&instruction.accounts, 5)?;
101            Ok(ParsedInstructionEnum {
102                instruction_type: "withdrawFromNonce".to_string(),
103                info: json!({
104                    "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(),
105                    "destination": account_keys[instruction.accounts[1] as usize].to_string(),
106                    "recentBlockhashesSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
107                    "rentSysvar": account_keys[instruction.accounts[3] as usize].to_string(),
108                    "nonceAuthority": account_keys[instruction.accounts[4] as usize].to_string(),
109                    "lamports": lamports,
110                }),
111            })
112        }
113        SystemInstruction::InitializeNonceAccount(authority) => {
114            check_num_system_accounts(&instruction.accounts, 3)?;
115            Ok(ParsedInstructionEnum {
116                instruction_type: "initializeNonce".to_string(),
117                info: json!({
118                    "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(),
119                    "recentBlockhashesSysvar": account_keys[instruction.accounts[1] as usize].to_string(),
120                    "rentSysvar": account_keys[instruction.accounts[2] as usize].to_string(),
121                    "nonceAuthority": authority.to_string(),
122                }),
123            })
124        }
125        SystemInstruction::AuthorizeNonceAccount(authority) => {
126            check_num_system_accounts(&instruction.accounts, 2)?;
127            Ok(ParsedInstructionEnum {
128                instruction_type: "authorizeNonce".to_string(),
129                info: json!({
130                    "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(),
131                    "nonceAuthority": account_keys[instruction.accounts[1] as usize].to_string(),
132                    "newAuthorized": authority.to_string(),
133                }),
134            })
135        }
136        SystemInstruction::UpgradeNonceAccount => {
137            check_num_system_accounts(&instruction.accounts, 1)?;
138            Ok(ParsedInstructionEnum {
139                instruction_type: "upgradeNonce".to_string(),
140                info: json!({
141                    "nonceAccount": account_keys[instruction.accounts[0] as usize].to_string(),
142                }),
143            })
144        }
145        SystemInstruction::Allocate { space } => {
146            check_num_system_accounts(&instruction.accounts, 1)?;
147            Ok(ParsedInstructionEnum {
148                instruction_type: "allocate".to_string(),
149                info: json!({
150                    "account": account_keys[instruction.accounts[0] as usize].to_string(),
151                    "space": space,
152                }),
153            })
154        }
155        SystemInstruction::AllocateWithSeed {
156            base,
157            seed,
158            space,
159            owner,
160        } => {
161            check_num_system_accounts(&instruction.accounts, 2)?;
162            Ok(ParsedInstructionEnum {
163                instruction_type: "allocateWithSeed".to_string(),
164                info: json!({
165                    "account": account_keys[instruction.accounts[0] as usize].to_string(),
166                    "base": base.to_string(),
167                    "seed": seed,
168                    "space": space,
169                    "owner": owner.to_string(),
170                }),
171            })
172        }
173        SystemInstruction::AssignWithSeed { base, seed, owner } => {
174            check_num_system_accounts(&instruction.accounts, 2)?;
175            Ok(ParsedInstructionEnum {
176                instruction_type: "assignWithSeed".to_string(),
177                info: json!({
178                    "account": account_keys[instruction.accounts[0] as usize].to_string(),
179                    "base": base.to_string(),
180                    "seed": seed,
181                    "owner": owner.to_string(),
182                }),
183            })
184        }
185        SystemInstruction::TransferWithSeed {
186            lamports,
187            from_seed,
188            from_owner,
189        } => {
190            check_num_system_accounts(&instruction.accounts, 3)?;
191            Ok(ParsedInstructionEnum {
192                instruction_type: "transferWithSeed".to_string(),
193                info: json!({
194                    "source": account_keys[instruction.accounts[0] as usize].to_string(),
195                    "sourceBase": account_keys[instruction.accounts[1] as usize].to_string(),
196                    "destination": account_keys[instruction.accounts[2] as usize].to_string(),
197                    "lamports": lamports,
198                    "sourceSeed": from_seed,
199                    "sourceOwner": from_owner.to_string(),
200                }),
201            })
202        }
203    }
204}
205
206fn check_num_system_accounts(accounts: &[u8], num: usize) -> Result<(), ParseInstructionError> {
207    check_num_accounts(accounts, num, ParsableProgram::System)
208}
209
210#[cfg(test)]
211mod test {
212    use {
213        super::*,
214        solana_sdk::{message::Message, pubkey::Pubkey, system_instruction, sysvar},
215    };
216
217    #[test]
218    fn test_parse_system_create_account_ix() {
219        let lamports = 55;
220        let space = 128;
221        let from_pubkey = Pubkey::new_unique();
222        let to_pubkey = Pubkey::new_unique();
223        let owner_pubkey = Pubkey::new_unique();
224
225        let instruction = system_instruction::create_account(
226            &from_pubkey,
227            &to_pubkey,
228            lamports,
229            space,
230            &owner_pubkey,
231        );
232        let mut message = Message::new(&[instruction], None);
233        assert_eq!(
234            parse_system(
235                &message.instructions[0],
236                &AccountKeys::new(&message.account_keys, None)
237            )
238            .unwrap(),
239            ParsedInstructionEnum {
240                instruction_type: "createAccount".to_string(),
241                info: json!({
242                    "source": from_pubkey.to_string(),
243                    "newAccount": to_pubkey.to_string(),
244                    "lamports": lamports,
245                    "owner": owner_pubkey.to_string(),
246                    "space": space,
247                }),
248            }
249        );
250        assert!(parse_system(
251            &message.instructions[0],
252            &AccountKeys::new(&message.account_keys[0..1], None)
253        )
254        .is_err());
255        let keys = message.account_keys.clone();
256        message.instructions[0].accounts.pop();
257        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
258    }
259
260    #[test]
261    fn test_parse_system_assign_ix() {
262        let account_pubkey = Pubkey::new_unique();
263        let owner_pubkey = Pubkey::new_unique();
264        let instruction = system_instruction::assign(&account_pubkey, &owner_pubkey);
265        let mut message = Message::new(&[instruction], None);
266        assert_eq!(
267            parse_system(
268                &message.instructions[0],
269                &AccountKeys::new(&message.account_keys, None)
270            )
271            .unwrap(),
272            ParsedInstructionEnum {
273                instruction_type: "assign".to_string(),
274                info: json!({
275                    "account": account_pubkey.to_string(),
276                    "owner": owner_pubkey.to_string(),
277                }),
278            }
279        );
280        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&[], None)).is_err());
281        let keys = message.account_keys.clone();
282        message.instructions[0].accounts.pop();
283        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
284    }
285
286    #[test]
287    fn test_parse_system_transfer_ix() {
288        let lamports = 55;
289        let from_pubkey = Pubkey::new_unique();
290        let to_pubkey = Pubkey::new_unique();
291        let instruction = system_instruction::transfer(&from_pubkey, &to_pubkey, lamports);
292        let mut message = Message::new(&[instruction], None);
293        assert_eq!(
294            parse_system(
295                &message.instructions[0],
296                &AccountKeys::new(&message.account_keys, None)
297            )
298            .unwrap(),
299            ParsedInstructionEnum {
300                instruction_type: "transfer".to_string(),
301                info: json!({
302                    "source": from_pubkey.to_string(),
303                    "destination": to_pubkey.to_string(),
304                    "lamports": lamports,
305                }),
306            }
307        );
308        assert!(parse_system(
309            &message.instructions[0],
310            &AccountKeys::new(&message.account_keys[0..1], None)
311        )
312        .is_err());
313        let keys = message.account_keys.clone();
314        message.instructions[0].accounts.pop();
315        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
316    }
317
318    #[test]
319    fn test_parse_system_create_account_with_seed_ix() {
320        let lamports = 55;
321        let space = 128;
322        let seed = "test_seed";
323        let from_pubkey = Pubkey::new_unique();
324        let to_pubkey = Pubkey::new_unique();
325        let base_pubkey = Pubkey::new_unique();
326        let owner_pubkey = Pubkey::new_unique();
327        let instruction = system_instruction::create_account_with_seed(
328            &from_pubkey,
329            &to_pubkey,
330            &base_pubkey,
331            seed,
332            lamports,
333            space,
334            &owner_pubkey,
335        );
336        let mut message = Message::new(&[instruction], None);
337        assert_eq!(
338            parse_system(
339                &message.instructions[0],
340                &AccountKeys::new(&message.account_keys, None)
341            )
342            .unwrap(),
343            ParsedInstructionEnum {
344                instruction_type: "createAccountWithSeed".to_string(),
345                info: json!({
346                    "source": from_pubkey.to_string(),
347                    "newAccount": to_pubkey.to_string(),
348                    "lamports": lamports,
349                    "base": base_pubkey.to_string(),
350                    "seed": seed,
351                    "owner": owner_pubkey.to_string(),
352                    "space": space,
353                }),
354            }
355        );
356
357        assert!(parse_system(
358            &message.instructions[0],
359            &AccountKeys::new(&message.account_keys[0..1], None)
360        )
361        .is_err());
362        let keys = message.account_keys.clone();
363        message.instructions[0].accounts.pop();
364        message.instructions[0].accounts.pop();
365        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
366    }
367
368    #[test]
369    fn test_parse_system_allocate_ix() {
370        let space = 128;
371        let account_pubkey = Pubkey::new_unique();
372        let instruction = system_instruction::allocate(&account_pubkey, space);
373        let mut message = Message::new(&[instruction], None);
374        assert_eq!(
375            parse_system(
376                &message.instructions[0],
377                &AccountKeys::new(&message.account_keys, None)
378            )
379            .unwrap(),
380            ParsedInstructionEnum {
381                instruction_type: "allocate".to_string(),
382                info: json!({
383                    "account": account_pubkey.to_string(),
384                    "space": space,
385                }),
386            }
387        );
388        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&[], None)).is_err());
389        let keys = message.account_keys.clone();
390        message.instructions[0].accounts.pop();
391        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
392    }
393
394    #[test]
395    fn test_parse_system_allocate_with_seed_ix() {
396        let space = 128;
397        let seed = "test_seed";
398        let account_pubkey = Pubkey::new_unique();
399        let base_pubkey = Pubkey::new_unique();
400        let owner_pubkey = Pubkey::new_unique();
401        let instruction = system_instruction::allocate_with_seed(
402            &account_pubkey,
403            &base_pubkey,
404            seed,
405            space,
406            &owner_pubkey,
407        );
408        let mut message = Message::new(&[instruction], None);
409        assert_eq!(
410            parse_system(
411                &message.instructions[0],
412                &AccountKeys::new(&message.account_keys, None)
413            )
414            .unwrap(),
415            ParsedInstructionEnum {
416                instruction_type: "allocateWithSeed".to_string(),
417                info: json!({
418                    "account": account_pubkey.to_string(),
419                    "base": base_pubkey.to_string(),
420                    "seed": seed,
421                    "owner": owner_pubkey.to_string(),
422                    "space": space,
423                }),
424            }
425        );
426        assert!(parse_system(
427            &message.instructions[0],
428            &AccountKeys::new(&message.account_keys[0..1], None)
429        )
430        .is_err());
431        let keys = message.account_keys.clone();
432        message.instructions[0].accounts.pop();
433        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
434    }
435
436    #[test]
437    fn test_parse_system_assign_with_seed_ix() {
438        let seed = "test_seed";
439        let account_pubkey = Pubkey::new_unique();
440        let base_pubkey = Pubkey::new_unique();
441        let owner_pubkey = Pubkey::new_unique();
442        let instruction = system_instruction::assign_with_seed(
443            &account_pubkey,
444            &base_pubkey,
445            seed,
446            &owner_pubkey,
447        );
448        let mut message = Message::new(&[instruction], None);
449        assert_eq!(
450            parse_system(
451                &message.instructions[0],
452                &AccountKeys::new(&message.account_keys, None)
453            )
454            .unwrap(),
455            ParsedInstructionEnum {
456                instruction_type: "assignWithSeed".to_string(),
457                info: json!({
458                    "account": account_pubkey.to_string(),
459                    "base": base_pubkey.to_string(),
460                    "seed": seed,
461                    "owner": owner_pubkey.to_string(),
462                }),
463            }
464        );
465        assert!(parse_system(
466            &message.instructions[0],
467            &AccountKeys::new(&message.account_keys[0..1], None)
468        )
469        .is_err());
470        let keys = message.account_keys.clone();
471        message.instructions[0].accounts.pop();
472        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
473    }
474
475    #[test]
476    fn test_parse_system_transfer_with_seed_ix() {
477        let lamports = 55;
478        let seed = "test_seed";
479        let from_pubkey = Pubkey::new_unique();
480        let from_base_pubkey = Pubkey::new_unique();
481        let from_owner_pubkey = Pubkey::new_unique();
482        let to_pubkey = Pubkey::new_unique();
483        let instruction = system_instruction::transfer_with_seed(
484            &from_pubkey,
485            &from_base_pubkey,
486            seed.to_string(),
487            &from_owner_pubkey,
488            &to_pubkey,
489            lamports,
490        );
491        let mut message = Message::new(&[instruction], None);
492        assert_eq!(
493            parse_system(
494                &message.instructions[0],
495                &AccountKeys::new(&message.account_keys, None)
496            )
497            .unwrap(),
498            ParsedInstructionEnum {
499                instruction_type: "transferWithSeed".to_string(),
500                info: json!({
501                    "source": from_pubkey.to_string(),
502                    "sourceBase": from_base_pubkey.to_string(),
503                    "sourceSeed": seed,
504                    "sourceOwner": from_owner_pubkey.to_string(),
505                    "lamports": lamports,
506                    "destination": to_pubkey.to_string()
507                }),
508            }
509        );
510        assert!(parse_system(
511            &message.instructions[0],
512            &AccountKeys::new(&message.account_keys[0..2], None)
513        )
514        .is_err());
515        let keys = message.account_keys.clone();
516        message.instructions[0].accounts.pop();
517        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
518    }
519
520    #[test]
521    fn test_parse_system_advance_nonce_account_ix() {
522        let nonce_pubkey = Pubkey::new_unique();
523        let authorized_pubkey = Pubkey::new_unique();
524
525        let instruction =
526            system_instruction::advance_nonce_account(&nonce_pubkey, &authorized_pubkey);
527        let mut message = Message::new(&[instruction], None);
528        assert_eq!(
529            parse_system(
530                &message.instructions[0],
531                &AccountKeys::new(&message.account_keys, None)
532            )
533            .unwrap(),
534            ParsedInstructionEnum {
535                instruction_type: "advanceNonce".to_string(),
536                info: json!({
537                    "nonceAccount": nonce_pubkey.to_string(),
538                    "recentBlockhashesSysvar": sysvar::recent_blockhashes::ID.to_string(),
539                    "nonceAuthority": authorized_pubkey.to_string(),
540                }),
541            }
542        );
543        assert!(parse_system(
544            &message.instructions[0],
545            &AccountKeys::new(&message.account_keys[0..2], None)
546        )
547        .is_err());
548        let keys = message.account_keys.clone();
549        message.instructions[0].accounts.pop();
550        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
551    }
552
553    #[test]
554    fn test_parse_system_withdraw_nonce_account_ix() {
555        let nonce_pubkey = Pubkey::new_unique();
556        let authorized_pubkey = Pubkey::new_unique();
557        let to_pubkey = Pubkey::new_unique();
558
559        let lamports = 55;
560        let instruction = system_instruction::withdraw_nonce_account(
561            &nonce_pubkey,
562            &authorized_pubkey,
563            &to_pubkey,
564            lamports,
565        );
566        let mut message = Message::new(&[instruction], None);
567        assert_eq!(
568            parse_system(
569                &message.instructions[0],
570                &AccountKeys::new(&message.account_keys, None)
571            )
572            .unwrap(),
573            ParsedInstructionEnum {
574                instruction_type: "withdrawFromNonce".to_string(),
575                info: json!({
576                    "nonceAccount": nonce_pubkey.to_string(),
577                    "destination": to_pubkey.to_string(),
578                    "recentBlockhashesSysvar": sysvar::recent_blockhashes::ID.to_string(),
579                    "rentSysvar": sysvar::rent::ID.to_string(),
580                    "nonceAuthority": authorized_pubkey.to_string(),
581                    "lamports": lamports
582                }),
583            }
584        );
585        assert!(parse_system(
586            &message.instructions[0],
587            &AccountKeys::new(&message.account_keys[0..4], None)
588        )
589        .is_err());
590        let keys = message.account_keys.clone();
591        message.instructions[0].accounts.pop();
592        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
593    }
594
595    #[test]
596    fn test_parse_system_initialize_nonce_ix() {
597        let lamports = 55;
598        let from_pubkey = Pubkey::new_unique();
599        let nonce_pubkey = Pubkey::new_unique();
600        let authorized_pubkey = Pubkey::new_unique();
601
602        let instructions = system_instruction::create_nonce_account(
603            &from_pubkey,
604            &nonce_pubkey,
605            &authorized_pubkey,
606            lamports,
607        );
608        let mut message = Message::new(&instructions, None);
609        assert_eq!(
610            parse_system(
611                &message.instructions[1],
612                &AccountKeys::new(&message.account_keys, None)
613            )
614            .unwrap(),
615            ParsedInstructionEnum {
616                instruction_type: "initializeNonce".to_string(),
617                info: json!({
618                    "nonceAccount": nonce_pubkey.to_string(),
619                    "recentBlockhashesSysvar": sysvar::recent_blockhashes::ID.to_string(),
620                    "rentSysvar": sysvar::rent::ID.to_string(),
621                    "nonceAuthority": authorized_pubkey.to_string(),
622                }),
623            }
624        );
625        assert!(parse_system(
626            &message.instructions[1],
627            &AccountKeys::new(&message.account_keys[0..3], None)
628        )
629        .is_err());
630        let keys = message.account_keys.clone();
631        message.instructions[0].accounts.pop();
632        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
633    }
634
635    #[test]
636    fn test_parse_system_authorize_nonce_account_ix() {
637        let nonce_pubkey = Pubkey::new_unique();
638        let authorized_pubkey = Pubkey::new_unique();
639        let new_authority_pubkey = Pubkey::new_unique();
640
641        let instruction = system_instruction::authorize_nonce_account(
642            &nonce_pubkey,
643            &authorized_pubkey,
644            &new_authority_pubkey,
645        );
646        let mut message = Message::new(&[instruction], None);
647        assert_eq!(
648            parse_system(
649                &message.instructions[0],
650                &AccountKeys::new(&message.account_keys, None)
651            )
652            .unwrap(),
653            ParsedInstructionEnum {
654                instruction_type: "authorizeNonce".to_string(),
655                info: json!({
656                    "nonceAccount": nonce_pubkey.to_string(),
657                    "newAuthorized": new_authority_pubkey.to_string(),
658                    "nonceAuthority": authorized_pubkey.to_string(),
659                }),
660            }
661        );
662        assert!(parse_system(
663            &message.instructions[0],
664            &AccountKeys::new(&message.account_keys[0..1], None)
665        )
666        .is_err());
667        let keys = message.account_keys.clone();
668        message.instructions[0].accounts.pop();
669        assert!(parse_system(&message.instructions[0], &AccountKeys::new(&keys, None)).is_err());
670    }
671}