1#[allow(deprecated)]
2use crate::sysvar::recent_blockhashes;
3use crate::{
4 decode_error::DecodeError,
5 instruction::{AccountMeta, Instruction, InstructionError},
6 nonce,
7 pubkey::Pubkey,
8 system_program,
9 sysvar::rent,
10};
11use num_derive::{FromPrimitive, ToPrimitive};
12use thiserror::Error;
13
14#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
15pub enum SystemError {
16 #[error("an account with the same address already exists")]
17 AccountAlreadyInUse,
18 #[error("account does not have enough GEMA to perform the operation")]
19 ResultWithNegativeCarats,
20 #[error("cannot assign account to this program id")]
21 InvalidProgramId,
22 #[error("cannot allocate account data of this length")]
23 InvalidAccountDataLength,
24 #[error("length of requested seed is too long")]
25 MaxSeedLengthExceeded,
26 #[error("provided address does not match addressed derived from seed")]
27 AddressWithSeedMismatch,
28 #[error("advancing stored nonce requires a populated RecentBlockhashes sysvar")]
29 NonceNoRecentBlockhashes,
30 #[error("stored nonce is still in recent_blockhashes")]
31 NonceBlockhashNotExpired,
32 #[error("specified nonce does not match stored nonce")]
33 NonceUnexpectedBlockhashValue,
34}
35
36impl<T> DecodeError<T> for SystemError {
37 fn type_of() -> &'static str {
38 "SystemError"
39 }
40}
41
42#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
43pub enum NonceError {
44 #[error("recent blockhash list is empty")]
45 NoRecentBlockhashes,
46 #[error("stored nonce is still in recent_blockhashes")]
47 NotExpired,
48 #[error("specified nonce does not match stored nonce")]
49 UnexpectedValue,
50 #[error("cannot handle request in current account state")]
51 BadAccountState,
52}
53
54impl<E> DecodeError<E> for NonceError {
55 fn type_of() -> &'static str {
56 "NonceError"
57 }
58}
59
60#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
61enum NonceErrorAdapter {
62 #[error("recent blockhash list is empty")]
63 NoRecentBlockhashes,
64 #[error("stored nonce is still in recent_blockhashes")]
65 NotExpired,
66 #[error("specified nonce does not match stored nonce")]
67 UnexpectedValue,
68 #[error("cannot handle request in current account state")]
69 BadAccountState,
70}
71
72impl<E> DecodeError<E> for NonceErrorAdapter {
73 fn type_of() -> &'static str {
74 "NonceErrorAdapter"
75 }
76}
77
78impl From<NonceErrorAdapter> for NonceError {
79 fn from(e: NonceErrorAdapter) -> Self {
80 match e {
81 NonceErrorAdapter::NoRecentBlockhashes => NonceError::NoRecentBlockhashes,
82 NonceErrorAdapter::NotExpired => NonceError::NotExpired,
83 NonceErrorAdapter::UnexpectedValue => NonceError::UnexpectedValue,
84 NonceErrorAdapter::BadAccountState => NonceError::BadAccountState,
85 }
86 }
87}
88
89pub fn nonce_to_instruction_error(error: NonceError, use_system_variant: bool) -> InstructionError {
90 if use_system_variant {
91 match error {
92 NonceError::NoRecentBlockhashes => SystemError::NonceNoRecentBlockhashes.into(),
93 NonceError::NotExpired => SystemError::NonceBlockhashNotExpired.into(),
94 NonceError::UnexpectedValue => SystemError::NonceUnexpectedBlockhashValue.into(),
95 NonceError::BadAccountState => InstructionError::InvalidAccountData,
96 }
97 } else {
98 match error {
99 NonceError::NoRecentBlockhashes => NonceErrorAdapter::NoRecentBlockhashes.into(),
100 NonceError::NotExpired => NonceErrorAdapter::NotExpired.into(),
101 NonceError::UnexpectedValue => NonceErrorAdapter::UnexpectedValue.into(),
102 NonceError::BadAccountState => NonceErrorAdapter::BadAccountState.into(),
103 }
104 }
105}
106
107pub fn instruction_to_nonce_error(
108 error: &InstructionError,
109 use_system_variant: bool,
110) -> Option<NonceError> {
111 if use_system_variant {
112 match error {
113 InstructionError::Custom(discriminant) => {
114 match SystemError::decode_custom_error_to_enum(*discriminant) {
115 Some(SystemError::NonceNoRecentBlockhashes) => {
116 Some(NonceError::NoRecentBlockhashes)
117 }
118 Some(SystemError::NonceBlockhashNotExpired) => Some(NonceError::NotExpired),
119 Some(SystemError::NonceUnexpectedBlockhashValue) => {
120 Some(NonceError::UnexpectedValue)
121 }
122 _ => None,
123 }
124 }
125 InstructionError::InvalidAccountData => Some(NonceError::BadAccountState),
126 _ => None,
127 }
128 } else if let InstructionError::Custom(discriminant) = error {
129 let maybe: Option<NonceErrorAdapter> =
130 NonceErrorAdapter::decode_custom_error_to_enum(*discriminant);
131 maybe.map(NonceError::from)
132 } else {
133 None
134 }
135}
136
137pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
139
140#[frozen_abi(digest = "2xnDcizcPKKR7b624FeuuPd1zj5bmnkmVsBWgoKPTh4w")]
141#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, AbiExample, AbiEnumVisitor)]
142pub enum SystemInstruction {
143 CreateAccount {
149 carats: u64,
151
152 space: u64,
154
155 owner: Pubkey,
157 },
158
159 Assign {
164 owner: Pubkey,
166 },
167
168 Transfer { carats: u64 },
174
175 CreateAccountWithSeed {
184 base: Pubkey,
186
187 seed: String,
189
190 carats: u64,
192
193 space: u64,
195
196 owner: Pubkey,
198 },
199
200 AdvanceNonceAccount,
207
208 WithdrawNonceAccount(u64),
220
221 InitializeNonceAccount(Pubkey),
234
235 AuthorizeNonceAccount(Pubkey),
243
244 Allocate {
249 space: u64,
251 },
252
253 AllocateWithSeed {
260 base: Pubkey,
262
263 seed: String,
265
266 space: u64,
268
269 owner: Pubkey,
271 },
272
273 AssignWithSeed {
279 base: Pubkey,
281
282 seed: String,
284
285 owner: Pubkey,
287 },
288
289 TransferWithSeed {
296 carats: u64,
298
299 from_seed: String,
301
302 from_owner: Pubkey,
304 },
305}
306
307pub fn create_account(
308 from_pubkey: &Pubkey,
309 to_pubkey: &Pubkey,
310 carats: u64,
311 space: u64,
312 owner: &Pubkey,
313) -> Instruction {
314 let account_metas = vec![
315 AccountMeta::new(*from_pubkey, true),
316 AccountMeta::new(*to_pubkey, true),
317 ];
318 Instruction::new_with_bincode(
319 system_program::id(),
320 &SystemInstruction::CreateAccount {
321 carats,
322 space,
323 owner: *owner,
324 },
325 account_metas,
326 )
327}
328
329pub fn create_account_with_seed(
332 from_pubkey: &Pubkey,
333 to_pubkey: &Pubkey, base: &Pubkey,
335 seed: &str,
336 carats: u64,
337 space: u64,
338 owner: &Pubkey,
339) -> Instruction {
340 let account_metas = vec![
341 AccountMeta::new(*from_pubkey, true),
342 AccountMeta::new(*to_pubkey, false),
343 AccountMeta::new_readonly(*base, true),
344 ];
345
346 Instruction::new_with_bincode(
347 system_program::id(),
348 &SystemInstruction::CreateAccountWithSeed {
349 base: *base,
350 seed: seed.to_string(),
351 carats,
352 space,
353 owner: *owner,
354 },
355 account_metas,
356 )
357}
358
359pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
360 let account_metas = vec![AccountMeta::new(*pubkey, true)];
361 Instruction::new_with_bincode(
362 system_program::id(),
363 &SystemInstruction::Assign { owner: *owner },
364 account_metas,
365 )
366}
367
368pub fn assign_with_seed(
369 address: &Pubkey, base: &Pubkey,
371 seed: &str,
372 owner: &Pubkey,
373) -> Instruction {
374 let account_metas = vec![
375 AccountMeta::new(*address, false),
376 AccountMeta::new_readonly(*base, true),
377 ];
378 Instruction::new_with_bincode(
379 system_program::id(),
380 &SystemInstruction::AssignWithSeed {
381 base: *base,
382 seed: seed.to_string(),
383 owner: *owner,
384 },
385 account_metas,
386 )
387}
388
389pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, carats: u64) -> Instruction {
390 let account_metas = vec![
391 AccountMeta::new(*from_pubkey, true),
392 AccountMeta::new(*to_pubkey, false),
393 ];
394 Instruction::new_with_bincode(
395 system_program::id(),
396 &SystemInstruction::Transfer { carats },
397 account_metas,
398 )
399}
400
401pub fn transfer_with_seed(
402 from_pubkey: &Pubkey, from_base: &Pubkey,
404 from_seed: String,
405 from_owner: &Pubkey,
406 to_pubkey: &Pubkey,
407 carats: u64,
408) -> Instruction {
409 let account_metas = vec![
410 AccountMeta::new(*from_pubkey, false),
411 AccountMeta::new_readonly(*from_base, true),
412 AccountMeta::new(*to_pubkey, false),
413 ];
414 Instruction::new_with_bincode(
415 system_program::id(),
416 &SystemInstruction::TransferWithSeed {
417 carats,
418 from_seed,
419 from_owner: *from_owner,
420 },
421 account_metas,
422 )
423}
424
425pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
426 let account_metas = vec![AccountMeta::new(*pubkey, true)];
427 Instruction::new_with_bincode(
428 system_program::id(),
429 &SystemInstruction::Allocate { space },
430 account_metas,
431 )
432}
433
434pub fn allocate_with_seed(
435 address: &Pubkey, base: &Pubkey,
437 seed: &str,
438 space: u64,
439 owner: &Pubkey,
440) -> Instruction {
441 let account_metas = vec![
442 AccountMeta::new(*address, false),
443 AccountMeta::new_readonly(*base, true),
444 ];
445 Instruction::new_with_bincode(
446 system_program::id(),
447 &SystemInstruction::AllocateWithSeed {
448 base: *base,
449 seed: seed.to_string(),
450 space,
451 owner: *owner,
452 },
453 account_metas,
454 )
455}
456
457pub fn transfer_many(from_pubkey: &Pubkey, to_carats: &[(Pubkey, u64)]) -> Vec<Instruction> {
459 to_carats
460 .iter()
461 .map(|(to_pubkey, carats)| transfer(from_pubkey, to_pubkey, *carats))
462 .collect()
463}
464
465pub fn create_nonce_account_with_seed(
466 from_pubkey: &Pubkey,
467 nonce_pubkey: &Pubkey,
468 base: &Pubkey,
469 seed: &str,
470 authority: &Pubkey,
471 carats: u64,
472) -> Vec<Instruction> {
473 vec![
474 create_account_with_seed(
475 from_pubkey,
476 nonce_pubkey,
477 base,
478 seed,
479 carats,
480 nonce::State::size() as u64,
481 &system_program::id(),
482 ),
483 Instruction::new_with_bincode(
484 system_program::id(),
485 &SystemInstruction::InitializeNonceAccount(*authority),
486 vec![
487 AccountMeta::new(*nonce_pubkey, false),
488 #[allow(deprecated)]
489 AccountMeta::new_readonly(recent_blockhashes::id(), false),
490 AccountMeta::new_readonly(rent::id(), false),
491 ],
492 ),
493 ]
494}
495
496pub fn create_nonce_account(
497 from_pubkey: &Pubkey,
498 nonce_pubkey: &Pubkey,
499 authority: &Pubkey,
500 carats: u64,
501) -> Vec<Instruction> {
502 vec![
503 create_account(
504 from_pubkey,
505 nonce_pubkey,
506 carats,
507 nonce::State::size() as u64,
508 &system_program::id(),
509 ),
510 Instruction::new_with_bincode(
511 system_program::id(),
512 &SystemInstruction::InitializeNonceAccount(*authority),
513 vec![
514 AccountMeta::new(*nonce_pubkey, false),
515 #[allow(deprecated)]
516 AccountMeta::new_readonly(recent_blockhashes::id(), false),
517 AccountMeta::new_readonly(rent::id(), false),
518 ],
519 ),
520 ]
521}
522
523pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
524 let account_metas = vec![
525 AccountMeta::new(*nonce_pubkey, false),
526 #[allow(deprecated)]
527 AccountMeta::new_readonly(recent_blockhashes::id(), false),
528 AccountMeta::new_readonly(*authorized_pubkey, true),
529 ];
530 Instruction::new_with_bincode(
531 system_program::id(),
532 &SystemInstruction::AdvanceNonceAccount,
533 account_metas,
534 )
535}
536
537pub fn withdraw_nonce_account(
538 nonce_pubkey: &Pubkey,
539 authorized_pubkey: &Pubkey,
540 to_pubkey: &Pubkey,
541 carats: u64,
542) -> Instruction {
543 let account_metas = vec![
544 AccountMeta::new(*nonce_pubkey, false),
545 AccountMeta::new(*to_pubkey, false),
546 #[allow(deprecated)]
547 AccountMeta::new_readonly(recent_blockhashes::id(), false),
548 AccountMeta::new_readonly(rent::id(), false),
549 AccountMeta::new_readonly(*authorized_pubkey, true),
550 ];
551 Instruction::new_with_bincode(
552 system_program::id(),
553 &SystemInstruction::WithdrawNonceAccount(carats),
554 account_metas,
555 )
556}
557
558pub fn authorize_nonce_account(
559 nonce_pubkey: &Pubkey,
560 authorized_pubkey: &Pubkey,
561 new_authority: &Pubkey,
562) -> Instruction {
563 let account_metas = vec![
564 AccountMeta::new(*nonce_pubkey, false),
565 AccountMeta::new_readonly(*authorized_pubkey, true),
566 ];
567 Instruction::new_with_bincode(
568 system_program::id(),
569 &SystemInstruction::AuthorizeNonceAccount(*new_authority),
570 account_metas,
571 )
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577 use crate::instruction::{Instruction, InstructionError};
578 use num_traits::ToPrimitive;
579
580 fn get_keys(instruction: &Instruction) -> Vec<Pubkey> {
581 instruction.accounts.iter().map(|x| x.pubkey).collect()
582 }
583
584 #[test]
585 fn test_move_many() {
586 let alice_pubkey = Pubkey::new_unique();
587 let bob_pubkey = Pubkey::new_unique();
588 let carol_pubkey = Pubkey::new_unique();
589 let to_carats = vec![(bob_pubkey, 1), (carol_pubkey, 2)];
590
591 let instructions = transfer_many(&alice_pubkey, &to_carats);
592 assert_eq!(instructions.len(), 2);
593 assert_eq!(get_keys(&instructions[0]), vec![alice_pubkey, bob_pubkey]);
594 assert_eq!(get_keys(&instructions[1]), vec![alice_pubkey, carol_pubkey]);
595 }
596
597 #[test]
598 fn test_create_nonce_account() {
599 let from_pubkey = Pubkey::new_unique();
600 let nonce_pubkey = Pubkey::new_unique();
601 let authorized = nonce_pubkey;
602 let ixs = create_nonce_account(&from_pubkey, &nonce_pubkey, &authorized, 42);
603 assert_eq!(ixs.len(), 2);
604 let ix = &ixs[0];
605 assert_eq!(ix.program_id, system_program::id());
606 let pubkeys: Vec<_> = ix.accounts.iter().map(|am| am.pubkey).collect();
607 assert!(pubkeys.contains(&from_pubkey));
608 assert!(pubkeys.contains(&nonce_pubkey));
609 }
610
611 #[test]
612 fn test_nonce_error_decode() {
613 use num_traits::FromPrimitive;
614 fn pretty_err<T>(err: InstructionError) -> String
615 where
616 T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
617 {
618 if let InstructionError::Custom(code) = err {
619 let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
620 format!(
621 "{:?}: {}::{:?} - {}",
622 err,
623 T::type_of(),
624 specific_error,
625 specific_error,
626 )
627 } else {
628 "".to_string()
629 }
630 }
631 assert_eq!(
632 "Custom(0): NonceError::NoRecentBlockhashes - recent blockhash list is empty",
633 pretty_err::<NonceError>(NonceError::NoRecentBlockhashes.into())
634 );
635 assert_eq!(
636 "Custom(1): NonceError::NotExpired - stored nonce is still in recent_blockhashes",
637 pretty_err::<NonceError>(NonceError::NotExpired.into())
638 );
639 assert_eq!(
640 "Custom(2): NonceError::UnexpectedValue - specified nonce does not match stored nonce",
641 pretty_err::<NonceError>(NonceError::UnexpectedValue.into())
642 );
643 assert_eq!(
644 "Custom(3): NonceError::BadAccountState - cannot handle request in current account state",
645 pretty_err::<NonceError>(NonceError::BadAccountState.into())
646 );
647 }
648
649 #[test]
650 fn test_nonce_to_instruction_error() {
651 assert_eq!(
652 nonce_to_instruction_error(NonceError::NoRecentBlockhashes, false),
653 NonceError::NoRecentBlockhashes.into(),
654 );
655 assert_eq!(
656 nonce_to_instruction_error(NonceError::NotExpired, false),
657 NonceError::NotExpired.into(),
658 );
659 assert_eq!(
660 nonce_to_instruction_error(NonceError::UnexpectedValue, false),
661 NonceError::UnexpectedValue.into(),
662 );
663 assert_eq!(
664 nonce_to_instruction_error(NonceError::BadAccountState, false),
665 NonceError::BadAccountState.into(),
666 );
667 assert_eq!(
668 nonce_to_instruction_error(NonceError::NoRecentBlockhashes, true),
669 SystemError::NonceNoRecentBlockhashes.into(),
670 );
671 assert_eq!(
672 nonce_to_instruction_error(NonceError::NotExpired, true),
673 SystemError::NonceBlockhashNotExpired.into(),
674 );
675 assert_eq!(
676 nonce_to_instruction_error(NonceError::UnexpectedValue, true),
677 SystemError::NonceUnexpectedBlockhashValue.into(),
678 );
679 assert_eq!(
680 nonce_to_instruction_error(NonceError::BadAccountState, true),
681 InstructionError::InvalidAccountData,
682 );
683 }
684
685 #[test]
686 fn test_instruction_to_nonce_error() {
687 assert_eq!(
688 instruction_to_nonce_error(
689 &InstructionError::Custom(NonceErrorAdapter::NoRecentBlockhashes.to_u32().unwrap(),),
690 false,
691 ),
692 Some(NonceError::NoRecentBlockhashes),
693 );
694 assert_eq!(
695 instruction_to_nonce_error(
696 &InstructionError::Custom(NonceErrorAdapter::NotExpired.to_u32().unwrap(),),
697 false,
698 ),
699 Some(NonceError::NotExpired),
700 );
701 assert_eq!(
702 instruction_to_nonce_error(
703 &InstructionError::Custom(NonceErrorAdapter::UnexpectedValue.to_u32().unwrap(),),
704 false,
705 ),
706 Some(NonceError::UnexpectedValue),
707 );
708 assert_eq!(
709 instruction_to_nonce_error(
710 &InstructionError::Custom(NonceErrorAdapter::BadAccountState.to_u32().unwrap(),),
711 false,
712 ),
713 Some(NonceError::BadAccountState),
714 );
715 assert_eq!(
716 instruction_to_nonce_error(&InstructionError::Custom(u32::MAX), false),
717 None,
718 );
719 assert_eq!(
720 instruction_to_nonce_error(
721 &InstructionError::Custom(SystemError::NonceNoRecentBlockhashes.to_u32().unwrap(),),
722 true,
723 ),
724 Some(NonceError::NoRecentBlockhashes),
725 );
726 assert_eq!(
727 instruction_to_nonce_error(
728 &InstructionError::Custom(SystemError::NonceBlockhashNotExpired.to_u32().unwrap(),),
729 true,
730 ),
731 Some(NonceError::NotExpired),
732 );
733 assert_eq!(
734 instruction_to_nonce_error(
735 &InstructionError::Custom(
736 SystemError::NonceUnexpectedBlockhashValue.to_u32().unwrap(),
737 ),
738 true,
739 ),
740 Some(NonceError::UnexpectedValue),
741 );
742 assert_eq!(
743 instruction_to_nonce_error(&InstructionError::InvalidAccountData, true),
744 Some(NonceError::BadAccountState),
745 );
746 assert_eq!(
747 instruction_to_nonce_error(&InstructionError::Custom(u32::MAX), true),
748 None,
749 );
750 }
751
752 #[test]
753 fn test_nonce_error_adapter_compat() {
754 assert_eq!(
755 NonceError::NoRecentBlockhashes.to_u32(),
756 NonceErrorAdapter::NoRecentBlockhashes.to_u32(),
757 );
758 assert_eq!(
759 NonceError::NotExpired.to_u32(),
760 NonceErrorAdapter::NotExpired.to_u32(),
761 );
762 assert_eq!(
763 NonceError::UnexpectedValue.to_u32(),
764 NonceErrorAdapter::UnexpectedValue.to_u32(),
765 );
766 assert_eq!(
767 NonceError::BadAccountState.to_u32(),
768 NonceErrorAdapter::BadAccountState.to_u32(),
769 );
770 }
771}