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