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