solana_program/
system_instruction.rs

1//! Instructions and constructors for the system program.
2//!
3//! The system program ID is defined in [`system_program`].
4
5#[allow(deprecated)]
6use {
7    crate::{
8        decode_error::DecodeError,
9        instruction::{AccountMeta, Instruction, InstructionError},
10        nonce,
11        pubkey::Pubkey,
12        system_program,
13        sysvar::{recent_blockhashes, rent},
14    },
15    num_derive::{FromPrimitive, ToPrimitive},
16    thiserror::Error,
17};
18
19#[derive(Error, Debug, Serialize, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
20pub enum SystemError {
21    #[error("an account with the same address already exists")]
22    AccountAlreadyInUse,
23    #[error("account does not have enough SAFE to perform the operation")]
24    ResultWithNegativeLamports,
25    #[error("cannot assign account to this program id")]
26    InvalidProgramId,
27    #[error("cannot allocate account data of this length")]
28    InvalidAccountDataLength,
29    #[error("length of requested seed is too long")]
30    MaxSeedLengthExceeded,
31    #[error("provided address does not match addressed derived from seed")]
32    AddressWithSeedMismatch,
33    #[error("advancing stored nonce requires a populated RecentBlockhashes sysvar")]
34    NonceNoRecentBlockhashes,
35    #[error("stored nonce is still in recent_blockhashes")]
36    NonceBlockhashNotExpired,
37    #[error("specified nonce does not match stored nonce")]
38    NonceUnexpectedBlockhashValue,
39}
40
41impl<T> DecodeError<T> for SystemError {
42    fn type_of() -> &'static str {
43        "SystemError"
44    }
45}
46
47#[derive(Error, Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
48pub enum NonceError {
49    #[error("recent blockhash list is empty")]
50    NoRecentBlockhashes,
51    #[error("stored nonce is still in recent_blockhashes")]
52    NotExpired,
53    #[error("specified nonce does not match stored nonce")]
54    UnexpectedValue,
55    #[error("cannot handle request in current account state")]
56    BadAccountState,
57}
58
59impl<E> DecodeError<E> for NonceError {
60    fn type_of() -> &'static str {
61        "NonceError"
62    }
63}
64
65#[derive(Error, Debug, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
66enum NonceErrorAdapter {
67    #[error("recent blockhash list is empty")]
68    NoRecentBlockhashes,
69    #[error("stored nonce is still in recent_blockhashes")]
70    NotExpired,
71    #[error("specified nonce does not match stored nonce")]
72    UnexpectedValue,
73    #[error("cannot handle request in current account state")]
74    BadAccountState,
75}
76
77impl<E> DecodeError<E> for NonceErrorAdapter {
78    fn type_of() -> &'static str {
79        "NonceErrorAdapter"
80    }
81}
82
83impl From<NonceErrorAdapter> for NonceError {
84    fn from(e: NonceErrorAdapter) -> Self {
85        match e {
86            NonceErrorAdapter::NoRecentBlockhashes => NonceError::NoRecentBlockhashes,
87            NonceErrorAdapter::NotExpired => NonceError::NotExpired,
88            NonceErrorAdapter::UnexpectedValue => NonceError::UnexpectedValue,
89            NonceErrorAdapter::BadAccountState => NonceError::BadAccountState,
90        }
91    }
92}
93
94pub fn nonce_to_instruction_error(error: NonceError, use_system_variant: bool) -> InstructionError {
95    if use_system_variant {
96        match error {
97            NonceError::NoRecentBlockhashes => SystemError::NonceNoRecentBlockhashes.into(),
98            NonceError::NotExpired => SystemError::NonceBlockhashNotExpired.into(),
99            NonceError::UnexpectedValue => SystemError::NonceUnexpectedBlockhashValue.into(),
100            NonceError::BadAccountState => InstructionError::InvalidAccountData,
101        }
102    } else {
103        match error {
104            NonceError::NoRecentBlockhashes => NonceErrorAdapter::NoRecentBlockhashes.into(),
105            NonceError::NotExpired => NonceErrorAdapter::NotExpired.into(),
106            NonceError::UnexpectedValue => NonceErrorAdapter::UnexpectedValue.into(),
107            NonceError::BadAccountState => NonceErrorAdapter::BadAccountState.into(),
108        }
109    }
110}
111
112pub fn instruction_to_nonce_error(
113    error: &InstructionError,
114    use_system_variant: bool,
115) -> Option<NonceError> {
116    if use_system_variant {
117        match error {
118            InstructionError::Custom(discriminant) => {
119                match SystemError::decode_custom_error_to_enum(*discriminant) {
120                    Some(SystemError::NonceNoRecentBlockhashes) => {
121                        Some(NonceError::NoRecentBlockhashes)
122                    }
123                    Some(SystemError::NonceBlockhashNotExpired) => Some(NonceError::NotExpired),
124                    Some(SystemError::NonceUnexpectedBlockhashValue) => {
125                        Some(NonceError::UnexpectedValue)
126                    }
127                    _ => None,
128                }
129            }
130            InstructionError::InvalidAccountData => Some(NonceError::BadAccountState),
131            _ => None,
132        }
133    } else if let InstructionError::Custom(discriminant) = error {
134        let maybe: Option<NonceErrorAdapter> =
135            NonceErrorAdapter::decode_custom_error_to_enum(*discriminant);
136        maybe.map(NonceError::from)
137    } else {
138        None
139    }
140}
141
142/// Maximum permitted size of data: 10 MiB
143pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
144
145// SBF program entrypoint assumes that the max account data length
146// will fit inside a u32. If this constant no longer fits in a u32,
147// the entrypoint deserialization code in the SDK must be updated.
148#[cfg(test)]
149static_assertions::const_assert!(MAX_PERMITTED_DATA_LENGTH <= u32::MAX as u64);
150
151#[cfg(test)]
152static_assertions::const_assert_eq!(MAX_PERMITTED_DATA_LENGTH, 10_485_760);
153
154#[frozen_abi(digest = "5e22s2kFu9Do77hdcCyxyhuKHD8ThAB6Q6dNaLTCjL5M")]
155#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, AbiExample, AbiEnumVisitor)]
156pub enum SystemInstruction {
157    /// Create a new account
158    ///
159    /// # Account references
160    ///   0. `[WRITE, SIGNER]` Funding account
161    ///   1. `[WRITE, SIGNER]` New account
162    CreateAccount {
163        /// Number of lamports to transfer to the new account
164        lamports: u64,
165
166        /// Number of bytes of memory to allocate
167        space: u64,
168
169        /// Address of program that will own the new account
170        owner: Pubkey,
171    },
172
173    /// Assign account to a program
174    ///
175    /// # Account references
176    ///   0. `[WRITE, SIGNER]` Assigned account public key
177    Assign {
178        /// Owner program account
179        owner: Pubkey,
180    },
181
182    /// Transfer lamports
183    ///
184    /// # Account references
185    ///   0. `[WRITE, SIGNER]` Funding account
186    ///   1. `[WRITE]` Recipient account
187    Transfer { lamports: u64 },
188
189    /// Create a new account at an address derived from a base pubkey and a seed
190    ///
191    /// # Account references
192    ///   0. `[WRITE, SIGNER]` Funding account
193    ///   1. `[WRITE]` Created account
194    ///   2. `[SIGNER]` (optional) Base account; the account matching the base Pubkey below must be
195    ///                          provided as a signer, but may be the same as the funding account
196    ///                          and provided as account 0
197    CreateAccountWithSeed {
198        /// Base public key
199        base: Pubkey,
200
201        /// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN`
202        seed: String,
203
204        /// Number of lamports to transfer to the new account
205        lamports: u64,
206
207        /// Number of bytes of memory to allocate
208        space: u64,
209
210        /// Owner program account address
211        owner: Pubkey,
212    },
213
214    /// Consumes a stored nonce, replacing it with a successor
215    ///
216    /// # Account references
217    ///   0. `[WRITE]` Nonce account
218    ///   1. `[]` RecentBlockhashes sysvar
219    ///   2. `[SIGNER]` Nonce authority
220    AdvanceNonceAccount,
221
222    /// Withdraw funds from a nonce account
223    ///
224    /// # Account references
225    ///   0. `[WRITE]` Nonce account
226    ///   1. `[WRITE]` Recipient account
227    ///   2. `[]` RecentBlockhashes sysvar
228    ///   3. `[]` Rent sysvar
229    ///   4. `[SIGNER]` Nonce authority
230    ///
231    /// The `u64` parameter is the lamports to withdraw, which must leave the
232    /// account balance above the rent exempt reserve or at zero.
233    WithdrawNonceAccount(u64),
234
235    /// Drive state of Uninitialized nonce account to Initialized, setting the nonce value
236    ///
237    /// # Account references
238    ///   0. `[WRITE]` Nonce account
239    ///   1. `[]` RecentBlockhashes sysvar
240    ///   2. `[]` Rent sysvar
241    ///
242    /// The `Pubkey` parameter specifies the entity authorized to execute nonce
243    /// instruction on the account
244    ///
245    /// No signatures are required to execute this instruction, enabling derived
246    /// nonce account addresses
247    InitializeNonceAccount(Pubkey),
248
249    /// Change the entity authorized to execute nonce instructions on the account
250    ///
251    /// # Account references
252    ///   0. `[WRITE]` Nonce account
253    ///   1. `[SIGNER]` Nonce authority
254    ///
255    /// The `Pubkey` parameter identifies the entity to authorize
256    AuthorizeNonceAccount(Pubkey),
257
258    /// Allocate space in a (possibly new) account without funding
259    ///
260    /// # Account references
261    ///   0. `[WRITE, SIGNER]` New account
262    Allocate {
263        /// Number of bytes of memory to allocate
264        space: u64,
265    },
266
267    /// Allocate space for and assign an account at an address
268    ///    derived from a base public key and a seed
269    ///
270    /// # Account references
271    ///   0. `[WRITE]` Allocated account
272    ///   1. `[SIGNER]` Base account
273    AllocateWithSeed {
274        /// Base public key
275        base: Pubkey,
276
277        /// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN`
278        seed: String,
279
280        /// Number of bytes of memory to allocate
281        space: u64,
282
283        /// Owner program account
284        owner: Pubkey,
285    },
286
287    /// Assign account to a program based on a seed
288    ///
289    /// # Account references
290    ///   0. `[WRITE]` Assigned account
291    ///   1. `[SIGNER]` Base account
292    AssignWithSeed {
293        /// Base public key
294        base: Pubkey,
295
296        /// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN`
297        seed: String,
298
299        /// Owner program account
300        owner: Pubkey,
301    },
302
303    /// Transfer lamports from a derived address
304    ///
305    /// # Account references
306    ///   0. `[WRITE]` Funding account
307    ///   1. `[SIGNER]` Base for funding account
308    ///   2. `[WRITE]` Recipient account
309    TransferWithSeed {
310        /// Amount to transfer
311        lamports: u64,
312
313        /// Seed to use to derive the funding account address
314        from_seed: String,
315
316        /// Owner to use to derive the funding account address
317        from_owner: Pubkey,
318    },
319
320    /// One-time idempotent upgrade of legacy nonce versions in order to bump
321    /// them out of chain blockhash domain.
322    ///
323    /// # Account references
324    ///   0. `[WRITE]` Nonce account
325    UpgradeNonceAccount,
326}
327
328pub fn create_account(
329    from_pubkey: &Pubkey,
330    to_pubkey: &Pubkey,
331    lamports: u64,
332    space: u64,
333    owner: &Pubkey,
334) -> Instruction {
335    let account_metas = vec![
336        AccountMeta::new(*from_pubkey, true),
337        AccountMeta::new(*to_pubkey, true),
338    ];
339    Instruction::new_with_bincode(
340        system_program::id(),
341        &SystemInstruction::CreateAccount {
342            lamports,
343            space,
344            owner: *owner,
345        },
346        account_metas,
347    )
348}
349
350// we accept `to` as a parameter so that callers do their own error handling when
351//   calling create_with_seed()
352pub fn create_account_with_seed(
353    from_pubkey: &Pubkey,
354    to_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner)
355    base: &Pubkey,
356    seed: &str,
357    lamports: u64,
358    space: u64,
359    owner: &Pubkey,
360) -> Instruction {
361    let account_metas = vec![
362        AccountMeta::new(*from_pubkey, true),
363        AccountMeta::new(*to_pubkey, false),
364        AccountMeta::new_readonly(*base, true),
365    ];
366
367    Instruction::new_with_bincode(
368        system_program::id(),
369        &SystemInstruction::CreateAccountWithSeed {
370            base: *base,
371            seed: seed.to_string(),
372            lamports,
373            space,
374            owner: *owner,
375        },
376        account_metas,
377    )
378}
379
380pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
381    let account_metas = vec![AccountMeta::new(*pubkey, true)];
382    Instruction::new_with_bincode(
383        system_program::id(),
384        &SystemInstruction::Assign { owner: *owner },
385        account_metas,
386    )
387}
388
389pub fn assign_with_seed(
390    address: &Pubkey, // must match create_with_seed(base, seed, owner)
391    base: &Pubkey,
392    seed: &str,
393    owner: &Pubkey,
394) -> Instruction {
395    let account_metas = vec![
396        AccountMeta::new(*address, false),
397        AccountMeta::new_readonly(*base, true),
398    ];
399    Instruction::new_with_bincode(
400        system_program::id(),
401        &SystemInstruction::AssignWithSeed {
402            base: *base,
403            seed: seed.to_string(),
404            owner: *owner,
405        },
406        account_metas,
407    )
408}
409
410pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction {
411    let account_metas = vec![
412        AccountMeta::new(*from_pubkey, true),
413        AccountMeta::new(*to_pubkey, false),
414    ];
415    Instruction::new_with_bincode(
416        system_program::id(),
417        &SystemInstruction::Transfer { lamports },
418        account_metas,
419    )
420}
421
422pub fn transfer_with_seed(
423    from_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner)
424    from_base: &Pubkey,
425    from_seed: String,
426    from_owner: &Pubkey,
427    to_pubkey: &Pubkey,
428    lamports: u64,
429) -> Instruction {
430    let account_metas = vec![
431        AccountMeta::new(*from_pubkey, false),
432        AccountMeta::new_readonly(*from_base, true),
433        AccountMeta::new(*to_pubkey, false),
434    ];
435    Instruction::new_with_bincode(
436        system_program::id(),
437        &SystemInstruction::TransferWithSeed {
438            lamports,
439            from_seed,
440            from_owner: *from_owner,
441        },
442        account_metas,
443    )
444}
445
446pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
447    let account_metas = vec![AccountMeta::new(*pubkey, true)];
448    Instruction::new_with_bincode(
449        system_program::id(),
450        &SystemInstruction::Allocate { space },
451        account_metas,
452    )
453}
454
455pub fn allocate_with_seed(
456    address: &Pubkey, // must match create_with_seed(base, seed, owner)
457    base: &Pubkey,
458    seed: &str,
459    space: u64,
460    owner: &Pubkey,
461) -> Instruction {
462    let account_metas = vec![
463        AccountMeta::new(*address, false),
464        AccountMeta::new_readonly(*base, true),
465    ];
466    Instruction::new_with_bincode(
467        system_program::id(),
468        &SystemInstruction::AllocateWithSeed {
469            base: *base,
470            seed: seed.to_string(),
471            space,
472            owner: *owner,
473        },
474        account_metas,
475    )
476}
477
478/// Create and sign new SystemInstruction::Transfer transaction to many destinations
479pub fn transfer_many(from_pubkey: &Pubkey, to_lamports: &[(Pubkey, u64)]) -> Vec<Instruction> {
480    to_lamports
481        .iter()
482        .map(|(to_pubkey, lamports)| transfer(from_pubkey, to_pubkey, *lamports))
483        .collect()
484}
485
486pub fn create_nonce_account_with_seed(
487    from_pubkey: &Pubkey,
488    nonce_pubkey: &Pubkey,
489    base: &Pubkey,
490    seed: &str,
491    authority: &Pubkey,
492    lamports: u64,
493) -> Vec<Instruction> {
494    vec![
495        create_account_with_seed(
496            from_pubkey,
497            nonce_pubkey,
498            base,
499            seed,
500            lamports,
501            nonce::State::size() as u64,
502            &system_program::id(),
503        ),
504        Instruction::new_with_bincode(
505            system_program::id(),
506            &SystemInstruction::InitializeNonceAccount(*authority),
507            vec![
508                AccountMeta::new(*nonce_pubkey, false),
509                #[allow(deprecated)]
510                AccountMeta::new_readonly(recent_blockhashes::id(), false),
511                AccountMeta::new_readonly(rent::id(), false),
512            ],
513        ),
514    ]
515}
516
517/// Create an account containing a durable transaction nonce.
518///
519/// This function produces a vector of [`Instruction`]s which must be submitted
520/// in a [`Transaction`] or [invoked] to take effect.
521///
522/// [`Transaction`]: https://docs.rs/safecoin-sdk/latest/solana_sdk/transaction/struct.Transaction.html
523/// [invoked]: crate::program::invoke
524///
525/// A [durable transaction nonce][dtn] is a special account that enables
526/// execution of transactions that have been signed in the past.
527///
528/// Standard Safecoin transactions include a [recent blockhash][rbh] (sometimes
529/// referred to as a _[nonce]_). During execution the Safecoin runtime verifies
530/// the recent blockhash is approximately less than two minutes old, and that in
531/// those two minutes no other identical transaction with the same blockhash has
532/// been executed. These checks prevent accidental replay of transactions.
533/// Consequently, it is not possible to sign a transaction, wait more than two
534/// minutes, then successfully execute that transaction.
535///
536/// [dtn]: https://docs.solana.com/implemented-proposals/durable-tx-nonces
537/// [rbh]: crate::message::Message::recent_blockhash
538/// [nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce
539///
540/// Durable transaction nonces are an alternative to the standard recent
541/// blockhash nonce. They are stored in accounts on chain, and every time they
542/// are used their value is changed to a new value for their next use. The
543/// runtime verifies that each durable nonce value is only used once, and there
544/// are no restrictions on how "old" the nonce is. Because they are stored on
545/// chain and require additional instructions to use, transacting with durable
546/// transaction nonces is more expensive than with standard transactions.
547///
548/// The value of the durable nonce is itself a blockhash and is accessible via
549/// the [`blockhash`] field of [`nonce::state::Data`], which is deserialized
550/// from the nonce account data.
551///
552/// [`blockhash`]: crate::nonce::state::Data::blockhash
553/// [`nonce::state::Data`]: crate::nonce::state::Data
554///
555/// The basic durable transaction nonce lifecycle is
556///
557/// 1) Create the nonce account with the `create_nonce_account` instruction.
558/// 2) Submit specially-formed transactions that include the
559///    [`advance_nonce_account`] instruction.
560/// 3) Destroy the nonce account by withdrawing its lamports with the
561///    [`withdraw_nonce_account`] instruction.
562///
563/// Nonce accounts have an associated _authority_ account, which is stored in
564/// their account data, and can be changed with the [`authorize_nonce_account`]
565/// instruction. The authority must sign transactions that include the
566/// `advance_nonce_account`, `authorize_nonce_account` and
567/// `withdraw_nonce_account` instructions.
568///
569/// Nonce accounts are owned by the system program.
570///
571/// This constructor creates a [`SystemInstruction::CreateAccount`] instruction
572/// and a [`SystemInstruction::InitializeNonceAccount`] instruction.
573///
574/// # Required signers
575///
576/// The `from_pubkey` and `nonce_pubkey` signers must sign the transaction.
577///
578/// # Examples
579///
580/// Create a nonce account from an off-chain client:
581///
582/// ```
583/// # use solana_program::example_mocks::solana_sdk;
584/// # use solana_program::example_mocks::safecoin_client;
585/// use safecoin_client::rpc_client::RpcClient;
586/// use solana_sdk::{
587/// #   pubkey::Pubkey,
588///     signature::{Keypair, Signer},
589///     system_instruction,
590///     transaction::Transaction,
591///     nonce::State,
592/// };
593/// use anyhow::Result;
594///
595/// fn submit_create_nonce_account_tx(
596///     client: &RpcClient,
597///     payer: &Keypair,
598/// ) -> Result<()> {
599///
600///     let nonce_account = Keypair::new();
601///
602///     let nonce_rent = client.get_minimum_balance_for_rent_exemption(State::size())?;
603///     let instr = system_instruction::create_nonce_account(
604///         &payer.pubkey(),
605///         &nonce_account.pubkey(),
606///         &payer.pubkey(), // Make the fee payer the nonce account authority
607///         nonce_rent,
608///     );
609///
610///     let mut tx = Transaction::new_with_payer(&instr, Some(&payer.pubkey()));
611///
612///     let blockhash = client.get_latest_blockhash()?;
613///     tx.try_sign(&[&nonce_account, payer], blockhash)?;
614///
615///     client.send_and_confirm_transaction(&tx)?;
616///
617///     Ok(())
618/// }
619/// #
620/// # let client = RpcClient::new(String::new());
621/// # let payer = Keypair::new();
622/// # submit_create_nonce_account_tx(&client, &payer)?;
623/// #
624/// # Ok::<(), anyhow::Error>(())
625/// ```
626pub fn create_nonce_account(
627    from_pubkey: &Pubkey,
628    nonce_pubkey: &Pubkey,
629    authority: &Pubkey,
630    lamports: u64,
631) -> Vec<Instruction> {
632    vec![
633        create_account(
634            from_pubkey,
635            nonce_pubkey,
636            lamports,
637            nonce::State::size() as u64,
638            &system_program::id(),
639        ),
640        Instruction::new_with_bincode(
641            system_program::id(),
642            &SystemInstruction::InitializeNonceAccount(*authority),
643            vec![
644                AccountMeta::new(*nonce_pubkey, false),
645                #[allow(deprecated)]
646                AccountMeta::new_readonly(recent_blockhashes::id(), false),
647                AccountMeta::new_readonly(rent::id(), false),
648            ],
649        ),
650    ]
651}
652
653/// Advance the value of a durable transaction nonce.
654///
655/// This function produces an [`Instruction`] which must be submitted in a
656/// [`Transaction`] or [invoked] to take effect.
657///
658/// [`Transaction`]: https://docs.rs/safecoin-sdk/latest/solana_sdk/transaction/struct.Transaction.html
659/// [invoked]: crate::program::invoke
660///
661/// Every transaction that relies on a durable transaction nonce must contain a
662/// [`SystemInstruction::AdvanceNonceAccount`] instruction as the first
663/// instruction in the [`Message`], as created by this function. When included
664/// in the first position, the Safecoin runtime recognizes the transaction as one
665/// that relies on a durable transaction nonce and processes it accordingly. The
666/// [`Message::new_with_nonce`] function can be used to construct a `Message` in
667/// the correct format without calling `advance_nonce_account` directly.
668///
669/// When constructing a transaction that includes an `AdvanceNonceInstruction`
670/// the [`recent_blockhash`] must be treated differently &mdash; instead of
671/// setting it to a recent blockhash, the value of the nonce must be retreived
672/// and deserialized from the nonce account, and that value specified as the
673/// "recent blockhash". A nonce account can be deserialized with the
674/// [`safecoin_client::nonce_utils::data_from_account`][dfa] function.
675///
676/// For further description of durable transaction nonces see
677/// [`create_nonce_account`].
678///
679/// [`Message`]: crate::message::Message
680/// [`Message::new_with_nonce`]: crate::message::Message::new_with_nonce
681/// [`recent_blockhash`]: crate::message::Message::recent_blockhash
682/// [dfa]: https://docs.rs/safecoin-client/latest/safecoin_client/nonce_utils/fn.data_from_account.html
683///
684/// # Required signers
685///
686/// The `authorized_pubkey` signer must sign the transaction.
687///
688/// # Examples
689///
690/// Create and sign a transaction with a durable nonce:
691///
692/// ```
693/// # use solana_program::example_mocks::solana_sdk;
694/// # use solana_program::example_mocks::safecoin_client;
695/// use safecoin_client::{
696///     rpc_client::RpcClient,
697///     nonce_utils,
698/// };
699/// use solana_sdk::{
700///     message::Message,
701///     pubkey::Pubkey,
702///     signature::{Keypair, Signer},
703///     system_instruction,
704///     transaction::Transaction,
705/// };
706/// # use solana_sdk::account::Account;
707/// use std::path::Path;
708/// use anyhow::Result;
709/// # use anyhow::anyhow;
710///
711/// fn create_transfer_tx_with_nonce(
712///     client: &RpcClient,
713///     nonce_account_pubkey: &Pubkey,
714///     payer: &Keypair,
715///     receiver: &Pubkey,
716///     amount: u64,
717///     tx_path: &Path,
718/// ) -> Result<()> {
719///
720///     let instr_transfer = system_instruction::transfer(
721///         &payer.pubkey(),
722///         receiver,
723///         amount,
724///     );
725///
726///     // In this example, `payer` is `nonce_account_pubkey`'s authority
727///     let instr_advance_nonce_account = system_instruction::advance_nonce_account(
728///         nonce_account_pubkey,
729///         &payer.pubkey(),
730///     );
731///
732///     // The `advance_nonce_account` instruction must be the first issued in
733///     // the transaction.
734///     let message = Message::new(
735///         &[
736///             instr_advance_nonce_account,
737///             instr_transfer
738///         ],
739///         Some(&payer.pubkey()),
740///     );
741///
742///     let mut tx = Transaction::new_unsigned(message);
743///
744///     // Sign the tx with nonce_account's `blockhash` instead of the
745///     // network's latest blockhash.
746///     # client.set_get_account_response(*nonce_account_pubkey, Account {
747///     #   lamports: 1,
748///     #   data: vec![0],
749///     #   owner: solana_sdk::system_program::ID,
750///     #   executable: false,
751///     #   rent_epoch: 1,
752///     # });
753///     let nonce_account = client.get_account(nonce_account_pubkey)?;
754///     let nonce_data = nonce_utils::data_from_account(&nonce_account)?;
755///     let blockhash = nonce_data.blockhash();
756///
757///     tx.try_sign(&[payer], blockhash)?;
758///
759///     // Save the signed transaction locally for later submission.
760///     save_tx_to_file(&tx_path, &tx)?;
761///
762///     Ok(())
763/// }
764/// #
765/// # fn save_tx_to_file(path: &Path, tx: &Transaction) -> Result<()> {
766/// #     Ok(())
767/// # }
768/// #
769/// # let client = RpcClient::new(String::new());
770/// # let nonce_account_pubkey = Pubkey::new_unique();
771/// # let payer = Keypair::new();
772/// # let receiver = Pubkey::new_unique();
773/// # create_transfer_tx_with_nonce(&client, &nonce_account_pubkey, &payer, &receiver, 1024, Path::new("new_tx"))?;
774/// #
775/// # Ok::<(), anyhow::Error>(())
776/// ```
777pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
778    let account_metas = vec![
779        AccountMeta::new(*nonce_pubkey, false),
780        #[allow(deprecated)]
781        AccountMeta::new_readonly(recent_blockhashes::id(), false),
782        AccountMeta::new_readonly(*authorized_pubkey, true),
783    ];
784    Instruction::new_with_bincode(
785        system_program::id(),
786        &SystemInstruction::AdvanceNonceAccount,
787        account_metas,
788    )
789}
790
791/// Withdraw lamports from a durable transaction nonce account.
792///
793/// This function produces an [`Instruction`] which must be submitted in a
794/// [`Transaction`] or [invoked] to take effect.
795///
796/// [`Transaction`]: https://docs.rs/safecoin-sdk/latest/solana_sdk/transaction/struct.Transaction.html
797/// [invoked]: crate::program::invoke
798///
799/// Withdrawing the entire balance of a nonce account will cause the runtime to
800/// destroy it upon successful completion of the transaction.
801///
802/// Otherwise, nonce accounts must maintain a balance greater than or equal to
803/// the minimum required for [rent exemption]. If the result of this instruction
804/// would leave the nonce account with a balance less than required for rent
805/// exemption, but also greater than zero, then the transaction will fail.
806///
807/// [rent exemption]: https://docs.solana.com/developing/programming-model/accounts#rent-exemption
808///
809/// This constructor creates a [`SystemInstruction::WithdrawNonceAccount`]
810/// instruction.
811///
812/// # Required signers
813///
814/// The `authorized_pubkey` signer must sign the transaction.
815///
816/// # Examples
817///
818/// ```
819/// # use solana_program::example_mocks::solana_sdk;
820/// # use solana_program::example_mocks::safecoin_client;
821/// use safecoin_client::rpc_client::RpcClient;
822/// use solana_sdk::{
823///     pubkey::Pubkey,
824///     signature::{Keypair, Signer},
825///     system_instruction,
826///     transaction::Transaction,
827/// };
828/// use anyhow::Result;
829///
830/// fn submit_withdraw_nonce_account_tx(
831///     client: &RpcClient,
832///     nonce_account_pubkey: &Pubkey,
833///     authorized_account: &Keypair,
834/// ) -> Result<()> {
835///
836///     let nonce_balance = client.get_balance(nonce_account_pubkey)?;
837///
838///     let instr = system_instruction::withdraw_nonce_account(
839///         &nonce_account_pubkey,
840///         &authorized_account.pubkey(),
841///         &authorized_account.pubkey(),
842///         nonce_balance,
843///     );
844///
845///     let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey()));
846///
847///     let blockhash = client.get_latest_blockhash()?;
848///     tx.try_sign(&[authorized_account], blockhash)?;
849///
850///     client.send_and_confirm_transaction(&tx)?;
851///
852///     Ok(())
853/// }
854/// #
855/// # let client = RpcClient::new(String::new());
856/// # let nonce_account_pubkey = Pubkey::new_unique();
857/// # let payer = Keypair::new();
858/// # submit_withdraw_nonce_account_tx(&client, &nonce_account_pubkey, &payer)?;
859/// #
860/// # Ok::<(), anyhow::Error>(())
861/// ```
862pub fn withdraw_nonce_account(
863    nonce_pubkey: &Pubkey,
864    authorized_pubkey: &Pubkey,
865    to_pubkey: &Pubkey,
866    lamports: u64,
867) -> Instruction {
868    let account_metas = vec![
869        AccountMeta::new(*nonce_pubkey, false),
870        AccountMeta::new(*to_pubkey, false),
871        #[allow(deprecated)]
872        AccountMeta::new_readonly(recent_blockhashes::id(), false),
873        AccountMeta::new_readonly(rent::id(), false),
874        AccountMeta::new_readonly(*authorized_pubkey, true),
875    ];
876    Instruction::new_with_bincode(
877        system_program::id(),
878        &SystemInstruction::WithdrawNonceAccount(lamports),
879        account_metas,
880    )
881}
882
883/// Change the authority of a durable transaction nonce account.
884///
885/// This function produces an [`Instruction`] which must be submitted in a
886/// [`Transaction`] or [invoked] to take effect.
887///
888/// [`Transaction`]: https://docs.rs/safecoin-sdk/latest/solana_sdk/transaction/struct.Transaction.html
889/// [invoked]: crate::program::invoke
890///
891/// This constructor creates a [`SystemInstruction::AuthorizeNonceAccount`]
892/// instruction.
893///
894/// # Required signers
895///
896/// The `authorized_pubkey` signer must sign the transaction.
897///
898/// # Examples
899///
900/// ```
901/// # use solana_program::example_mocks::solana_sdk;
902/// # use solana_program::example_mocks::safecoin_client;
903/// use safecoin_client::rpc_client::RpcClient;
904/// use solana_sdk::{
905///     pubkey::Pubkey,
906///     signature::{Keypair, Signer},
907///     system_instruction,
908///     transaction::Transaction,
909/// };
910/// use anyhow::Result;
911///
912/// fn authorize_nonce_account_tx(
913///     client: &RpcClient,
914///     nonce_account_pubkey: &Pubkey,
915///     authorized_account: &Keypair,
916///     new_authority_pubkey: &Pubkey,
917/// ) -> Result<()> {
918///
919///     let instr = system_instruction::authorize_nonce_account(
920///         &nonce_account_pubkey,
921///         &authorized_account.pubkey(),
922///         &new_authority_pubkey,
923///     );
924///
925///     let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey()));
926///
927///     let blockhash = client.get_latest_blockhash()?;
928///     tx.try_sign(&[authorized_account], blockhash)?;
929///
930///     client.send_and_confirm_transaction(&tx)?;
931///
932///     Ok(())
933/// }
934/// #
935/// # let client = RpcClient::new(String::new());
936/// # let nonce_account_pubkey = Pubkey::new_unique();
937/// # let payer = Keypair::new();
938/// # let new_authority_pubkey = Pubkey::new_unique();
939/// # authorize_nonce_account_tx(&client, &nonce_account_pubkey, &payer, &new_authority_pubkey)?;
940/// #
941/// # Ok::<(), anyhow::Error>(())
942/// ```
943pub fn authorize_nonce_account(
944    nonce_pubkey: &Pubkey,
945    authorized_pubkey: &Pubkey,
946    new_authority: &Pubkey,
947) -> Instruction {
948    let account_metas = vec![
949        AccountMeta::new(*nonce_pubkey, false),
950        AccountMeta::new_readonly(*authorized_pubkey, true),
951    ];
952    Instruction::new_with_bincode(
953        system_program::id(),
954        &SystemInstruction::AuthorizeNonceAccount(*new_authority),
955        account_metas,
956    )
957}
958
959/// One-time idempotent upgrade of legacy nonce versions in order to bump
960/// them out of chain blockhash domain.
961pub fn upgrade_nonce_account(nonce_pubkey: Pubkey) -> Instruction {
962    let account_metas = vec![AccountMeta::new(nonce_pubkey, /*is_signer:*/ false)];
963    Instruction::new_with_bincode(
964        system_program::id(),
965        &SystemInstruction::UpgradeNonceAccount,
966        account_metas,
967    )
968}
969
970#[cfg(test)]
971mod tests {
972    use {
973        super::*,
974        crate::instruction::{Instruction, InstructionError},
975        num_traits::ToPrimitive,
976    };
977
978    fn get_keys(instruction: &Instruction) -> Vec<Pubkey> {
979        instruction.accounts.iter().map(|x| x.pubkey).collect()
980    }
981
982    #[test]
983    fn test_move_many() {
984        let alice_pubkey = Pubkey::new_unique();
985        let bob_pubkey = Pubkey::new_unique();
986        let carol_pubkey = Pubkey::new_unique();
987        let to_lamports = vec![(bob_pubkey, 1), (carol_pubkey, 2)];
988
989        let instructions = transfer_many(&alice_pubkey, &to_lamports);
990        assert_eq!(instructions.len(), 2);
991        assert_eq!(get_keys(&instructions[0]), vec![alice_pubkey, bob_pubkey]);
992        assert_eq!(get_keys(&instructions[1]), vec![alice_pubkey, carol_pubkey]);
993    }
994
995    #[test]
996    fn test_create_nonce_account() {
997        let from_pubkey = Pubkey::new_unique();
998        let nonce_pubkey = Pubkey::new_unique();
999        let authorized = nonce_pubkey;
1000        let ixs = create_nonce_account(&from_pubkey, &nonce_pubkey, &authorized, 42);
1001        assert_eq!(ixs.len(), 2);
1002        let ix = &ixs[0];
1003        assert_eq!(ix.program_id, system_program::id());
1004        let pubkeys: Vec<_> = ix.accounts.iter().map(|am| am.pubkey).collect();
1005        assert!(pubkeys.contains(&from_pubkey));
1006        assert!(pubkeys.contains(&nonce_pubkey));
1007    }
1008
1009    #[test]
1010    fn test_nonce_error_decode() {
1011        use num_traits::FromPrimitive;
1012        fn pretty_err<T>(err: InstructionError) -> String
1013        where
1014            T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
1015        {
1016            if let InstructionError::Custom(code) = err {
1017                let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
1018                format!(
1019                    "{:?}: {}::{:?} - {}",
1020                    err,
1021                    T::type_of(),
1022                    specific_error,
1023                    specific_error,
1024                )
1025            } else {
1026                "".to_string()
1027            }
1028        }
1029        assert_eq!(
1030            "Custom(0): NonceError::NoRecentBlockhashes - recent blockhash list is empty",
1031            pretty_err::<NonceError>(NonceError::NoRecentBlockhashes.into())
1032        );
1033        assert_eq!(
1034            "Custom(1): NonceError::NotExpired - stored nonce is still in recent_blockhashes",
1035            pretty_err::<NonceError>(NonceError::NotExpired.into())
1036        );
1037        assert_eq!(
1038            "Custom(2): NonceError::UnexpectedValue - specified nonce does not match stored nonce",
1039            pretty_err::<NonceError>(NonceError::UnexpectedValue.into())
1040        );
1041        assert_eq!(
1042            "Custom(3): NonceError::BadAccountState - cannot handle request in current account state",
1043            pretty_err::<NonceError>(NonceError::BadAccountState.into())
1044        );
1045    }
1046
1047    #[test]
1048    fn test_nonce_to_instruction_error() {
1049        assert_eq!(
1050            nonce_to_instruction_error(NonceError::NoRecentBlockhashes, false),
1051            NonceError::NoRecentBlockhashes.into(),
1052        );
1053        assert_eq!(
1054            nonce_to_instruction_error(NonceError::NotExpired, false),
1055            NonceError::NotExpired.into(),
1056        );
1057        assert_eq!(
1058            nonce_to_instruction_error(NonceError::UnexpectedValue, false),
1059            NonceError::UnexpectedValue.into(),
1060        );
1061        assert_eq!(
1062            nonce_to_instruction_error(NonceError::BadAccountState, false),
1063            NonceError::BadAccountState.into(),
1064        );
1065        assert_eq!(
1066            nonce_to_instruction_error(NonceError::NoRecentBlockhashes, true),
1067            SystemError::NonceNoRecentBlockhashes.into(),
1068        );
1069        assert_eq!(
1070            nonce_to_instruction_error(NonceError::NotExpired, true),
1071            SystemError::NonceBlockhashNotExpired.into(),
1072        );
1073        assert_eq!(
1074            nonce_to_instruction_error(NonceError::UnexpectedValue, true),
1075            SystemError::NonceUnexpectedBlockhashValue.into(),
1076        );
1077        assert_eq!(
1078            nonce_to_instruction_error(NonceError::BadAccountState, true),
1079            InstructionError::InvalidAccountData,
1080        );
1081    }
1082
1083    #[test]
1084    fn test_instruction_to_nonce_error() {
1085        assert_eq!(
1086            instruction_to_nonce_error(
1087                &InstructionError::Custom(NonceErrorAdapter::NoRecentBlockhashes.to_u32().unwrap(),),
1088                false,
1089            ),
1090            Some(NonceError::NoRecentBlockhashes),
1091        );
1092        assert_eq!(
1093            instruction_to_nonce_error(
1094                &InstructionError::Custom(NonceErrorAdapter::NotExpired.to_u32().unwrap(),),
1095                false,
1096            ),
1097            Some(NonceError::NotExpired),
1098        );
1099        assert_eq!(
1100            instruction_to_nonce_error(
1101                &InstructionError::Custom(NonceErrorAdapter::UnexpectedValue.to_u32().unwrap(),),
1102                false,
1103            ),
1104            Some(NonceError::UnexpectedValue),
1105        );
1106        assert_eq!(
1107            instruction_to_nonce_error(
1108                &InstructionError::Custom(NonceErrorAdapter::BadAccountState.to_u32().unwrap(),),
1109                false,
1110            ),
1111            Some(NonceError::BadAccountState),
1112        );
1113        assert_eq!(
1114            instruction_to_nonce_error(&InstructionError::Custom(u32::MAX), false),
1115            None,
1116        );
1117        assert_eq!(
1118            instruction_to_nonce_error(
1119                &InstructionError::Custom(SystemError::NonceNoRecentBlockhashes.to_u32().unwrap(),),
1120                true,
1121            ),
1122            Some(NonceError::NoRecentBlockhashes),
1123        );
1124        assert_eq!(
1125            instruction_to_nonce_error(
1126                &InstructionError::Custom(SystemError::NonceBlockhashNotExpired.to_u32().unwrap(),),
1127                true,
1128            ),
1129            Some(NonceError::NotExpired),
1130        );
1131        assert_eq!(
1132            instruction_to_nonce_error(
1133                &InstructionError::Custom(
1134                    SystemError::NonceUnexpectedBlockhashValue.to_u32().unwrap(),
1135                ),
1136                true,
1137            ),
1138            Some(NonceError::UnexpectedValue),
1139        );
1140        assert_eq!(
1141            instruction_to_nonce_error(&InstructionError::InvalidAccountData, true),
1142            Some(NonceError::BadAccountState),
1143        );
1144        assert_eq!(
1145            instruction_to_nonce_error(&InstructionError::Custom(u32::MAX), true),
1146            None,
1147        );
1148    }
1149
1150    #[test]
1151    fn test_nonce_error_adapter_compat() {
1152        assert_eq!(
1153            NonceError::NoRecentBlockhashes.to_u32(),
1154            NonceErrorAdapter::NoRecentBlockhashes.to_u32(),
1155        );
1156        assert_eq!(
1157            NonceError::NotExpired.to_u32(),
1158            NonceErrorAdapter::NotExpired.to_u32(),
1159        );
1160        assert_eq!(
1161            NonceError::UnexpectedValue.to_u32(),
1162            NonceErrorAdapter::UnexpectedValue.to_u32(),
1163        );
1164        assert_eq!(
1165            NonceError::BadAccountState.to_u32(),
1166            NonceErrorAdapter::BadAccountState.to_u32(),
1167        );
1168    }
1169}