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