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 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}