cbe_program/
system_instruction.rs

1//! Instructions and constructors for the system program.
2//!
3//! The system program is responsible for the creation of accounts and [nonce
4//! accounts][na]. It is responsible for transferring scoobies from accounts
5//! owned by the system program, including typical user wallet accounts.
6//!
7//! [na]: https://docs.cartallum.com/implemented-proposals/durable-tx-nonces
8//!
9//! Account creation typically involves three steps: [`allocate`] space,
10//! [`transfer`] scoobies for rent, [`assign`] to its owning program. The
11//! [`create_account`] function does all three at once. All new accounts must
12//! contain enough scoobies to be [rent exempt], or else the creation
13//! instruction will fail.
14//!
15//! [rent exempt]: https://docs.cartallum.com/developing/programming-model/accounts#rent-exemption
16//!
17//! The accounts created by the system program can either be user-controlled,
18//! where the secret keys are held outside the blockchain,
19//! or they can be [program derived addresses][pda],
20//! where write access to accounts is granted by an owning program.
21//!
22//! [pda]: crate::pubkey::Pubkey::find_program_address
23//!
24//! The system program ID is defined in [`system_program`].
25//!
26//! Most of the functions in this module construct an [`Instruction`], that must
27//! be submitted to the runtime for execution, either via RPC, typically with
28//! [`RpcClient`], or through [cross-program invocation][cpi].
29//!
30//! When invoking through CPI, the [`invoke`] or [`invoke_signed`] instruction
31//! requires all account references to be provided explicitly as [`AccountInfo`]
32//! values. The account references required are specified in the documentation
33//! for the [`SystemInstruction`] variants for each system program instruction,
34//! and these variants are linked from the documentation for their constructors.
35//!
36//! [`RpcClient`]: https://docs.cartallum.com/cbe-client/latest/cbe_client/rpc_client/struct.RpcClient.html
37//! [cpi]: crate::program
38//! [`invoke`]: crate::program::invoke
39//! [`invoke_signed`]: crate::program::invoke_signed
40//! [`AccountInfo`]: crate::account_info::AccountInfo
41
42#[allow(deprecated)]
43use {
44    crate::{
45        decode_error::DecodeError,
46        instruction::{AccountMeta, Instruction, InstructionError},
47        nonce,
48        pubkey::Pubkey,
49        system_program,
50        sysvar::{recent_blockhashes, rent},
51    },
52    num_derive::{FromPrimitive, ToPrimitive},
53    thiserror::Error,
54};
55
56#[derive(Error, Debug, Serialize, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
57pub enum SystemError {
58    #[error("an account with the same address already exists")]
59    AccountAlreadyInUse,
60    #[error("account does not have enough CBC to perform the operation")]
61    ResultWithNegativeScoobies,
62    #[error("cannot assign account to this program id")]
63    InvalidProgramId,
64    #[error("cannot allocate account data of this length")]
65    InvalidAccountDataLength,
66    #[error("length of requested seed is too long")]
67    MaxSeedLengthExceeded,
68    #[error("provided address does not match addressed derived from seed")]
69    AddressWithSeedMismatch,
70    #[error("advancing stored nonce requires a populated RecentBlockhashes sysvar")]
71    NonceNoRecentBlockhashes,
72    #[error("stored nonce is still in recent_blockhashes")]
73    NonceBlockhashNotExpired,
74    #[error("specified nonce does not match stored nonce")]
75    NonceUnexpectedBlockhashValue,
76}
77
78impl<T> DecodeError<T> for SystemError {
79    fn type_of() -> &'static str {
80        "SystemError"
81    }
82}
83
84#[derive(Error, Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
85pub enum NonceError {
86    #[error("recent blockhash list is empty")]
87    NoRecentBlockhashes,
88    #[error("stored nonce is still in recent_blockhashes")]
89    NotExpired,
90    #[error("specified nonce does not match stored nonce")]
91    UnexpectedValue,
92    #[error("cannot handle request in current account state")]
93    BadAccountState,
94}
95
96impl<E> DecodeError<E> for NonceError {
97    fn type_of() -> &'static str {
98        "NonceError"
99    }
100}
101
102#[derive(Error, Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
103enum NonceErrorAdapter {
104    #[error("recent blockhash list is empty")]
105    NoRecentBlockhashes,
106    #[error("stored nonce is still in recent_blockhashes")]
107    NotExpired,
108    #[error("specified nonce does not match stored nonce")]
109    UnexpectedValue,
110    #[error("cannot handle request in current account state")]
111    BadAccountState,
112}
113
114impl<E> DecodeError<E> for NonceErrorAdapter {
115    fn type_of() -> &'static str {
116        "NonceErrorAdapter"
117    }
118}
119
120impl From<NonceErrorAdapter> for NonceError {
121    fn from(e: NonceErrorAdapter) -> Self {
122        match e {
123            NonceErrorAdapter::NoRecentBlockhashes => NonceError::NoRecentBlockhashes,
124            NonceErrorAdapter::NotExpired => NonceError::NotExpired,
125            NonceErrorAdapter::UnexpectedValue => NonceError::UnexpectedValue,
126            NonceErrorAdapter::BadAccountState => NonceError::BadAccountState,
127        }
128    }
129}
130
131pub fn nonce_to_instruction_error(error: NonceError, use_system_variant: bool) -> InstructionError {
132    if use_system_variant {
133        match error {
134            NonceError::NoRecentBlockhashes => SystemError::NonceNoRecentBlockhashes.into(),
135            NonceError::NotExpired => SystemError::NonceBlockhashNotExpired.into(),
136            NonceError::UnexpectedValue => SystemError::NonceUnexpectedBlockhashValue.into(),
137            NonceError::BadAccountState => InstructionError::InvalidAccountData,
138        }
139    } else {
140        match error {
141            NonceError::NoRecentBlockhashes => NonceErrorAdapter::NoRecentBlockhashes.into(),
142            NonceError::NotExpired => NonceErrorAdapter::NotExpired.into(),
143            NonceError::UnexpectedValue => NonceErrorAdapter::UnexpectedValue.into(),
144            NonceError::BadAccountState => NonceErrorAdapter::BadAccountState.into(),
145        }
146    }
147}
148
149pub fn instruction_to_nonce_error(
150    error: &InstructionError,
151    use_system_variant: bool,
152) -> Option<NonceError> {
153    if use_system_variant {
154        match error {
155            InstructionError::Custom(discriminant) => {
156                match SystemError::decode_custom_error_to_enum(*discriminant) {
157                    Some(SystemError::NonceNoRecentBlockhashes) => {
158                        Some(NonceError::NoRecentBlockhashes)
159                    }
160                    Some(SystemError::NonceBlockhashNotExpired) => Some(NonceError::NotExpired),
161                    Some(SystemError::NonceUnexpectedBlockhashValue) => {
162                        Some(NonceError::UnexpectedValue)
163                    }
164                    _ => None,
165                }
166            }
167            InstructionError::InvalidAccountData => Some(NonceError::BadAccountState),
168            _ => None,
169        }
170    } else if let InstructionError::Custom(discriminant) = error {
171        let maybe: Option<NonceErrorAdapter> =
172            NonceErrorAdapter::decode_custom_error_to_enum(*discriminant);
173        maybe.map(NonceError::from)
174    } else {
175        None
176    }
177}
178
179/// Maximum permitted size of account data (10 MiB).
180pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
181
182/// Maximum permitted size of new allocations per transaction, in bytes.
183///
184/// The value was chosen such that at least one max sized account could be created,
185/// plus some additional resize allocations.
186pub const MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION: i64 =
187    MAX_PERMITTED_DATA_LENGTH as i64 * 2;
188
189// SBF program entrypoint assumes that the max account data length
190// will fit inside a u32. If this constant no longer fits in a u32,
191// the entrypoint deserialization code in the SDK must be updated.
192#[cfg(test)]
193static_assertions::const_assert!(MAX_PERMITTED_DATA_LENGTH <= u32::MAX as u64);
194
195#[cfg(test)]
196static_assertions::const_assert_eq!(MAX_PERMITTED_DATA_LENGTH, 10_485_760);
197
198/// An instruction to the system program.
199#[frozen_abi(digest = "5e22s2kFu9Do77hdcCyxyhuKHD8ThAB6Q6dNaLTCjL5M")]
200#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, AbiExample, AbiEnumVisitor)]
201pub enum SystemInstruction {
202    /// Create a new account
203    ///
204    /// # Account references
205    ///   0. `[WRITE, SIGNER]` Funding account
206    ///   1. `[WRITE, SIGNER]` New account
207    CreateAccount {
208        /// Number of scoobies to transfer to the new account
209        scoobies: u64,
210
211        /// Number of bytes of memory to allocate
212        space: u64,
213
214        /// Address of program that will own the new account
215        owner: Pubkey,
216    },
217
218    /// Assign account to a program
219    ///
220    /// # Account references
221    ///   0. `[WRITE, SIGNER]` Assigned account public key
222    Assign {
223        /// Owner program account
224        owner: Pubkey,
225    },
226
227    /// Transfer scoobies
228    ///
229    /// # Account references
230    ///   0. `[WRITE, SIGNER]` Funding account
231    ///   1. `[WRITE]` Recipient account
232    Transfer { scoobies: u64 },
233
234    /// Create a new account at an address derived from a base pubkey and a seed
235    ///
236    /// # Account references
237    ///   0. `[WRITE, SIGNER]` Funding account
238    ///   1. `[WRITE]` Created account
239    ///   2. `[SIGNER]` (optional) Base account; the account matching the base Pubkey below must be
240    ///                          provided as a signer, but may be the same as the funding account
241    ///                          and provided as account 0
242    CreateAccountWithSeed {
243        /// Base public key
244        base: Pubkey,
245
246        /// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN`
247        seed: String,
248
249        /// Number of scoobies to transfer to the new account
250        scoobies: u64,
251
252        /// Number of bytes of memory to allocate
253        space: u64,
254
255        /// Owner program account address
256        owner: Pubkey,
257    },
258
259    /// Consumes a stored nonce, replacing it with a successor
260    ///
261    /// # Account references
262    ///   0. `[WRITE]` Nonce account
263    ///   1. `[]` RecentBlockhashes sysvar
264    ///   2. `[SIGNER]` Nonce authority
265    AdvanceNonceAccount,
266
267    /// Withdraw funds from a nonce account
268    ///
269    /// # Account references
270    ///   0. `[WRITE]` Nonce account
271    ///   1. `[WRITE]` Recipient account
272    ///   2. `[]` RecentBlockhashes sysvar
273    ///   3. `[]` Rent sysvar
274    ///   4. `[SIGNER]` Nonce authority
275    ///
276    /// The `u64` parameter is the scoobies to withdraw, which must leave the
277    /// account balance above the rent exempt reserve or at zero.
278    WithdrawNonceAccount(u64),
279
280    /// Drive state of Uninitialized nonce account to Initialized, setting the nonce value
281    ///
282    /// # Account references
283    ///   0. `[WRITE]` Nonce account
284    ///   1. `[]` RecentBlockhashes sysvar
285    ///   2. `[]` Rent sysvar
286    ///
287    /// The `Pubkey` parameter specifies the entity authorized to execute nonce
288    /// instruction on the account
289    ///
290    /// No signatures are required to execute this instruction, enabling derived
291    /// nonce account addresses
292    InitializeNonceAccount(Pubkey),
293
294    /// Change the entity authorized to execute nonce instructions on the account
295    ///
296    /// # Account references
297    ///   0. `[WRITE]` Nonce account
298    ///   1. `[SIGNER]` Nonce authority
299    ///
300    /// The `Pubkey` parameter identifies the entity to authorize
301    AuthorizeNonceAccount(Pubkey),
302
303    /// Allocate space in a (possibly new) account without funding
304    ///
305    /// # Account references
306    ///   0. `[WRITE, SIGNER]` New account
307    Allocate {
308        /// Number of bytes of memory to allocate
309        space: u64,
310    },
311
312    /// Allocate space for and assign an account at an address
313    ///    derived from a base public key and a seed
314    ///
315    /// # Account references
316    ///   0. `[WRITE]` Allocated account
317    ///   1. `[SIGNER]` Base account
318    AllocateWithSeed {
319        /// Base public key
320        base: Pubkey,
321
322        /// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN`
323        seed: String,
324
325        /// Number of bytes of memory to allocate
326        space: u64,
327
328        /// Owner program account
329        owner: Pubkey,
330    },
331
332    /// Assign account to a program based on a seed
333    ///
334    /// # Account references
335    ///   0. `[WRITE]` Assigned account
336    ///   1. `[SIGNER]` Base account
337    AssignWithSeed {
338        /// Base public key
339        base: Pubkey,
340
341        /// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN`
342        seed: String,
343
344        /// Owner program account
345        owner: Pubkey,
346    },
347
348    /// Transfer scoobies from a derived address
349    ///
350    /// # Account references
351    ///   0. `[WRITE]` Funding account
352    ///   1. `[SIGNER]` Base for funding account
353    ///   2. `[WRITE]` Recipient account
354    TransferWithSeed {
355        /// Amount to transfer
356        scoobies: u64,
357
358        /// Seed to use to derive the funding account address
359        from_seed: String,
360
361        /// Owner to use to derive the funding account address
362        from_owner: Pubkey,
363    },
364
365    /// One-time idempotent upgrade of legacy nonce versions in order to bump
366    /// them out of chain blockhash domain.
367    ///
368    /// # Account references
369    ///   0. `[WRITE]` Nonce account
370    UpgradeNonceAccount,
371}
372
373/// Create an account.
374///
375/// This function produces an [`Instruction`] which must be submitted in a
376/// [`Transaction`] or [invoked] to take effect, containing a serialized
377/// [`SystemInstruction::CreateAccount`].
378///
379/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
380/// [invoked]: crate::program::invoke
381///
382/// Account creation typically involves three steps: [`allocate`] space,
383/// [`transfer`] scoobies for rent, [`assign`] to its owning program. The
384/// [`create_account`] function does all three at once.
385///
386/// # Required signers
387///
388/// The `from_pubkey` and `to_pubkey` signers must sign the transaction.
389///
390/// # Examples
391///
392/// These examples use a single invocation of
393/// [`SystemInstruction::CreateAccount`] to create a new account, allocate some
394/// space, transfer it the minimum scoobies for rent exemption, and assign it to
395/// the system program,
396///
397/// ## Example: client-side RPC
398///
399/// This example submits the instruction from an RPC client.
400/// The `payer` and `new_account` are signers.
401///
402/// ```
403/// # use cbe_program::example_mocks::{cbe_sdk, cbe_rpc_client};
404/// use cbe_rpc_client::rpc_client::RpcClient;
405/// use cbe_sdk::{
406///     pubkey::Pubkey,
407///     signature::{Keypair, Signer},
408///     system_instruction,
409///     system_program,
410///     transaction::Transaction,
411/// };
412/// use anyhow::Result;
413///
414/// fn create_account(
415///     client: &RpcClient,
416///     payer: &Keypair,
417///     new_account: &Keypair,
418///     space: u64,
419/// ) -> Result<()> {
420///     let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?;
421///     let instr = system_instruction::create_account(
422///         &payer.pubkey(),
423///         &new_account.pubkey(),
424///         rent,
425///         space,
426///         &system_program::ID,
427///     );
428///
429///     let blockhash = client.get_latest_blockhash()?;
430///     let tx = Transaction::new_signed_with_payer(
431///         &[instr],
432///         Some(&payer.pubkey()),
433///         &[payer, new_account],
434///         blockhash,
435///     );
436///
437///     let _sig = client.send_and_confirm_transaction(&tx)?;
438///
439///     Ok(())
440/// }
441/// # let payer = Keypair::new();
442/// # let new_account = Keypair::new();
443/// # let client = RpcClient::new(String::new());
444/// # create_account(&client, &payer, &new_account, 0);
445/// #
446/// # Ok::<(), anyhow::Error>(())
447/// ```
448///
449/// ## Example: on-chain program
450///
451/// This example submits the instruction from an on-chain Cartallum CBE program. The
452/// created account is a [program derived address][pda]. The `payer` and
453/// `new_account_pda` are signers, with `new_account_pda` being signed for
454/// virtually by the program itself via [`invoke_signed`], `payer` being signed
455/// for by the client that submitted the transaction.
456///
457/// [pda]: Pubkey::find_program_address
458/// [`invoke_signed`]: crate::program::invoke_signed
459///
460/// ```
461/// # use borsh_derive::BorshDeserialize;
462/// # use borsh::BorshSerialize;
463/// # use borsh::de::BorshDeserialize;
464/// use cbe_program::{
465///     account_info::{next_account_info, AccountInfo},
466///     entrypoint,
467///     entrypoint::ProgramResult,
468///     msg,
469///     program::invoke_signed,
470///     pubkey::Pubkey,
471///     system_instruction,
472///     system_program,
473///     sysvar::rent::Rent,
474///     sysvar::Sysvar,
475/// };
476///
477/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
478/// pub struct CreateAccountInstruction {
479///     /// The PDA seed used to distinguish the new account from other PDAs
480///     pub new_account_seed: [u8; 16],
481///     /// The PDA bump seed
482///     pub new_account_bump_seed: u8,
483///     /// The amount of space to allocate for `new_account_pda`
484///     pub space: u64,
485/// }
486///
487/// entrypoint!(process_instruction);
488///
489/// fn process_instruction(
490///     program_id: &Pubkey,
491///     accounts: &[AccountInfo],
492///     instruction_data: &[u8],
493/// ) -> ProgramResult {
494///     let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;
495///
496///     let account_info_iter = &mut accounts.iter();
497///
498///     let payer = next_account_info(account_info_iter)?;
499///     let new_account_pda = next_account_info(account_info_iter)?;
500///     let system_account = next_account_info(account_info_iter)?;
501///
502///     assert!(payer.is_signer);
503///     assert!(payer.is_writable);
504///     // Note that `new_account_pda` is not a signer yet.
505///     // This program will sign for it via `invoke_signed`.
506///     assert!(!new_account_pda.is_signer);
507///     assert!(new_account_pda.is_writable);
508///     assert!(system_program::check_id(system_account.key));
509///
510///     let new_account_seed = &instr.new_account_seed;
511///     let new_account_bump_seed = instr.new_account_bump_seed;
512///
513///     let rent = Rent::get()?
514///         .minimum_balance(instr.space.try_into().expect("overflow"));
515///
516///     invoke_signed(
517///         &system_instruction::create_account(
518///             payer.key,
519///             new_account_pda.key,
520///             rent,
521///             instr.space,
522///             &system_program::ID
523///         ),
524///         &[payer.clone(), new_account_pda.clone()],
525///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
526///     )?;
527///
528///     Ok(())
529/// }
530///
531/// # Ok::<(), anyhow::Error>(())
532/// ```
533pub fn create_account(
534    from_pubkey: &Pubkey,
535    to_pubkey: &Pubkey,
536    scoobies: u64,
537    space: u64,
538    owner: &Pubkey,
539) -> Instruction {
540    let account_metas = vec![
541        AccountMeta::new(*from_pubkey, true),
542        AccountMeta::new(*to_pubkey, true),
543    ];
544    Instruction::new_with_bincode(
545        system_program::id(),
546        &SystemInstruction::CreateAccount {
547            scoobies,
548            space,
549            owner: *owner,
550        },
551        account_metas,
552    )
553}
554
555// we accept `to` as a parameter so that callers do their own error handling when
556//   calling create_with_seed()
557pub fn create_account_with_seed(
558    from_pubkey: &Pubkey,
559    to_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner)
560    base: &Pubkey,
561    seed: &str,
562    scoobies: u64,
563    space: u64,
564    owner: &Pubkey,
565) -> Instruction {
566    let account_metas = vec![
567        AccountMeta::new(*from_pubkey, true),
568        AccountMeta::new(*to_pubkey, false),
569        AccountMeta::new_readonly(*base, true),
570    ];
571
572    Instruction::new_with_bincode(
573        system_program::id(),
574        &SystemInstruction::CreateAccountWithSeed {
575            base: *base,
576            seed: seed.to_string(),
577            scoobies,
578            space,
579            owner: *owner,
580        },
581        account_metas,
582    )
583}
584
585/// Assign ownership of an account from the system program.
586///
587/// This function produces an [`Instruction`] which must be submitted in a
588/// [`Transaction`] or [invoked] to take effect, containing a serialized
589/// [`SystemInstruction::Assign`].
590///
591/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
592/// [invoked]: crate::program::invoke
593///
594/// # Required signers
595///
596/// The `pubkey` signer must sign the transaction.
597///
598/// # Examples
599///
600/// These examples allocate space for an account, transfer it the minimum
601/// balance for rent exemption, and assign the account to a program.
602///
603/// ## Example: client-side RPC
604///
605/// This example submits the instructions from an RPC client.
606/// It assigns the account to a provided program account.
607/// The `payer` and `new_account` are signers.
608///
609/// ```
610/// # use cbe_program::example_mocks::{cbe_sdk, cbe_rpc_client};
611/// use cbe_rpc_client::rpc_client::RpcClient;
612/// use cbe_sdk::{
613///     pubkey::Pubkey,
614///     signature::{Keypair, Signer},
615///     system_instruction,
616///     transaction::Transaction,
617/// };
618/// use anyhow::Result;
619///
620/// fn create_account(
621///     client: &RpcClient,
622///     payer: &Keypair,
623///     new_account: &Keypair,
624///     owning_program: &Pubkey,
625///     space: u64,
626/// ) -> Result<()> {
627///     let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?;
628///
629///     let transfer_instr = system_instruction::transfer(
630///         &payer.pubkey(),
631///         &new_account.pubkey(),
632///         rent,
633///     );
634///
635///     let allocate_instr = system_instruction::allocate(
636///         &new_account.pubkey(),
637///         space,
638///     );
639///
640///     let assign_instr = system_instruction::assign(
641///         &new_account.pubkey(),
642///         owning_program,
643///     );
644///
645///     let blockhash = client.get_latest_blockhash()?;
646///     let tx = Transaction::new_signed_with_payer(
647///         &[transfer_instr, allocate_instr, assign_instr],
648///         Some(&payer.pubkey()),
649///         &[payer, new_account],
650///         blockhash,
651///     );
652///
653///     let _sig = client.send_and_confirm_transaction(&tx)?;
654///
655///     Ok(())
656/// }
657/// # let client = RpcClient::new(String::new());
658/// # let payer = Keypair::new();
659/// # let new_account = Keypair::new();
660/// # let owning_program = Pubkey::new_unique();
661/// # create_account(&client, &payer, &new_account, &owning_program, 1);
662/// #
663/// # Ok::<(), anyhow::Error>(())
664/// ```
665///
666/// ## Example: on-chain program
667///
668/// This example submits the instructions from an on-chain Cartallum CBE program. The
669/// created account is a [program derived address][pda], funded by `payer`, and
670/// assigned to the running program. The `payer` and `new_account_pda` are
671/// signers, with `new_account_pda` being signed for virtually by the program
672/// itself via [`invoke_signed`], `payer` being signed for by the client that
673/// submitted the transaction.
674///
675/// [pda]: Pubkey::find_program_address
676/// [`invoke_signed`]: crate::program::invoke_signed
677///
678/// ```
679/// # use borsh_derive::BorshDeserialize;
680/// # use borsh::BorshSerialize;
681/// # use borsh::de::BorshDeserialize;
682/// use cbe_program::{
683///     account_info::{next_account_info, AccountInfo},
684///     entrypoint,
685///     entrypoint::ProgramResult,
686///     msg,
687///     program::invoke_signed,
688///     pubkey::Pubkey,
689///     system_instruction,
690///     system_program,
691///     sysvar::rent::Rent,
692///     sysvar::Sysvar,
693/// };
694///
695/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
696/// pub struct CreateAccountInstruction {
697///     /// The PDA seed used to distinguish the new account from other PDAs
698///     pub new_account_seed: [u8; 16],
699///     /// The PDA bump seed
700///     pub new_account_bump_seed: u8,
701///     /// The amount of space to allocate for `new_account_pda`
702///     pub space: u64,
703/// }
704///
705/// entrypoint!(process_instruction);
706///
707/// fn process_instruction(
708///     program_id: &Pubkey,
709///     accounts: &[AccountInfo],
710///     instruction_data: &[u8],
711/// ) -> ProgramResult {
712///     let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;
713///
714///     let account_info_iter = &mut accounts.iter();
715///
716///     let payer = next_account_info(account_info_iter)?;
717///     let new_account_pda = next_account_info(account_info_iter)?;
718///     let system_account = next_account_info(account_info_iter)?;
719///
720///     assert!(payer.is_signer);
721///     assert!(payer.is_writable);
722///     // Note that `new_account_pda` is not a signer yet.
723///     // This program will sign for it via `invoke_signed`.
724///     assert!(!new_account_pda.is_signer);
725///     assert!(new_account_pda.is_writable);
726///     assert!(system_program::check_id(system_account.key));
727///
728///     let new_account_seed = &instr.new_account_seed;
729///     let new_account_bump_seed = instr.new_account_bump_seed;
730///
731///     let rent = Rent::get()?
732///         .minimum_balance(instr.space.try_into().expect("overflow"));
733///
734///     invoke_signed(
735///         &system_instruction::transfer(
736///             payer.key,
737///             new_account_pda.key,
738///             rent,
739///         ),
740///         &[payer.clone(), new_account_pda.clone()],
741///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
742///     )?;
743///
744///     invoke_signed(
745///         &system_instruction::allocate(
746///             new_account_pda.key,
747///             instr.space,
748///         ),
749///         &[new_account_pda.clone()],
750///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
751///     )?;
752///
753///     invoke_signed(
754///         &system_instruction::assign(
755///             new_account_pda.key,
756///             &program_id,
757///         ),
758///         &[new_account_pda.clone()],
759///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
760///     )?;
761///
762///     Ok(())
763/// }
764///
765/// # Ok::<(), anyhow::Error>(())
766/// ```
767pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
768    let account_metas = vec![AccountMeta::new(*pubkey, true)];
769    Instruction::new_with_bincode(
770        system_program::id(),
771        &SystemInstruction::Assign { owner: *owner },
772        account_metas,
773    )
774}
775
776pub fn assign_with_seed(
777    address: &Pubkey, // must match create_with_seed(base, seed, owner)
778    base: &Pubkey,
779    seed: &str,
780    owner: &Pubkey,
781) -> Instruction {
782    let account_metas = vec![
783        AccountMeta::new(*address, false),
784        AccountMeta::new_readonly(*base, true),
785    ];
786    Instruction::new_with_bincode(
787        system_program::id(),
788        &SystemInstruction::AssignWithSeed {
789            base: *base,
790            seed: seed.to_string(),
791            owner: *owner,
792        },
793        account_metas,
794    )
795}
796
797/// Transfer scoobies from an account owned by the system program.
798///
799/// This function produces an [`Instruction`] which must be submitted in a
800/// [`Transaction`] or [invoked] to take effect, containing a serialized
801/// [`SystemInstruction::Transfer`].
802///
803/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
804/// [invoked]: crate::program::invoke
805///
806/// # Required signers
807///
808/// The `from_pubkey` signer must sign the transaction.
809///
810/// # Examples
811///
812/// These examples allocate space for an account, transfer it the minimum
813/// balance for rent exemption, and assign the account to a program.
814///
815/// # Example: client-side RPC
816///
817/// This example submits the instructions from an RPC client.
818/// It assigns the account to a provided program account.
819/// The `payer` and `new_account` are signers.
820///
821/// ```
822/// # use cbe_program::example_mocks::{cbe_sdk, cbe_rpc_client};
823/// use cbe_rpc_client::rpc_client::RpcClient;
824/// use cbe_sdk::{
825///     pubkey::Pubkey,
826///     signature::{Keypair, Signer},
827///     system_instruction,
828///     transaction::Transaction,
829/// };
830/// use anyhow::Result;
831///
832/// fn create_account(
833///     client: &RpcClient,
834///     payer: &Keypair,
835///     new_account: &Keypair,
836///     owning_program: &Pubkey,
837///     space: u64,
838/// ) -> Result<()> {
839///     let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?;
840///
841///     let transfer_instr = system_instruction::transfer(
842///         &payer.pubkey(),
843///         &new_account.pubkey(),
844///         rent,
845///     );
846///
847///     let allocate_instr = system_instruction::allocate(
848///         &new_account.pubkey(),
849///         space,
850///     );
851///
852///     let assign_instr = system_instruction::assign(
853///         &new_account.pubkey(),
854///         owning_program,
855///     );
856///
857///     let blockhash = client.get_latest_blockhash()?;
858///     let tx = Transaction::new_signed_with_payer(
859///         &[transfer_instr, allocate_instr, assign_instr],
860///         Some(&payer.pubkey()),
861///         &[payer, new_account],
862///         blockhash,
863///     );
864///
865///     let _sig = client.send_and_confirm_transaction(&tx)?;
866///
867///     Ok(())
868/// }
869/// # let client = RpcClient::new(String::new());
870/// # let payer = Keypair::new();
871/// # let new_account = Keypair::new();
872/// # let owning_program = Pubkey::new_unique();
873/// # create_account(&client, &payer, &new_account, &owning_program, 1);
874/// #
875/// # Ok::<(), anyhow::Error>(())
876/// ```
877///
878/// ## Example: on-chain program
879///
880/// This example submits the instructions from an on-chain Cartallum CBE program. The
881/// created account is a [program derived address][pda], funded by `payer`, and
882/// assigned to the running program. The `payer` and `new_account_pda` are
883/// signers, with `new_account_pda` being signed for virtually by the program
884/// itself via [`invoke_signed`], `payer` being signed for by the client that
885/// submitted the transaction.
886///
887/// [pda]: Pubkey::find_program_address
888/// [`invoke_signed`]: crate::program::invoke_signed
889///
890/// ```
891/// # use borsh_derive::BorshDeserialize;
892/// # use borsh::BorshSerialize;
893/// # use borsh::de::BorshDeserialize;
894/// use cbe_program::{
895///     account_info::{next_account_info, AccountInfo},
896///     entrypoint,
897///     entrypoint::ProgramResult,
898///     msg,
899///     program::invoke_signed,
900///     pubkey::Pubkey,
901///     system_instruction,
902///     system_program,
903///     sysvar::rent::Rent,
904///     sysvar::Sysvar,
905/// };
906///
907/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
908/// pub struct CreateAccountInstruction {
909///     /// The PDA seed used to distinguish the new account from other PDAs
910///     pub new_account_seed: [u8; 16],
911///     /// The PDA bump seed
912///     pub new_account_bump_seed: u8,
913///     /// The amount of space to allocate for `new_account_pda`
914///     pub space: u64,
915/// }
916///
917/// entrypoint!(process_instruction);
918///
919/// fn process_instruction(
920///     program_id: &Pubkey,
921///     accounts: &[AccountInfo],
922///     instruction_data: &[u8],
923/// ) -> ProgramResult {
924///     let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;
925///
926///     let account_info_iter = &mut accounts.iter();
927///
928///     let payer = next_account_info(account_info_iter)?;
929///     let new_account_pda = next_account_info(account_info_iter)?;
930///     let system_account = next_account_info(account_info_iter)?;
931///
932///     assert!(payer.is_signer);
933///     assert!(payer.is_writable);
934///     // Note that `new_account_pda` is not a signer yet.
935///     // This program will sign for it via `invoke_signed`.
936///     assert!(!new_account_pda.is_signer);
937///     assert!(new_account_pda.is_writable);
938///     assert!(system_program::check_id(system_account.key));
939///
940///     let new_account_seed = &instr.new_account_seed;
941///     let new_account_bump_seed = instr.new_account_bump_seed;
942///
943///     let rent = Rent::get()?
944///         .minimum_balance(instr.space.try_into().expect("overflow"));
945///
946///     invoke_signed(
947///         &system_instruction::transfer(
948///             payer.key,
949///             new_account_pda.key,
950///             rent,
951///         ),
952///         &[payer.clone(), new_account_pda.clone()],
953///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
954///     )?;
955///
956///     invoke_signed(
957///         &system_instruction::allocate(
958///             new_account_pda.key,
959///             instr.space,
960///         ),
961///         &[new_account_pda.clone()],
962///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
963///     )?;
964///
965///     invoke_signed(
966///         &system_instruction::assign(
967///             new_account_pda.key,
968///             &program_id,
969///         ),
970///         &[new_account_pda.clone()],
971///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
972///     )?;
973///
974///     Ok(())
975/// }
976///
977/// # Ok::<(), anyhow::Error>(())
978/// ```
979pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, scoobies: u64) -> Instruction {
980    let account_metas = vec![
981        AccountMeta::new(*from_pubkey, true),
982        AccountMeta::new(*to_pubkey, false),
983    ];
984    Instruction::new_with_bincode(
985        system_program::id(),
986        &SystemInstruction::Transfer { scoobies },
987        account_metas,
988    )
989}
990
991pub fn transfer_with_seed(
992    from_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner)
993    from_base: &Pubkey,
994    from_seed: String,
995    from_owner: &Pubkey,
996    to_pubkey: &Pubkey,
997    scoobies: u64,
998) -> Instruction {
999    let account_metas = vec![
1000        AccountMeta::new(*from_pubkey, false),
1001        AccountMeta::new_readonly(*from_base, true),
1002        AccountMeta::new(*to_pubkey, false),
1003    ];
1004    Instruction::new_with_bincode(
1005        system_program::id(),
1006        &SystemInstruction::TransferWithSeed {
1007            scoobies,
1008            from_seed,
1009            from_owner: *from_owner,
1010        },
1011        account_metas,
1012    )
1013}
1014
1015/// Allocate space for an account.
1016///
1017/// This function produces an [`Instruction`] which must be submitted in a
1018/// [`Transaction`] or [invoked] to take effect, containing a serialized
1019/// [`SystemInstruction::Allocate`].
1020///
1021/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
1022/// [invoked]: crate::program::invoke
1023///
1024/// The transaction will fail if the account already has size greater than 0,
1025/// or if the requested size is greater than [`MAX_PERMITTED_DATA_LENGTH`].
1026///
1027/// # Required signers
1028///
1029/// The `pubkey` signer must sign the transaction.
1030///
1031/// # Examples
1032///
1033/// These examples allocate space for an account, transfer it the minimum
1034/// balance for rent exemption, and assign the account to a program.
1035///
1036/// # Example: client-side RPC
1037///
1038/// This example submits the instructions from an RPC client.
1039/// It assigns the account to a provided program account.
1040/// The `payer` and `new_account` are signers.
1041///
1042/// ```
1043/// # use cbe_program::example_mocks::{cbe_sdk, cbe_rpc_client};
1044/// use cbe_rpc_client::rpc_client::RpcClient;
1045/// use cbe_sdk::{
1046///     pubkey::Pubkey,
1047///     signature::{Keypair, Signer},
1048///     system_instruction,
1049///     transaction::Transaction,
1050/// };
1051/// use anyhow::Result;
1052///
1053/// fn create_account(
1054///     client: &RpcClient,
1055///     payer: &Keypair,
1056///     new_account: &Keypair,
1057///     owning_program: &Pubkey,
1058///     space: u64,
1059/// ) -> Result<()> {
1060///     let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?;
1061///
1062///     let transfer_instr = system_instruction::transfer(
1063///         &payer.pubkey(),
1064///         &new_account.pubkey(),
1065///         rent,
1066///     );
1067///
1068///     let allocate_instr = system_instruction::allocate(
1069///         &new_account.pubkey(),
1070///         space,
1071///     );
1072///
1073///     let assign_instr = system_instruction::assign(
1074///         &new_account.pubkey(),
1075///         owning_program,
1076///     );
1077///
1078///     let blockhash = client.get_latest_blockhash()?;
1079///     let tx = Transaction::new_signed_with_payer(
1080///         &[transfer_instr, allocate_instr, assign_instr],
1081///         Some(&payer.pubkey()),
1082///         &[payer, new_account],
1083///         blockhash,
1084///     );
1085///
1086///     let _sig = client.send_and_confirm_transaction(&tx)?;
1087///
1088///     Ok(())
1089/// }
1090/// # let client = RpcClient::new(String::new());
1091/// # let payer = Keypair::new();
1092/// # let new_account = Keypair::new();
1093/// # let owning_program = Pubkey::new_unique();
1094/// # create_account(&client, &payer, &new_account, &owning_program, 1);
1095/// #
1096/// # Ok::<(), anyhow::Error>(())
1097/// ```
1098///
1099/// ## Example: on-chain program
1100///
1101/// This example submits the instructions from an on-chain Cartallum CBE program. The
1102/// created account is a [program derived address][pda], funded by `payer`, and
1103/// assigned to the running program. The `payer` and `new_account_pda` are
1104/// signers, with `new_account_pda` being signed for virtually by the program
1105/// itself via [`invoke_signed`], `payer` being signed for by the client that
1106/// submitted the transaction.
1107///
1108/// [pda]: Pubkey::find_program_address
1109/// [`invoke_signed`]: crate::program::invoke_signed
1110///
1111/// ```
1112/// # use borsh_derive::BorshDeserialize;
1113/// # use borsh::BorshSerialize;
1114/// # use borsh::de::BorshDeserialize;
1115/// use cbe_program::{
1116///     account_info::{next_account_info, AccountInfo},
1117///     entrypoint,
1118///     entrypoint::ProgramResult,
1119///     msg,
1120///     program::invoke_signed,
1121///     pubkey::Pubkey,
1122///     system_instruction,
1123///     system_program,
1124///     sysvar::rent::Rent,
1125///     sysvar::Sysvar,
1126/// };
1127///
1128/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
1129/// pub struct CreateAccountInstruction {
1130///     /// The PDA seed used to distinguish the new account from other PDAs
1131///     pub new_account_seed: [u8; 16],
1132///     /// The PDA bump seed
1133///     pub new_account_bump_seed: u8,
1134///     /// The amount of space to allocate for `new_account_pda`
1135///     pub space: u64,
1136/// }
1137///
1138/// entrypoint!(process_instruction);
1139///
1140/// fn process_instruction(
1141///     program_id: &Pubkey,
1142///     accounts: &[AccountInfo],
1143///     instruction_data: &[u8],
1144/// ) -> ProgramResult {
1145///     let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;
1146///
1147///     let account_info_iter = &mut accounts.iter();
1148///
1149///     let payer = next_account_info(account_info_iter)?;
1150///     let new_account_pda = next_account_info(account_info_iter)?;
1151///     let system_account = next_account_info(account_info_iter)?;
1152///
1153///     assert!(payer.is_signer);
1154///     assert!(payer.is_writable);
1155///     // Note that `new_account_pda` is not a signer yet.
1156///     // This program will sign for it via `invoke_signed`.
1157///     assert!(!new_account_pda.is_signer);
1158///     assert!(new_account_pda.is_writable);
1159///     assert!(system_program::check_id(system_account.key));
1160///
1161///     let new_account_seed = &instr.new_account_seed;
1162///     let new_account_bump_seed = instr.new_account_bump_seed;
1163///
1164///     let rent = Rent::get()?
1165///         .minimum_balance(instr.space.try_into().expect("overflow"));
1166///
1167///     invoke_signed(
1168///         &system_instruction::transfer(
1169///             payer.key,
1170///             new_account_pda.key,
1171///             rent,
1172///         ),
1173///         &[payer.clone(), new_account_pda.clone()],
1174///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
1175///     )?;
1176///
1177///     invoke_signed(
1178///         &system_instruction::allocate(
1179///             new_account_pda.key,
1180///             instr.space,
1181///         ),
1182///         &[new_account_pda.clone()],
1183///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
1184///     )?;
1185///
1186///     invoke_signed(
1187///         &system_instruction::assign(
1188///             new_account_pda.key,
1189///             &program_id,
1190///         ),
1191///         &[new_account_pda.clone()],
1192///         &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]],
1193///     )?;
1194///
1195///     Ok(())
1196/// }
1197///
1198/// # Ok::<(), anyhow::Error>(())
1199/// ```
1200pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
1201    let account_metas = vec![AccountMeta::new(*pubkey, true)];
1202    Instruction::new_with_bincode(
1203        system_program::id(),
1204        &SystemInstruction::Allocate { space },
1205        account_metas,
1206    )
1207}
1208
1209pub fn allocate_with_seed(
1210    address: &Pubkey, // must match create_with_seed(base, seed, owner)
1211    base: &Pubkey,
1212    seed: &str,
1213    space: u64,
1214    owner: &Pubkey,
1215) -> Instruction {
1216    let account_metas = vec![
1217        AccountMeta::new(*address, false),
1218        AccountMeta::new_readonly(*base, true),
1219    ];
1220    Instruction::new_with_bincode(
1221        system_program::id(),
1222        &SystemInstruction::AllocateWithSeed {
1223            base: *base,
1224            seed: seed.to_string(),
1225            space,
1226            owner: *owner,
1227        },
1228        account_metas,
1229    )
1230}
1231
1232/// Transfer scoobies from an account owned by the system program to multiple accounts.
1233///
1234/// This function produces a vector of [`Instruction`]s which must be submitted
1235/// in a [`Transaction`] or [invoked] to take effect, containing serialized
1236/// [`SystemInstruction::Transfer`]s.
1237///
1238/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
1239/// [invoked]: crate::program::invoke
1240///
1241/// # Required signers
1242///
1243/// The `from_pubkey` signer must sign the transaction.
1244///
1245/// # Examples
1246///
1247/// ## Example: client-side RPC
1248///
1249/// This example performs multiple transfers in a single transaction.
1250///
1251/// ```
1252/// # use cbe_program::example_mocks::{cbe_sdk, cbe_rpc_client};
1253/// use cbe_rpc_client::rpc_client::RpcClient;
1254/// use cbe_sdk::{
1255///     pubkey::Pubkey,
1256///     signature::{Keypair, Signer},
1257///     system_instruction,
1258///     transaction::Transaction,
1259/// };
1260/// use anyhow::Result;
1261///
1262/// fn transfer_scoobies_to_many(
1263///     client: &RpcClient,
1264///     from: &Keypair,
1265///     to_and_amount: &[(Pubkey, u64)],
1266/// ) -> Result<()> {
1267///     let instrs = system_instruction::transfer_many(&from.pubkey(), to_and_amount);
1268///
1269///     let blockhash = client.get_latest_blockhash()?;
1270///     let tx = Transaction::new_signed_with_payer(
1271///         &instrs,
1272///         Some(&from.pubkey()),
1273///         &[from],
1274///         blockhash,
1275///     );
1276///
1277///     let _sig = client.send_and_confirm_transaction(&tx)?;
1278///
1279///     Ok(())
1280/// }
1281/// # let from = Keypair::new();
1282/// # let to_and_amount = vec![
1283/// #     (Pubkey::new_unique(), 1_000),
1284/// #     (Pubkey::new_unique(), 2_000),
1285/// #     (Pubkey::new_unique(), 3_000),
1286/// # ];
1287/// # let client = RpcClient::new(String::new());
1288/// # transfer_scoobies_to_many(&client, &from, &to_and_amount);
1289/// #
1290/// # Ok::<(), anyhow::Error>(())
1291/// ```
1292///
1293/// ## Example: on-chain program
1294///
1295/// This example makes multiple transfers out of a "bank" account,
1296/// a [program derived address][pda] owned by the calling program.
1297/// This example submits the instructions from an on-chain Cartallum CBE program. The
1298/// created account is a [program derived address][pda], and it is assigned to
1299/// the running program. The `payer` and `new_account_pda` are signers, with
1300/// `new_account_pda` being signed for virtually by the program itself via
1301/// [`invoke_signed`], `payer` being signed for by the client that submitted the
1302/// transaction.
1303///
1304/// [pda]: Pubkey::find_program_address
1305/// [`invoke_signed`]: crate::program::invoke_signed
1306///
1307/// ```
1308/// # use borsh_derive::BorshDeserialize;
1309/// # use borsh::BorshSerialize;
1310/// # use borsh::de::BorshDeserialize;
1311/// use cbe_program::{
1312///     account_info::{next_account_info, next_account_infos, AccountInfo},
1313///     entrypoint,
1314///     entrypoint::ProgramResult,
1315///     msg,
1316///     program::invoke_signed,
1317///     pubkey::Pubkey,
1318///     system_instruction,
1319///     system_program,
1320/// };
1321///
1322/// /// # Accounts
1323/// ///
1324/// /// - 0: bank_pda - writable
1325/// /// - 1: system_program - executable
1326/// /// - *: to - writable
1327/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
1328/// pub struct TransferScoobiesToManyInstruction {
1329///     pub bank_pda_bump_seed: u8,
1330///     pub amount_list: Vec<u64>,
1331/// }
1332///
1333/// entrypoint!(process_instruction);
1334///
1335/// fn process_instruction(
1336///     program_id: &Pubkey,
1337///     accounts: &[AccountInfo],
1338///     instruction_data: &[u8],
1339/// ) -> ProgramResult {
1340///     let instr = TransferScoobiesToManyInstruction::deserialize(&mut &instruction_data[..])?;
1341///
1342///     let account_info_iter = &mut accounts.iter();
1343///
1344///     let bank_pda = next_account_info(account_info_iter)?;
1345///     let bank_pda_bump_seed = instr.bank_pda_bump_seed;
1346///     let system_account = next_account_info(account_info_iter)?;
1347///
1348///     assert!(system_program::check_id(system_account.key));
1349///
1350///     let to_accounts = next_account_infos(account_info_iter, account_info_iter.len())?;
1351///
1352///     for to_account in to_accounts {
1353///          assert!(to_account.is_writable);
1354///          // ... do other verification ...
1355///     }
1356///
1357///     let to_and_amount = to_accounts
1358///         .iter()
1359///         .zip(instr.amount_list.iter())
1360///         .map(|(to, amount)| (*to.key, *amount))
1361///         .collect::<Vec<(Pubkey, u64)>>();
1362///
1363///     let instrs = system_instruction::transfer_many(bank_pda.key, to_and_amount.as_ref());
1364///
1365///     for instr in instrs {
1366///         invoke_signed(&instr, accounts, &[&[b"bank", &[bank_pda_bump_seed]]])?;
1367///     }
1368///
1369///     Ok(())
1370/// }
1371///
1372/// # Ok::<(), anyhow::Error>(())
1373/// ```
1374pub fn transfer_many(from_pubkey: &Pubkey, to_scoobies: &[(Pubkey, u64)]) -> Vec<Instruction> {
1375    to_scoobies
1376        .iter()
1377        .map(|(to_pubkey, scoobies)| transfer(from_pubkey, to_pubkey, *scoobies))
1378        .collect()
1379}
1380
1381pub fn create_nonce_account_with_seed(
1382    from_pubkey: &Pubkey,
1383    nonce_pubkey: &Pubkey,
1384    base: &Pubkey,
1385    seed: &str,
1386    authority: &Pubkey,
1387    scoobies: u64,
1388) -> Vec<Instruction> {
1389    vec![
1390        create_account_with_seed(
1391            from_pubkey,
1392            nonce_pubkey,
1393            base,
1394            seed,
1395            scoobies,
1396            nonce::State::size() as u64,
1397            &system_program::id(),
1398        ),
1399        Instruction::new_with_bincode(
1400            system_program::id(),
1401            &SystemInstruction::InitializeNonceAccount(*authority),
1402            vec![
1403                AccountMeta::new(*nonce_pubkey, false),
1404                #[allow(deprecated)]
1405                AccountMeta::new_readonly(recent_blockhashes::id(), false),
1406                AccountMeta::new_readonly(rent::id(), false),
1407            ],
1408        ),
1409    ]
1410}
1411
1412/// Create an account containing a durable transaction nonce.
1413///
1414/// This function produces a vector of [`Instruction`]s which must be submitted
1415/// in a [`Transaction`] or [invoked] to take effect, containing a serialized
1416/// [`SystemInstruction::CreateAccount`] and
1417/// [`SystemInstruction::InitializeNonceAccount`].
1418///
1419/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
1420/// [invoked]: crate::program::invoke
1421///
1422/// A [durable transaction nonce][dtn] is a special account that enables
1423/// execution of transactions that have been signed in the past.
1424///
1425/// Standard Cartallum CBE transactions include a [recent blockhash][rbh] (sometimes
1426/// referred to as a _[nonce]_). During execution the Cartallum CBE runtime verifies
1427/// the recent blockhash is approximately less than two minutes old, and that in
1428/// those two minutes no other identical transaction with the same blockhash has
1429/// been executed. These checks prevent accidental replay of transactions.
1430/// Consequently, it is not possible to sign a transaction, wait more than two
1431/// minutes, then successfully execute that transaction.
1432///
1433/// [dtn]: https://docs.cartallum.com/implemented-proposals/durable-tx-nonces
1434/// [rbh]: crate::message::Message::recent_blockhash
1435/// [nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce
1436///
1437/// Durable transaction nonces are an alternative to the standard recent
1438/// blockhash nonce. They are stored in accounts on chain, and every time they
1439/// are used their value is changed to a new value for their next use. The
1440/// runtime verifies that each durable nonce value is only used once, and there
1441/// are no restrictions on how "old" the nonce is. Because they are stored on
1442/// chain and require additional instructions to use, transacting with durable
1443/// transaction nonces is more expensive than with standard transactions.
1444///
1445/// The value of the durable nonce is itself a blockhash and is accessible via
1446/// the [`blockhash`] field of [`nonce::state::Data`], which is deserialized
1447/// from the nonce account data.
1448///
1449/// [`blockhash`]: crate::nonce::state::Data::blockhash
1450/// [`nonce::state::Data`]: crate::nonce::state::Data
1451///
1452/// The basic durable transaction nonce lifecycle is
1453///
1454/// 1) Create the nonce account with the `create_nonce_account` instruction.
1455/// 2) Submit specially-formed transactions that include the
1456///    [`advance_nonce_account`] instruction.
1457/// 3) Destroy the nonce account by withdrawing its scoobies with the
1458///    [`withdraw_nonce_account`] instruction.
1459///
1460/// Nonce accounts have an associated _authority_ account, which is stored in
1461/// their account data, and can be changed with the [`authorize_nonce_account`]
1462/// instruction. The authority must sign transactions that include the
1463/// `advance_nonce_account`, `authorize_nonce_account` and
1464/// `withdraw_nonce_account` instructions.
1465///
1466/// Nonce accounts are owned by the system program.
1467///
1468/// This constructor creates a [`SystemInstruction::CreateAccount`] instruction
1469/// and a [`SystemInstruction::InitializeNonceAccount`] instruction.
1470///
1471/// # Required signers
1472///
1473/// The `from_pubkey` and `nonce_pubkey` signers must sign the transaction.
1474///
1475/// # Examples
1476///
1477/// Create a nonce account from an off-chain client:
1478///
1479/// ```
1480/// # use cbe_program::example_mocks::cbe_sdk;
1481/// # use cbe_program::example_mocks::cbe_rpc_client;
1482/// use cbe_rpc_client::rpc_client::RpcClient;
1483/// use cbe_sdk::{
1484/// #   pubkey::Pubkey,
1485///     signature::{Keypair, Signer},
1486///     system_instruction,
1487///     transaction::Transaction,
1488///     nonce::State,
1489/// };
1490/// use anyhow::Result;
1491///
1492/// fn submit_create_nonce_account_tx(
1493///     client: &RpcClient,
1494///     payer: &Keypair,
1495/// ) -> Result<()> {
1496///
1497///     let nonce_account = Keypair::new();
1498///
1499///     let nonce_rent = client.get_minimum_balance_for_rent_exemption(State::size())?;
1500///     let instr = system_instruction::create_nonce_account(
1501///         &payer.pubkey(),
1502///         &nonce_account.pubkey(),
1503///         &payer.pubkey(), // Make the fee payer the nonce account authority
1504///         nonce_rent,
1505///     );
1506///
1507///     let mut tx = Transaction::new_with_payer(&instr, Some(&payer.pubkey()));
1508///
1509///     let blockhash = client.get_latest_blockhash()?;
1510///     tx.try_sign(&[&nonce_account, payer], blockhash)?;
1511///
1512///     client.send_and_confirm_transaction(&tx)?;
1513///
1514///     Ok(())
1515/// }
1516/// #
1517/// # let client = RpcClient::new(String::new());
1518/// # let payer = Keypair::new();
1519/// # submit_create_nonce_account_tx(&client, &payer)?;
1520/// #
1521/// # Ok::<(), anyhow::Error>(())
1522/// ```
1523pub fn create_nonce_account(
1524    from_pubkey: &Pubkey,
1525    nonce_pubkey: &Pubkey,
1526    authority: &Pubkey,
1527    scoobies: u64,
1528) -> Vec<Instruction> {
1529    vec![
1530        create_account(
1531            from_pubkey,
1532            nonce_pubkey,
1533            scoobies,
1534            nonce::State::size() as u64,
1535            &system_program::id(),
1536        ),
1537        Instruction::new_with_bincode(
1538            system_program::id(),
1539            &SystemInstruction::InitializeNonceAccount(*authority),
1540            vec![
1541                AccountMeta::new(*nonce_pubkey, false),
1542                #[allow(deprecated)]
1543                AccountMeta::new_readonly(recent_blockhashes::id(), false),
1544                AccountMeta::new_readonly(rent::id(), false),
1545            ],
1546        ),
1547    ]
1548}
1549
1550/// Advance the value of a durable transaction nonce.
1551///
1552/// This function produces an [`Instruction`] which must be submitted in a
1553/// [`Transaction`] or [invoked] to take effect, containing a serialized
1554/// [`SystemInstruction::AdvanceNonceAccount`].
1555///
1556/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
1557/// [invoked]: crate::program::invoke
1558///
1559/// Every transaction that relies on a durable transaction nonce must contain a
1560/// [`SystemInstruction::AdvanceNonceAccount`] instruction as the first
1561/// instruction in the [`Message`], as created by this function. When included
1562/// in the first position, the Cartallum CBE runtime recognizes the transaction as one
1563/// that relies on a durable transaction nonce and processes it accordingly. The
1564/// [`Message::new_with_nonce`] function can be used to construct a `Message` in
1565/// the correct format without calling `advance_nonce_account` directly.
1566///
1567/// When constructing a transaction that includes an `AdvanceNonceInstruction`
1568/// the [`recent_blockhash`] must be treated differently &mdash; instead of
1569/// setting it to a recent blockhash, the value of the nonce must be retreived
1570/// and deserialized from the nonce account, and that value specified as the
1571/// "recent blockhash". A nonce account can be deserialized with the
1572/// [`cbe_rpc_client_nonce_utils::data_from_account`][dfa] function.
1573///
1574/// For further description of durable transaction nonces see
1575/// [`create_nonce_account`].
1576///
1577/// [`Message`]: crate::message::Message
1578/// [`Message::new_with_nonce`]: crate::message::Message::new_with_nonce
1579/// [`recent_blockhash`]: crate::message::Message::recent_blockhash
1580/// [dfa]: https://docs.cartallum.com/cbe-rpc-client-nonce-utils/latest/cbe_rpc_client_nonce_utils/fn.data_from_account.html
1581///
1582/// # Required signers
1583///
1584/// The `authorized_pubkey` signer must sign the transaction.
1585///
1586/// # Examples
1587///
1588/// Create and sign a transaction with a durable nonce:
1589///
1590/// ```
1591/// # use cbe_program::example_mocks::cbe_sdk;
1592/// # use cbe_program::example_mocks::cbe_rpc_client;
1593/// # use cbe_program::example_mocks::cbe_rpc_client_nonce_utils;
1594/// use cbe_rpc_client::rpc_client::RpcClient;
1595/// use cbe_sdk::{
1596///     message::Message,
1597///     pubkey::Pubkey,
1598///     signature::{Keypair, Signer},
1599///     system_instruction,
1600///     transaction::Transaction,
1601/// };
1602/// # use cbe_sdk::account::Account;
1603/// use std::path::Path;
1604/// use anyhow::Result;
1605/// # use anyhow::anyhow;
1606///
1607/// fn create_transfer_tx_with_nonce(
1608///     client: &RpcClient,
1609///     nonce_account_pubkey: &Pubkey,
1610///     payer: &Keypair,
1611///     receiver: &Pubkey,
1612///     amount: u64,
1613///     tx_path: &Path,
1614/// ) -> Result<()> {
1615///
1616///     let instr_transfer = system_instruction::transfer(
1617///         &payer.pubkey(),
1618///         receiver,
1619///         amount,
1620///     );
1621///
1622///     // In this example, `payer` is `nonce_account_pubkey`'s authority
1623///     let instr_advance_nonce_account = system_instruction::advance_nonce_account(
1624///         nonce_account_pubkey,
1625///         &payer.pubkey(),
1626///     );
1627///
1628///     // The `advance_nonce_account` instruction must be the first issued in
1629///     // the transaction.
1630///     let message = Message::new(
1631///         &[
1632///             instr_advance_nonce_account,
1633///             instr_transfer
1634///         ],
1635///         Some(&payer.pubkey()),
1636///     );
1637///
1638///     let mut tx = Transaction::new_unsigned(message);
1639///
1640///     // Sign the tx with nonce_account's `blockhash` instead of the
1641///     // network's latest blockhash.
1642///     # client.set_get_account_response(*nonce_account_pubkey, Account {
1643///     #   scoobies: 1,
1644///     #   data: vec![0],
1645///     #   owner: cbe_sdk::system_program::ID,
1646///     #   executable: false,
1647///     #   rent_epoch: 1,
1648///     # });
1649///     let nonce_account = client.get_account(nonce_account_pubkey)?;
1650///     let nonce_data = cbe_rpc_client_nonce_utils::data_from_account(&nonce_account)?;
1651///     let blockhash = nonce_data.blockhash();
1652///
1653///     tx.try_sign(&[payer], blockhash)?;
1654///
1655///     // Save the signed transaction locally for later submission.
1656///     save_tx_to_file(&tx_path, &tx)?;
1657///
1658///     Ok(())
1659/// }
1660/// #
1661/// # fn save_tx_to_file(path: &Path, tx: &Transaction) -> Result<()> {
1662/// #     Ok(())
1663/// # }
1664/// #
1665/// # let client = RpcClient::new(String::new());
1666/// # let nonce_account_pubkey = Pubkey::new_unique();
1667/// # let payer = Keypair::new();
1668/// # let receiver = Pubkey::new_unique();
1669/// # create_transfer_tx_with_nonce(&client, &nonce_account_pubkey, &payer, &receiver, 1024, Path::new("new_tx"))?;
1670/// #
1671/// # Ok::<(), anyhow::Error>(())
1672/// ```
1673pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
1674    let account_metas = vec![
1675        AccountMeta::new(*nonce_pubkey, false),
1676        #[allow(deprecated)]
1677        AccountMeta::new_readonly(recent_blockhashes::id(), false),
1678        AccountMeta::new_readonly(*authorized_pubkey, true),
1679    ];
1680    Instruction::new_with_bincode(
1681        system_program::id(),
1682        &SystemInstruction::AdvanceNonceAccount,
1683        account_metas,
1684    )
1685}
1686
1687/// Withdraw scoobies from a durable transaction nonce account.
1688///
1689/// This function produces an [`Instruction`] which must be submitted in a
1690/// [`Transaction`] or [invoked] to take effect, containing a serialized
1691/// [`SystemInstruction::WithdrawNonceAccount`].
1692///
1693/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
1694/// [invoked]: crate::program::invoke
1695///
1696/// Withdrawing the entire balance of a nonce account will cause the runtime to
1697/// destroy it upon successful completion of the transaction.
1698///
1699/// Otherwise, nonce accounts must maintain a balance greater than or equal to
1700/// the minimum required for [rent exemption]. If the result of this instruction
1701/// would leave the nonce account with a balance less than required for rent
1702/// exemption, but also greater than zero, then the transaction will fail.
1703///
1704/// [rent exemption]: https://docs.cartallum.com/developing/programming-model/accounts#rent-exemption
1705///
1706/// This constructor creates a [`SystemInstruction::WithdrawNonceAccount`]
1707/// instruction.
1708///
1709/// # Required signers
1710///
1711/// The `authorized_pubkey` signer must sign the transaction.
1712///
1713/// # Examples
1714///
1715/// ```
1716/// # use cbe_program::example_mocks::cbe_sdk;
1717/// # use cbe_program::example_mocks::cbe_rpc_client;
1718/// use cbe_rpc_client::rpc_client::RpcClient;
1719/// use cbe_sdk::{
1720///     pubkey::Pubkey,
1721///     signature::{Keypair, Signer},
1722///     system_instruction,
1723///     transaction::Transaction,
1724/// };
1725/// use anyhow::Result;
1726///
1727/// fn submit_withdraw_nonce_account_tx(
1728///     client: &RpcClient,
1729///     nonce_account_pubkey: &Pubkey,
1730///     authorized_account: &Keypair,
1731/// ) -> Result<()> {
1732///
1733///     let nonce_balance = client.get_balance(nonce_account_pubkey)?;
1734///
1735///     let instr = system_instruction::withdraw_nonce_account(
1736///         &nonce_account_pubkey,
1737///         &authorized_account.pubkey(),
1738///         &authorized_account.pubkey(),
1739///         nonce_balance,
1740///     );
1741///
1742///     let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey()));
1743///
1744///     let blockhash = client.get_latest_blockhash()?;
1745///     tx.try_sign(&[authorized_account], blockhash)?;
1746///
1747///     client.send_and_confirm_transaction(&tx)?;
1748///
1749///     Ok(())
1750/// }
1751/// #
1752/// # let client = RpcClient::new(String::new());
1753/// # let nonce_account_pubkey = Pubkey::new_unique();
1754/// # let payer = Keypair::new();
1755/// # submit_withdraw_nonce_account_tx(&client, &nonce_account_pubkey, &payer)?;
1756/// #
1757/// # Ok::<(), anyhow::Error>(())
1758/// ```
1759pub fn withdraw_nonce_account(
1760    nonce_pubkey: &Pubkey,
1761    authorized_pubkey: &Pubkey,
1762    to_pubkey: &Pubkey,
1763    scoobies: u64,
1764) -> Instruction {
1765    let account_metas = vec![
1766        AccountMeta::new(*nonce_pubkey, false),
1767        AccountMeta::new(*to_pubkey, false),
1768        #[allow(deprecated)]
1769        AccountMeta::new_readonly(recent_blockhashes::id(), false),
1770        AccountMeta::new_readonly(rent::id(), false),
1771        AccountMeta::new_readonly(*authorized_pubkey, true),
1772    ];
1773    Instruction::new_with_bincode(
1774        system_program::id(),
1775        &SystemInstruction::WithdrawNonceAccount(scoobies),
1776        account_metas,
1777    )
1778}
1779
1780/// Change the authority of a durable transaction nonce account.
1781///
1782/// This function produces an [`Instruction`] which must be submitted in a
1783/// [`Transaction`] or [invoked] to take effect, containing a serialized
1784/// [`SystemInstruction::AuthorizeNonceAccount`].
1785///
1786/// [`Transaction`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/transaction/struct.Transaction.html
1787/// [invoked]: crate::program::invoke
1788///
1789/// This constructor creates a [`SystemInstruction::AuthorizeNonceAccount`]
1790/// instruction.
1791///
1792/// # Required signers
1793///
1794/// The `authorized_pubkey` signer must sign the transaction.
1795///
1796/// # Examples
1797///
1798/// ```
1799/// # use cbe_program::example_mocks::cbe_sdk;
1800/// # use cbe_program::example_mocks::cbe_rpc_client;
1801/// use cbe_rpc_client::rpc_client::RpcClient;
1802/// use cbe_sdk::{
1803///     pubkey::Pubkey,
1804///     signature::{Keypair, Signer},
1805///     system_instruction,
1806///     transaction::Transaction,
1807/// };
1808/// use anyhow::Result;
1809///
1810/// fn authorize_nonce_account_tx(
1811///     client: &RpcClient,
1812///     nonce_account_pubkey: &Pubkey,
1813///     authorized_account: &Keypair,
1814///     new_authority_pubkey: &Pubkey,
1815/// ) -> Result<()> {
1816///
1817///     let instr = system_instruction::authorize_nonce_account(
1818///         &nonce_account_pubkey,
1819///         &authorized_account.pubkey(),
1820///         &new_authority_pubkey,
1821///     );
1822///
1823///     let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey()));
1824///
1825///     let blockhash = client.get_latest_blockhash()?;
1826///     tx.try_sign(&[authorized_account], blockhash)?;
1827///
1828///     client.send_and_confirm_transaction(&tx)?;
1829///
1830///     Ok(())
1831/// }
1832/// #
1833/// # let client = RpcClient::new(String::new());
1834/// # let nonce_account_pubkey = Pubkey::new_unique();
1835/// # let payer = Keypair::new();
1836/// # let new_authority_pubkey = Pubkey::new_unique();
1837/// # authorize_nonce_account_tx(&client, &nonce_account_pubkey, &payer, &new_authority_pubkey)?;
1838/// #
1839/// # Ok::<(), anyhow::Error>(())
1840/// ```
1841pub fn authorize_nonce_account(
1842    nonce_pubkey: &Pubkey,
1843    authorized_pubkey: &Pubkey,
1844    new_authority: &Pubkey,
1845) -> Instruction {
1846    let account_metas = vec![
1847        AccountMeta::new(*nonce_pubkey, false),
1848        AccountMeta::new_readonly(*authorized_pubkey, true),
1849    ];
1850    Instruction::new_with_bincode(
1851        system_program::id(),
1852        &SystemInstruction::AuthorizeNonceAccount(*new_authority),
1853        account_metas,
1854    )
1855}
1856
1857/// One-time idempotent upgrade of legacy nonce versions in order to bump
1858/// them out of chain blockhash domain.
1859pub fn upgrade_nonce_account(nonce_pubkey: Pubkey) -> Instruction {
1860    let account_metas = vec![AccountMeta::new(nonce_pubkey, /*is_signer:*/ false)];
1861    Instruction::new_with_bincode(
1862        system_program::id(),
1863        &SystemInstruction::UpgradeNonceAccount,
1864        account_metas,
1865    )
1866}
1867
1868#[cfg(test)]
1869mod tests {
1870    use {
1871        super::*,
1872        crate::instruction::{Instruction, InstructionError},
1873        num_traits::ToPrimitive,
1874    };
1875
1876    fn get_keys(instruction: &Instruction) -> Vec<Pubkey> {
1877        instruction.accounts.iter().map(|x| x.pubkey).collect()
1878    }
1879
1880    #[test]
1881    fn test_move_many() {
1882        let alice_pubkey = Pubkey::new_unique();
1883        let bob_pubkey = Pubkey::new_unique();
1884        let carol_pubkey = Pubkey::new_unique();
1885        let to_scoobies = vec![(bob_pubkey, 1), (carol_pubkey, 2)];
1886
1887        let instructions = transfer_many(&alice_pubkey, &to_scoobies);
1888        assert_eq!(instructions.len(), 2);
1889        assert_eq!(get_keys(&instructions[0]), vec![alice_pubkey, bob_pubkey]);
1890        assert_eq!(get_keys(&instructions[1]), vec![alice_pubkey, carol_pubkey]);
1891    }
1892
1893    #[test]
1894    fn test_create_nonce_account() {
1895        let from_pubkey = Pubkey::new_unique();
1896        let nonce_pubkey = Pubkey::new_unique();
1897        let authorized = nonce_pubkey;
1898        let ixs = create_nonce_account(&from_pubkey, &nonce_pubkey, &authorized, 42);
1899        assert_eq!(ixs.len(), 2);
1900        let ix = &ixs[0];
1901        assert_eq!(ix.program_id, system_program::id());
1902        let pubkeys: Vec<_> = ix.accounts.iter().map(|am| am.pubkey).collect();
1903        assert!(pubkeys.contains(&from_pubkey));
1904        assert!(pubkeys.contains(&nonce_pubkey));
1905    }
1906
1907    #[test]
1908    fn test_nonce_error_decode() {
1909        use num_traits::FromPrimitive;
1910        fn pretty_err<T>(err: InstructionError) -> String
1911        where
1912            T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
1913        {
1914            if let InstructionError::Custom(code) = err {
1915                let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
1916                format!(
1917                    "{:?}: {}::{:?} - {}",
1918                    err,
1919                    T::type_of(),
1920                    specific_error,
1921                    specific_error,
1922                )
1923            } else {
1924                "".to_string()
1925            }
1926        }
1927        assert_eq!(
1928            "Custom(0): NonceError::NoRecentBlockhashes - recent blockhash list is empty",
1929            pretty_err::<NonceError>(NonceError::NoRecentBlockhashes.into())
1930        );
1931        assert_eq!(
1932            "Custom(1): NonceError::NotExpired - stored nonce is still in recent_blockhashes",
1933            pretty_err::<NonceError>(NonceError::NotExpired.into())
1934        );
1935        assert_eq!(
1936            "Custom(2): NonceError::UnexpectedValue - specified nonce does not match stored nonce",
1937            pretty_err::<NonceError>(NonceError::UnexpectedValue.into())
1938        );
1939        assert_eq!(
1940            "Custom(3): NonceError::BadAccountState - cannot handle request in current account state",
1941            pretty_err::<NonceError>(NonceError::BadAccountState.into())
1942        );
1943    }
1944
1945    #[test]
1946    fn test_nonce_to_instruction_error() {
1947        assert_eq!(
1948            nonce_to_instruction_error(NonceError::NoRecentBlockhashes, false),
1949            NonceError::NoRecentBlockhashes.into(),
1950        );
1951        assert_eq!(
1952            nonce_to_instruction_error(NonceError::NotExpired, false),
1953            NonceError::NotExpired.into(),
1954        );
1955        assert_eq!(
1956            nonce_to_instruction_error(NonceError::UnexpectedValue, false),
1957            NonceError::UnexpectedValue.into(),
1958        );
1959        assert_eq!(
1960            nonce_to_instruction_error(NonceError::BadAccountState, false),
1961            NonceError::BadAccountState.into(),
1962        );
1963        assert_eq!(
1964            nonce_to_instruction_error(NonceError::NoRecentBlockhashes, true),
1965            SystemError::NonceNoRecentBlockhashes.into(),
1966        );
1967        assert_eq!(
1968            nonce_to_instruction_error(NonceError::NotExpired, true),
1969            SystemError::NonceBlockhashNotExpired.into(),
1970        );
1971        assert_eq!(
1972            nonce_to_instruction_error(NonceError::UnexpectedValue, true),
1973            SystemError::NonceUnexpectedBlockhashValue.into(),
1974        );
1975        assert_eq!(
1976            nonce_to_instruction_error(NonceError::BadAccountState, true),
1977            InstructionError::InvalidAccountData,
1978        );
1979    }
1980
1981    #[test]
1982    fn test_instruction_to_nonce_error() {
1983        assert_eq!(
1984            instruction_to_nonce_error(
1985                &InstructionError::Custom(NonceErrorAdapter::NoRecentBlockhashes.to_u32().unwrap(),),
1986                false,
1987            ),
1988            Some(NonceError::NoRecentBlockhashes),
1989        );
1990        assert_eq!(
1991            instruction_to_nonce_error(
1992                &InstructionError::Custom(NonceErrorAdapter::NotExpired.to_u32().unwrap(),),
1993                false,
1994            ),
1995            Some(NonceError::NotExpired),
1996        );
1997        assert_eq!(
1998            instruction_to_nonce_error(
1999                &InstructionError::Custom(NonceErrorAdapter::UnexpectedValue.to_u32().unwrap(),),
2000                false,
2001            ),
2002            Some(NonceError::UnexpectedValue),
2003        );
2004        assert_eq!(
2005            instruction_to_nonce_error(
2006                &InstructionError::Custom(NonceErrorAdapter::BadAccountState.to_u32().unwrap(),),
2007                false,
2008            ),
2009            Some(NonceError::BadAccountState),
2010        );
2011        assert_eq!(
2012            instruction_to_nonce_error(&InstructionError::Custom(u32::MAX), false),
2013            None,
2014        );
2015        assert_eq!(
2016            instruction_to_nonce_error(
2017                &InstructionError::Custom(SystemError::NonceNoRecentBlockhashes.to_u32().unwrap(),),
2018                true,
2019            ),
2020            Some(NonceError::NoRecentBlockhashes),
2021        );
2022        assert_eq!(
2023            instruction_to_nonce_error(
2024                &InstructionError::Custom(SystemError::NonceBlockhashNotExpired.to_u32().unwrap(),),
2025                true,
2026            ),
2027            Some(NonceError::NotExpired),
2028        );
2029        assert_eq!(
2030            instruction_to_nonce_error(
2031                &InstructionError::Custom(
2032                    SystemError::NonceUnexpectedBlockhashValue.to_u32().unwrap(),
2033                ),
2034                true,
2035            ),
2036            Some(NonceError::UnexpectedValue),
2037        );
2038        assert_eq!(
2039            instruction_to_nonce_error(&InstructionError::InvalidAccountData, true),
2040            Some(NonceError::BadAccountState),
2041        );
2042        assert_eq!(
2043            instruction_to_nonce_error(&InstructionError::Custom(u32::MAX), true),
2044            None,
2045        );
2046    }
2047
2048    #[test]
2049    fn test_nonce_error_adapter_compat() {
2050        assert_eq!(
2051            NonceError::NoRecentBlockhashes.to_u32(),
2052            NonceErrorAdapter::NoRecentBlockhashes.to_u32(),
2053        );
2054        assert_eq!(
2055            NonceError::NotExpired.to_u32(),
2056            NonceErrorAdapter::NotExpired.to_u32(),
2057        );
2058        assert_eq!(
2059            NonceError::UnexpectedValue.to_u32(),
2060            NonceErrorAdapter::UnexpectedValue.to_u32(),
2061        );
2062        assert_eq!(
2063            NonceError::BadAccountState.to_u32(),
2064            NonceErrorAdapter::BadAccountState.to_u32(),
2065        );
2066    }
2067}