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