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