diff --git a/sdk/program/src/system_instruction.rs b/sdk/program/src/system_instruction.rs index bd0fcb0b37e0a6..ec316c2d387172 100644 --- a/sdk/program/src/system_instruction.rs +++ b/sdk/program/src/system_instruction.rs @@ -1,1806 +1,11 @@ -//! Instructions and constructors for the system program. -//! -//! The system program is responsible for the creation of accounts and [nonce -//! accounts][na]. It is responsible for transferring lamports from accounts -//! owned by the system program, including typical user wallet accounts. -//! -//! [na]: https://docs.solanalabs.com/implemented-proposals/durable-tx-nonces -//! -//! Account creation typically involves three steps: [`allocate`] space, -//! [`transfer`] lamports for rent, [`assign`] to its owning program. The -//! [`create_account`] function does all three at once. All new accounts must -//! contain enough lamports to be [rent exempt], or else the creation -//! instruction will fail. -//! -//! [rent exempt]: https://solana.com/docs/core/accounts#rent-exemption -//! -//! The accounts created by the system program can either be user-controlled, -//! where the secret keys are held outside the blockchain, -//! or they can be [program derived addresses][pda], -//! where write access to accounts is granted by an owning program. -//! -//! [pda]: crate::pubkey::Pubkey::find_program_address -//! -//! The system program ID is defined in [`system_program`]. -//! -//! Most of the functions in this module construct an [`Instruction`], that must -//! be submitted to the runtime for execution, either via RPC, typically with -//! [`RpcClient`], or through [cross-program invocation][cpi]. -//! -//! When invoking through CPI, the [`invoke`] or [`invoke_signed`] instruction -//! requires all account references to be provided explicitly as [`AccountInfo`] -//! values. The account references required are specified in the documentation -//! for the [`SystemInstruction`] variants for each system program instruction, -//! and these variants are linked from the documentation for their constructors. -//! -//! [`RpcClient`]: https://docs.rs/solana-client/latest/solana_client/rpc_client/struct.RpcClient.html -//! [cpi]: crate::program -//! [`invoke`]: crate::program::invoke -//! [`invoke_signed`]: crate::program::invoke_signed -//! [`AccountInfo`]: crate::account_info::AccountInfo - -#[allow(deprecated)] -use { - crate::{ - instruction::{AccountMeta, Instruction}, - nonce, - pubkey::Pubkey, - system_program, - sysvar::{recent_blockhashes, rent}, - }, - num_derive::{FromPrimitive, ToPrimitive}, - solana_decode_error::DecodeError, - thiserror::Error, +#[deprecated(since = "2.2.0", note = "Use `solana_system_interface` crate instead")] +pub use solana_system_interface::{ + error::SystemError, + instruction::{ + advance_nonce_account, allocate, allocate_with_seed, assign, assign_with_seed, + authorize_nonce_account, create_account, create_account_with_seed, create_nonce_account, + create_nonce_account_with_seed, transfer, transfer_many, transfer_with_seed, + upgrade_nonce_account, withdraw_nonce_account, SystemInstruction, + }, + MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION, MAX_PERMITTED_DATA_LENGTH, }; - -#[derive(Error, Debug, Serialize, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)] -pub enum SystemError { - #[error("an account with the same address already exists")] - AccountAlreadyInUse, - #[error("account does not have enough SOL to perform the operation")] - ResultWithNegativeLamports, - #[error("cannot assign account to this program id")] - InvalidProgramId, - #[error("cannot allocate account data of this length")] - InvalidAccountDataLength, - #[error("length of requested seed is too long")] - MaxSeedLengthExceeded, - #[error("provided address does not match addressed derived from seed")] - AddressWithSeedMismatch, - #[error("advancing stored nonce requires a populated RecentBlockhashes sysvar")] - NonceNoRecentBlockhashes, - #[error("stored nonce is still in recent_blockhashes")] - NonceBlockhashNotExpired, - #[error("specified nonce does not match stored nonce")] - NonceUnexpectedBlockhashValue, -} - -impl DecodeError for SystemError { - fn type_of() -> &'static str { - "SystemError" - } -} - -/// Maximum permitted size of account data (10 MiB). -pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024; - -/// Maximum permitted size of new allocations per transaction, in bytes. -/// -/// The value was chosen such that at least one max sized account could be created, -/// plus some additional resize allocations. -pub const MAX_PERMITTED_ACCOUNTS_DATA_ALLOCATIONS_PER_TRANSACTION: i64 = - MAX_PERMITTED_DATA_LENGTH as i64 * 2; - -// SBF program entrypoint assumes that the max account data length -// will fit inside a u32. If this constant no longer fits in a u32, -// the entrypoint deserialization code in the SDK must be updated. -#[cfg(test)] -static_assertions::const_assert!(MAX_PERMITTED_DATA_LENGTH <= u32::MAX as u64); - -#[cfg(test)] -static_assertions::const_assert_eq!(MAX_PERMITTED_DATA_LENGTH, 10_485_760); - -/// An instruction to the system program. -#[cfg_attr( - feature = "frozen-abi", - frozen_abi(digest = "2LnVTnJg7LxB1FawNZLoQEY8yiYx3MT3paTdx4s5kAXU"), - derive(AbiExample, AbiEnumVisitor) -)] -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub enum SystemInstruction { - /// Create a new account - /// - /// # Account references - /// 0. `[WRITE, SIGNER]` Funding account - /// 1. `[WRITE, SIGNER]` New account - CreateAccount { - /// Number of lamports to transfer to the new account - lamports: u64, - - /// Number of bytes of memory to allocate - space: u64, - - /// Address of program that will own the new account - owner: Pubkey, - }, - - /// Assign account to a program - /// - /// # Account references - /// 0. `[WRITE, SIGNER]` Assigned account public key - Assign { - /// Owner program account - owner: Pubkey, - }, - - /// Transfer lamports - /// - /// # Account references - /// 0. `[WRITE, SIGNER]` Funding account - /// 1. `[WRITE]` Recipient account - Transfer { lamports: u64 }, - - /// Create a new account at an address derived from a base pubkey and a seed - /// - /// # Account references - /// 0. `[WRITE, SIGNER]` Funding account - /// 1. `[WRITE]` Created account - /// 2. `[SIGNER]` (optional) Base account; the account matching the base Pubkey below must be - /// provided as a signer, but may be the same as the funding account - /// and provided as account 0 - CreateAccountWithSeed { - /// Base public key - base: Pubkey, - - /// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN` - seed: String, - - /// Number of lamports to transfer to the new account - lamports: u64, - - /// Number of bytes of memory to allocate - space: u64, - - /// Owner program account address - owner: Pubkey, - }, - - /// Consumes a stored nonce, replacing it with a successor - /// - /// # Account references - /// 0. `[WRITE]` Nonce account - /// 1. `[]` RecentBlockhashes sysvar - /// 2. `[SIGNER]` Nonce authority - AdvanceNonceAccount, - - /// Withdraw funds from a nonce account - /// - /// # Account references - /// 0. `[WRITE]` Nonce account - /// 1. `[WRITE]` Recipient account - /// 2. `[]` RecentBlockhashes sysvar - /// 3. `[]` Rent sysvar - /// 4. `[SIGNER]` Nonce authority - /// - /// The `u64` parameter is the lamports to withdraw, which must leave the - /// account balance above the rent exempt reserve or at zero. - WithdrawNonceAccount(u64), - - /// Drive state of Uninitialized nonce account to Initialized, setting the nonce value - /// - /// # Account references - /// 0. `[WRITE]` Nonce account - /// 1. `[]` RecentBlockhashes sysvar - /// 2. `[]` Rent sysvar - /// - /// The `Pubkey` parameter specifies the entity authorized to execute nonce - /// instruction on the account - /// - /// No signatures are required to execute this instruction, enabling derived - /// nonce account addresses - InitializeNonceAccount(Pubkey), - - /// Change the entity authorized to execute nonce instructions on the account - /// - /// # Account references - /// 0. `[WRITE]` Nonce account - /// 1. `[SIGNER]` Nonce authority - /// - /// The `Pubkey` parameter identifies the entity to authorize - AuthorizeNonceAccount(Pubkey), - - /// Allocate space in a (possibly new) account without funding - /// - /// # Account references - /// 0. `[WRITE, SIGNER]` New account - Allocate { - /// Number of bytes of memory to allocate - space: u64, - }, - - /// Allocate space for and assign an account at an address - /// derived from a base public key and a seed - /// - /// # Account references - /// 0. `[WRITE]` Allocated account - /// 1. `[SIGNER]` Base account - AllocateWithSeed { - /// Base public key - base: Pubkey, - - /// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN` - seed: String, - - /// Number of bytes of memory to allocate - space: u64, - - /// Owner program account - owner: Pubkey, - }, - - /// Assign account to a program based on a seed - /// - /// # Account references - /// 0. `[WRITE]` Assigned account - /// 1. `[SIGNER]` Base account - AssignWithSeed { - /// Base public key - base: Pubkey, - - /// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN` - seed: String, - - /// Owner program account - owner: Pubkey, - }, - - /// Transfer lamports from a derived address - /// - /// # Account references - /// 0. `[WRITE]` Funding account - /// 1. `[SIGNER]` Base for funding account - /// 2. `[WRITE]` Recipient account - TransferWithSeed { - /// Amount to transfer - lamports: u64, - - /// Seed to use to derive the funding account address - from_seed: String, - - /// Owner to use to derive the funding account address - from_owner: Pubkey, - }, - - /// One-time idempotent upgrade of legacy nonce versions in order to bump - /// them out of chain blockhash domain. - /// - /// # Account references - /// 0. `[WRITE]` Nonce account - UpgradeNonceAccount, -} - -/// Create an account. -/// -/// This function produces an [`Instruction`] which must be submitted in a -/// [`Transaction`] or [invoked] to take effect, containing a serialized -/// [`SystemInstruction::CreateAccount`]. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// Account creation typically involves three steps: [`allocate`] space, -/// [`transfer`] lamports for rent, [`assign`] to its owning program. The -/// [`create_account`] function does all three at once. -/// -/// # Required signers -/// -/// The `from_pubkey` and `to_pubkey` signers must sign the transaction. -/// -/// # Examples -/// -/// These examples use a single invocation of -/// [`SystemInstruction::CreateAccount`] to create a new account, allocate some -/// space, transfer it the minimum lamports for rent exemption, and assign it to -/// the system program, -/// -/// ## Example: client-side RPC -/// -/// This example submits the instruction from an RPC client. -/// The `payer` and `new_account` are signers. -/// -/// ``` -/// # use solana_program::example_mocks::{solana_sdk, solana_rpc_client}; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// system_program, -/// transaction::Transaction, -/// }; -/// use anyhow::Result; -/// -/// fn create_account( -/// client: &RpcClient, -/// payer: &Keypair, -/// new_account: &Keypair, -/// space: u64, -/// ) -> Result<()> { -/// let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?; -/// let instr = system_instruction::create_account( -/// &payer.pubkey(), -/// &new_account.pubkey(), -/// rent, -/// space, -/// &system_program::ID, -/// ); -/// -/// let blockhash = client.get_latest_blockhash()?; -/// let tx = Transaction::new_signed_with_payer( -/// &[instr], -/// Some(&payer.pubkey()), -/// &[payer, new_account], -/// blockhash, -/// ); -/// -/// let _sig = client.send_and_confirm_transaction(&tx)?; -/// -/// Ok(()) -/// } -/// # let payer = Keypair::new(); -/// # let new_account = Keypair::new(); -/// # let client = RpcClient::new(String::new()); -/// # create_account(&client, &payer, &new_account, 0); -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -/// -/// ## Example: on-chain program -/// -/// This example submits the instruction from an on-chain Solana program. The -/// created account is a [program derived address][pda]. The `payer` and -/// `new_account_pda` are signers, with `new_account_pda` being signed for -/// virtually by the program itself via [`invoke_signed`], `payer` being signed -/// for by the client that submitted the transaction. -/// -/// [pda]: Pubkey::find_program_address -/// [`invoke_signed`]: crate::program::invoke_signed -/// -/// ``` -/// # use borsh::{BorshDeserialize, BorshSerialize}; -/// use solana_program::{ -/// account_info::{next_account_info, AccountInfo}, -/// entrypoint, -/// entrypoint::ProgramResult, -/// msg, -/// program::invoke_signed, -/// pubkey::Pubkey, -/// system_instruction, -/// system_program, -/// sysvar::rent::Rent, -/// sysvar::Sysvar, -/// }; -/// -/// #[derive(BorshSerialize, BorshDeserialize, Debug)] -/// # #[borsh(crate = "borsh")] -/// pub struct CreateAccountInstruction { -/// /// The PDA seed used to distinguish the new account from other PDAs -/// pub new_account_seed: [u8; 16], -/// /// The PDA bump seed -/// pub new_account_bump_seed: u8, -/// /// The amount of space to allocate for `new_account_pda` -/// pub space: u64, -/// } -/// -/// entrypoint!(process_instruction); -/// -/// fn process_instruction( -/// program_id: &Pubkey, -/// accounts: &[AccountInfo], -/// instruction_data: &[u8], -/// ) -> ProgramResult { -/// let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?; -/// -/// let account_info_iter = &mut accounts.iter(); -/// -/// let payer = next_account_info(account_info_iter)?; -/// let new_account_pda = next_account_info(account_info_iter)?; -/// let system_account = next_account_info(account_info_iter)?; -/// -/// assert!(payer.is_signer); -/// assert!(payer.is_writable); -/// // Note that `new_account_pda` is not a signer yet. -/// // This program will sign for it via `invoke_signed`. -/// assert!(!new_account_pda.is_signer); -/// assert!(new_account_pda.is_writable); -/// assert!(system_program::check_id(system_account.key)); -/// -/// let new_account_seed = &instr.new_account_seed; -/// let new_account_bump_seed = instr.new_account_bump_seed; -/// -/// let rent = Rent::get()? -/// .minimum_balance(instr.space.try_into().expect("overflow")); -/// -/// invoke_signed( -/// &system_instruction::create_account( -/// payer.key, -/// new_account_pda.key, -/// rent, -/// instr.space, -/// &system_program::ID -/// ), -/// &[payer.clone(), new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// Ok(()) -/// } -/// -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn create_account( - from_pubkey: &Pubkey, - to_pubkey: &Pubkey, - lamports: u64, - space: u64, - owner: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*from_pubkey, true), - AccountMeta::new(*to_pubkey, true), - ]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::CreateAccount { - lamports, - space, - owner: *owner, - }, - account_metas, - ) -} - -// we accept `to` as a parameter so that callers do their own error handling when -// calling create_with_seed() -pub fn create_account_with_seed( - from_pubkey: &Pubkey, - to_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner) - base: &Pubkey, - seed: &str, - lamports: u64, - space: u64, - owner: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*from_pubkey, true), - AccountMeta::new(*to_pubkey, false), - AccountMeta::new_readonly(*base, true), - ]; - - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::CreateAccountWithSeed { - base: *base, - seed: seed.to_string(), - lamports, - space, - owner: *owner, - }, - account_metas, - ) -} - -/// Assign ownership of an account from the system program. -/// -/// This function produces an [`Instruction`] which must be submitted in a -/// [`Transaction`] or [invoked] to take effect, containing a serialized -/// [`SystemInstruction::Assign`]. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// # Required signers -/// -/// The `pubkey` signer must sign the transaction. -/// -/// # Examples -/// -/// These examples allocate space for an account, transfer it the minimum -/// balance for rent exemption, and assign the account to a program. -/// -/// ## Example: client-side RPC -/// -/// This example submits the instructions from an RPC client. -/// It assigns the account to a provided program account. -/// The `payer` and `new_account` are signers. -/// -/// ``` -/// # use solana_program::example_mocks::{solana_sdk, solana_rpc_client}; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// transaction::Transaction, -/// }; -/// use anyhow::Result; -/// -/// fn create_account( -/// client: &RpcClient, -/// payer: &Keypair, -/// new_account: &Keypair, -/// owning_program: &Pubkey, -/// space: u64, -/// ) -> Result<()> { -/// let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?; -/// -/// let transfer_instr = system_instruction::transfer( -/// &payer.pubkey(), -/// &new_account.pubkey(), -/// rent, -/// ); -/// -/// let allocate_instr = system_instruction::allocate( -/// &new_account.pubkey(), -/// space, -/// ); -/// -/// let assign_instr = system_instruction::assign( -/// &new_account.pubkey(), -/// owning_program, -/// ); -/// -/// let blockhash = client.get_latest_blockhash()?; -/// let tx = Transaction::new_signed_with_payer( -/// &[transfer_instr, allocate_instr, assign_instr], -/// Some(&payer.pubkey()), -/// &[payer, new_account], -/// blockhash, -/// ); -/// -/// let _sig = client.send_and_confirm_transaction(&tx)?; -/// -/// Ok(()) -/// } -/// # let client = RpcClient::new(String::new()); -/// # let payer = Keypair::new(); -/// # let new_account = Keypair::new(); -/// # let owning_program = Pubkey::new_unique(); -/// # create_account(&client, &payer, &new_account, &owning_program, 1); -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -/// -/// ## Example: on-chain program -/// -/// This example submits the instructions from an on-chain Solana program. The -/// created account is a [program derived address][pda], funded by `payer`, and -/// assigned to the running program. The `payer` and `new_account_pda` are -/// signers, with `new_account_pda` being signed for virtually by the program -/// itself via [`invoke_signed`], `payer` being signed for by the client that -/// submitted the transaction. -/// -/// [pda]: Pubkey::find_program_address -/// [`invoke_signed`]: crate::program::invoke_signed -/// -/// ``` -/// # use borsh::{BorshDeserialize, BorshSerialize}; -/// use solana_program::{ -/// account_info::{next_account_info, AccountInfo}, -/// entrypoint, -/// entrypoint::ProgramResult, -/// msg, -/// program::invoke_signed, -/// pubkey::Pubkey, -/// system_instruction, -/// system_program, -/// sysvar::rent::Rent, -/// sysvar::Sysvar, -/// }; -/// -/// #[derive(BorshSerialize, BorshDeserialize, Debug)] -/// # #[borsh(crate = "borsh")] -/// pub struct CreateAccountInstruction { -/// /// The PDA seed used to distinguish the new account from other PDAs -/// pub new_account_seed: [u8; 16], -/// /// The PDA bump seed -/// pub new_account_bump_seed: u8, -/// /// The amount of space to allocate for `new_account_pda` -/// pub space: u64, -/// } -/// -/// entrypoint!(process_instruction); -/// -/// fn process_instruction( -/// program_id: &Pubkey, -/// accounts: &[AccountInfo], -/// instruction_data: &[u8], -/// ) -> ProgramResult { -/// let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?; -/// -/// let account_info_iter = &mut accounts.iter(); -/// -/// let payer = next_account_info(account_info_iter)?; -/// let new_account_pda = next_account_info(account_info_iter)?; -/// let system_account = next_account_info(account_info_iter)?; -/// -/// assert!(payer.is_signer); -/// assert!(payer.is_writable); -/// // Note that `new_account_pda` is not a signer yet. -/// // This program will sign for it via `invoke_signed`. -/// assert!(!new_account_pda.is_signer); -/// assert!(new_account_pda.is_writable); -/// assert!(system_program::check_id(system_account.key)); -/// -/// let new_account_seed = &instr.new_account_seed; -/// let new_account_bump_seed = instr.new_account_bump_seed; -/// -/// let rent = Rent::get()? -/// .minimum_balance(instr.space.try_into().expect("overflow")); -/// -/// invoke_signed( -/// &system_instruction::transfer( -/// payer.key, -/// new_account_pda.key, -/// rent, -/// ), -/// &[payer.clone(), new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// invoke_signed( -/// &system_instruction::allocate( -/// new_account_pda.key, -/// instr.space, -/// ), -/// &[new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// invoke_signed( -/// &system_instruction::assign( -/// new_account_pda.key, -/// &program_id, -/// ), -/// &[new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// Ok(()) -/// } -/// -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction { - let account_metas = vec![AccountMeta::new(*pubkey, true)]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::Assign { owner: *owner }, - account_metas, - ) -} - -pub fn assign_with_seed( - address: &Pubkey, // must match create_with_seed(base, seed, owner) - base: &Pubkey, - seed: &str, - owner: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*address, false), - AccountMeta::new_readonly(*base, true), - ]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::AssignWithSeed { - base: *base, - seed: seed.to_string(), - owner: *owner, - }, - account_metas, - ) -} - -/// Transfer lamports from an account owned by the system program. -/// -/// This function produces an [`Instruction`] which must be submitted in a -/// [`Transaction`] or [invoked] to take effect, containing a serialized -/// [`SystemInstruction::Transfer`]. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// # Required signers -/// -/// The `from_pubkey` signer must sign the transaction. -/// -/// # Examples -/// -/// These examples allocate space for an account, transfer it the minimum -/// balance for rent exemption, and assign the account to a program. -/// -/// # Example: client-side RPC -/// -/// This example submits the instructions from an RPC client. -/// It assigns the account to a provided program account. -/// The `payer` and `new_account` are signers. -/// -/// ``` -/// # use solana_program::example_mocks::{solana_sdk, solana_rpc_client}; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// transaction::Transaction, -/// }; -/// use anyhow::Result; -/// -/// fn create_account( -/// client: &RpcClient, -/// payer: &Keypair, -/// new_account: &Keypair, -/// owning_program: &Pubkey, -/// space: u64, -/// ) -> Result<()> { -/// let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?; -/// -/// let transfer_instr = system_instruction::transfer( -/// &payer.pubkey(), -/// &new_account.pubkey(), -/// rent, -/// ); -/// -/// let allocate_instr = system_instruction::allocate( -/// &new_account.pubkey(), -/// space, -/// ); -/// -/// let assign_instr = system_instruction::assign( -/// &new_account.pubkey(), -/// owning_program, -/// ); -/// -/// let blockhash = client.get_latest_blockhash()?; -/// let tx = Transaction::new_signed_with_payer( -/// &[transfer_instr, allocate_instr, assign_instr], -/// Some(&payer.pubkey()), -/// &[payer, new_account], -/// blockhash, -/// ); -/// -/// let _sig = client.send_and_confirm_transaction(&tx)?; -/// -/// Ok(()) -/// } -/// # let client = RpcClient::new(String::new()); -/// # let payer = Keypair::new(); -/// # let new_account = Keypair::new(); -/// # let owning_program = Pubkey::new_unique(); -/// # create_account(&client, &payer, &new_account, &owning_program, 1); -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -/// -/// ## Example: on-chain program -/// -/// This example submits the instructions from an on-chain Solana program. The -/// created account is a [program derived address][pda], funded by `payer`, and -/// assigned to the running program. The `payer` and `new_account_pda` are -/// signers, with `new_account_pda` being signed for virtually by the program -/// itself via [`invoke_signed`], `payer` being signed for by the client that -/// submitted the transaction. -/// -/// [pda]: Pubkey::find_program_address -/// [`invoke_signed`]: crate::program::invoke_signed -/// -/// ``` -/// # use borsh::{BorshDeserialize, BorshSerialize}; -/// use solana_program::{ -/// account_info::{next_account_info, AccountInfo}, -/// entrypoint, -/// entrypoint::ProgramResult, -/// msg, -/// program::invoke_signed, -/// pubkey::Pubkey, -/// system_instruction, -/// system_program, -/// sysvar::rent::Rent, -/// sysvar::Sysvar, -/// }; -/// -/// #[derive(BorshSerialize, BorshDeserialize, Debug)] -/// # #[borsh(crate = "borsh")] -/// pub struct CreateAccountInstruction { -/// /// The PDA seed used to distinguish the new account from other PDAs -/// pub new_account_seed: [u8; 16], -/// /// The PDA bump seed -/// pub new_account_bump_seed: u8, -/// /// The amount of space to allocate for `new_account_pda` -/// pub space: u64, -/// } -/// -/// entrypoint!(process_instruction); -/// -/// fn process_instruction( -/// program_id: &Pubkey, -/// accounts: &[AccountInfo], -/// instruction_data: &[u8], -/// ) -> ProgramResult { -/// let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?; -/// -/// let account_info_iter = &mut accounts.iter(); -/// -/// let payer = next_account_info(account_info_iter)?; -/// let new_account_pda = next_account_info(account_info_iter)?; -/// let system_account = next_account_info(account_info_iter)?; -/// -/// assert!(payer.is_signer); -/// assert!(payer.is_writable); -/// // Note that `new_account_pda` is not a signer yet. -/// // This program will sign for it via `invoke_signed`. -/// assert!(!new_account_pda.is_signer); -/// assert!(new_account_pda.is_writable); -/// assert!(system_program::check_id(system_account.key)); -/// -/// let new_account_seed = &instr.new_account_seed; -/// let new_account_bump_seed = instr.new_account_bump_seed; -/// -/// let rent = Rent::get()? -/// .minimum_balance(instr.space.try_into().expect("overflow")); -/// -/// invoke_signed( -/// &system_instruction::transfer( -/// payer.key, -/// new_account_pda.key, -/// rent, -/// ), -/// &[payer.clone(), new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// invoke_signed( -/// &system_instruction::allocate( -/// new_account_pda.key, -/// instr.space, -/// ), -/// &[new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// invoke_signed( -/// &system_instruction::assign( -/// new_account_pda.key, -/// &program_id, -/// ), -/// &[new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// Ok(()) -/// } -/// -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn transfer(from_pubkey: &Pubkey, to_pubkey: &Pubkey, lamports: u64) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*from_pubkey, true), - AccountMeta::new(*to_pubkey, false), - ]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::Transfer { lamports }, - account_metas, - ) -} - -pub fn transfer_with_seed( - from_pubkey: &Pubkey, // must match create_with_seed(base, seed, owner) - from_base: &Pubkey, - from_seed: String, - from_owner: &Pubkey, - to_pubkey: &Pubkey, - lamports: u64, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*from_pubkey, false), - AccountMeta::new_readonly(*from_base, true), - AccountMeta::new(*to_pubkey, false), - ]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::TransferWithSeed { - lamports, - from_seed, - from_owner: *from_owner, - }, - account_metas, - ) -} - -/// Allocate space for an account. -/// -/// This function produces an [`Instruction`] which must be submitted in a -/// [`Transaction`] or [invoked] to take effect, containing a serialized -/// [`SystemInstruction::Allocate`]. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// The transaction will fail if the account already has size greater than 0, -/// or if the requested size is greater than [`MAX_PERMITTED_DATA_LENGTH`]. -/// -/// # Required signers -/// -/// The `pubkey` signer must sign the transaction. -/// -/// # Examples -/// -/// These examples allocate space for an account, transfer it the minimum -/// balance for rent exemption, and assign the account to a program. -/// -/// # Example: client-side RPC -/// -/// This example submits the instructions from an RPC client. -/// It assigns the account to a provided program account. -/// The `payer` and `new_account` are signers. -/// -/// ``` -/// # use solana_program::example_mocks::{solana_sdk, solana_rpc_client}; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// transaction::Transaction, -/// }; -/// use anyhow::Result; -/// -/// fn create_account( -/// client: &RpcClient, -/// payer: &Keypair, -/// new_account: &Keypair, -/// owning_program: &Pubkey, -/// space: u64, -/// ) -> Result<()> { -/// let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?; -/// -/// let transfer_instr = system_instruction::transfer( -/// &payer.pubkey(), -/// &new_account.pubkey(), -/// rent, -/// ); -/// -/// let allocate_instr = system_instruction::allocate( -/// &new_account.pubkey(), -/// space, -/// ); -/// -/// let assign_instr = system_instruction::assign( -/// &new_account.pubkey(), -/// owning_program, -/// ); -/// -/// let blockhash = client.get_latest_blockhash()?; -/// let tx = Transaction::new_signed_with_payer( -/// &[transfer_instr, allocate_instr, assign_instr], -/// Some(&payer.pubkey()), -/// &[payer, new_account], -/// blockhash, -/// ); -/// -/// let _sig = client.send_and_confirm_transaction(&tx)?; -/// -/// Ok(()) -/// } -/// # let client = RpcClient::new(String::new()); -/// # let payer = Keypair::new(); -/// # let new_account = Keypair::new(); -/// # let owning_program = Pubkey::new_unique(); -/// # create_account(&client, &payer, &new_account, &owning_program, 1); -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -/// -/// ## Example: on-chain program -/// -/// This example submits the instructions from an on-chain Solana program. The -/// created account is a [program derived address][pda], funded by `payer`, and -/// assigned to the running program. The `payer` and `new_account_pda` are -/// signers, with `new_account_pda` being signed for virtually by the program -/// itself via [`invoke_signed`], `payer` being signed for by the client that -/// submitted the transaction. -/// -/// [pda]: Pubkey::find_program_address -/// [`invoke_signed`]: crate::program::invoke_signed -/// -/// ``` -/// # use borsh::{BorshDeserialize, BorshSerialize}; -/// use solana_program::{ -/// account_info::{next_account_info, AccountInfo}, -/// entrypoint, -/// entrypoint::ProgramResult, -/// msg, -/// program::invoke_signed, -/// pubkey::Pubkey, -/// system_instruction, -/// system_program, -/// sysvar::rent::Rent, -/// sysvar::Sysvar, -/// }; -/// -/// #[derive(BorshSerialize, BorshDeserialize, Debug)] -/// # #[borsh(crate = "borsh")] -/// pub struct CreateAccountInstruction { -/// /// The PDA seed used to distinguish the new account from other PDAs -/// pub new_account_seed: [u8; 16], -/// /// The PDA bump seed -/// pub new_account_bump_seed: u8, -/// /// The amount of space to allocate for `new_account_pda` -/// pub space: u64, -/// } -/// -/// entrypoint!(process_instruction); -/// -/// fn process_instruction( -/// program_id: &Pubkey, -/// accounts: &[AccountInfo], -/// instruction_data: &[u8], -/// ) -> ProgramResult { -/// let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?; -/// -/// let account_info_iter = &mut accounts.iter(); -/// -/// let payer = next_account_info(account_info_iter)?; -/// let new_account_pda = next_account_info(account_info_iter)?; -/// let system_account = next_account_info(account_info_iter)?; -/// -/// assert!(payer.is_signer); -/// assert!(payer.is_writable); -/// // Note that `new_account_pda` is not a signer yet. -/// // This program will sign for it via `invoke_signed`. -/// assert!(!new_account_pda.is_signer); -/// assert!(new_account_pda.is_writable); -/// assert!(system_program::check_id(system_account.key)); -/// -/// let new_account_seed = &instr.new_account_seed; -/// let new_account_bump_seed = instr.new_account_bump_seed; -/// -/// let rent = Rent::get()? -/// .minimum_balance(instr.space.try_into().expect("overflow")); -/// -/// invoke_signed( -/// &system_instruction::transfer( -/// payer.key, -/// new_account_pda.key, -/// rent, -/// ), -/// &[payer.clone(), new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// invoke_signed( -/// &system_instruction::allocate( -/// new_account_pda.key, -/// instr.space, -/// ), -/// &[new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// invoke_signed( -/// &system_instruction::assign( -/// new_account_pda.key, -/// &program_id, -/// ), -/// &[new_account_pda.clone()], -/// &[&[payer.key.as_ref(), new_account_seed, &[new_account_bump_seed]]], -/// )?; -/// -/// Ok(()) -/// } -/// -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction { - let account_metas = vec![AccountMeta::new(*pubkey, true)]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::Allocate { space }, - account_metas, - ) -} - -pub fn allocate_with_seed( - address: &Pubkey, // must match create_with_seed(base, seed, owner) - base: &Pubkey, - seed: &str, - space: u64, - owner: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*address, false), - AccountMeta::new_readonly(*base, true), - ]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::AllocateWithSeed { - base: *base, - seed: seed.to_string(), - space, - owner: *owner, - }, - account_metas, - ) -} - -/// Transfer lamports from an account owned by the system program to multiple accounts. -/// -/// This function produces a vector of [`Instruction`]s which must be submitted -/// in a [`Transaction`] or [invoked] to take effect, containing serialized -/// [`SystemInstruction::Transfer`]s. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// # Required signers -/// -/// The `from_pubkey` signer must sign the transaction. -/// -/// # Examples -/// -/// ## Example: client-side RPC -/// -/// This example performs multiple transfers in a single transaction. -/// -/// ``` -/// # use solana_program::example_mocks::{solana_sdk, solana_rpc_client}; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// transaction::Transaction, -/// }; -/// use anyhow::Result; -/// -/// fn transfer_lamports_to_many( -/// client: &RpcClient, -/// from: &Keypair, -/// to_and_amount: &[(Pubkey, u64)], -/// ) -> Result<()> { -/// let instrs = system_instruction::transfer_many(&from.pubkey(), to_and_amount); -/// -/// let blockhash = client.get_latest_blockhash()?; -/// let tx = Transaction::new_signed_with_payer( -/// &instrs, -/// Some(&from.pubkey()), -/// &[from], -/// blockhash, -/// ); -/// -/// let _sig = client.send_and_confirm_transaction(&tx)?; -/// -/// Ok(()) -/// } -/// # let from = Keypair::new(); -/// # let to_and_amount = vec![ -/// # (Pubkey::new_unique(), 1_000), -/// # (Pubkey::new_unique(), 2_000), -/// # (Pubkey::new_unique(), 3_000), -/// # ]; -/// # let client = RpcClient::new(String::new()); -/// # transfer_lamports_to_many(&client, &from, &to_and_amount); -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -/// -/// ## Example: on-chain program -/// -/// This example makes multiple transfers out of a "bank" account, -/// a [program derived address][pda] owned by the calling program. -/// This example submits the instructions from an on-chain Solana program. The -/// created account is a [program derived address][pda], and it is assigned to -/// the running program. The `payer` and `new_account_pda` are signers, with -/// `new_account_pda` being signed for virtually by the program itself via -/// [`invoke_signed`], `payer` being signed for by the client that submitted the -/// transaction. -/// -/// [pda]: Pubkey::find_program_address -/// [`invoke_signed`]: crate::program::invoke_signed -/// -/// ``` -/// # use borsh::{BorshDeserialize, BorshSerialize}; -/// use solana_program::{ -/// account_info::{next_account_info, next_account_infos, AccountInfo}, -/// entrypoint, -/// entrypoint::ProgramResult, -/// msg, -/// program::invoke_signed, -/// pubkey::Pubkey, -/// system_instruction, -/// system_program, -/// }; -/// -/// /// # Accounts -/// /// -/// /// - 0: bank_pda - writable -/// /// - 1: system_program - executable -/// /// - *: to - writable -/// #[derive(BorshSerialize, BorshDeserialize, Debug)] -/// # #[borsh(crate = "borsh")] -/// pub struct TransferLamportsToManyInstruction { -/// pub bank_pda_bump_seed: u8, -/// pub amount_list: Vec, -/// } -/// -/// entrypoint!(process_instruction); -/// -/// fn process_instruction( -/// program_id: &Pubkey, -/// accounts: &[AccountInfo], -/// instruction_data: &[u8], -/// ) -> ProgramResult { -/// let instr = TransferLamportsToManyInstruction::deserialize(&mut &instruction_data[..])?; -/// -/// let account_info_iter = &mut accounts.iter(); -/// -/// let bank_pda = next_account_info(account_info_iter)?; -/// let bank_pda_bump_seed = instr.bank_pda_bump_seed; -/// let system_account = next_account_info(account_info_iter)?; -/// -/// assert!(system_program::check_id(system_account.key)); -/// -/// let to_accounts = next_account_infos(account_info_iter, account_info_iter.len())?; -/// -/// for to_account in to_accounts { -/// assert!(to_account.is_writable); -/// // ... do other verification ... -/// } -/// -/// let to_and_amount = to_accounts -/// .iter() -/// .zip(instr.amount_list.iter()) -/// .map(|(to, amount)| (*to.key, *amount)) -/// .collect::>(); -/// -/// let instrs = system_instruction::transfer_many(bank_pda.key, to_and_amount.as_ref()); -/// -/// for instr in instrs { -/// invoke_signed(&instr, accounts, &[&[b"bank", &[bank_pda_bump_seed]]])?; -/// } -/// -/// Ok(()) -/// } -/// -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn transfer_many(from_pubkey: &Pubkey, to_lamports: &[(Pubkey, u64)]) -> Vec { - to_lamports - .iter() - .map(|(to_pubkey, lamports)| transfer(from_pubkey, to_pubkey, *lamports)) - .collect() -} - -pub fn create_nonce_account_with_seed( - from_pubkey: &Pubkey, - nonce_pubkey: &Pubkey, - base: &Pubkey, - seed: &str, - authority: &Pubkey, - lamports: u64, -) -> Vec { - vec![ - create_account_with_seed( - from_pubkey, - nonce_pubkey, - base, - seed, - lamports, - nonce::State::size() as u64, - &system_program::id(), - ), - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::InitializeNonceAccount(*authority), - vec![ - AccountMeta::new(*nonce_pubkey, false), - #[allow(deprecated)] - AccountMeta::new_readonly(recent_blockhashes::id(), false), - AccountMeta::new_readonly(rent::id(), false), - ], - ), - ] -} - -/// Create an account containing a durable transaction nonce. -/// -/// This function produces a vector of [`Instruction`]s which must be submitted -/// in a [`Transaction`] or [invoked] to take effect, containing a serialized -/// [`SystemInstruction::CreateAccount`] and -/// [`SystemInstruction::InitializeNonceAccount`]. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// A [durable transaction nonce][dtn] is a special account that enables -/// execution of transactions that have been signed in the past. -/// -/// Standard Solana transactions include a [recent blockhash][rbh] (sometimes -/// referred to as a _[nonce]_). During execution the Solana runtime verifies -/// the recent blockhash is approximately less than two minutes old, and that in -/// those two minutes no other identical transaction with the same blockhash has -/// been executed. These checks prevent accidental replay of transactions. -/// Consequently, it is not possible to sign a transaction, wait more than two -/// minutes, then successfully execute that transaction. -/// -/// [dtn]: https://docs.solanalabs.com/implemented-proposals/durable-tx-nonces -/// [rbh]: crate::message::Message::recent_blockhash -/// [nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce -/// -/// Durable transaction nonces are an alternative to the standard recent -/// blockhash nonce. They are stored in accounts on chain, and every time they -/// are used their value is changed to a new value for their next use. The -/// runtime verifies that each durable nonce value is only used once, and there -/// are no restrictions on how "old" the nonce is. Because they are stored on -/// chain and require additional instructions to use, transacting with durable -/// transaction nonces is more expensive than with standard transactions. -/// -/// The value of the durable nonce is itself a blockhash and is accessible via -/// the [`blockhash`] field of [`nonce::state::Data`], which is deserialized -/// from the nonce account data. -/// -/// [`blockhash`]: crate::nonce::state::Data::blockhash -/// [`nonce::state::Data`]: crate::nonce::state::Data -/// -/// The basic durable transaction nonce lifecycle is -/// -/// 1) Create the nonce account with the `create_nonce_account` instruction. -/// 2) Submit specially-formed transactions that include the -/// [`advance_nonce_account`] instruction. -/// 3) Destroy the nonce account by withdrawing its lamports with the -/// [`withdraw_nonce_account`] instruction. -/// -/// Nonce accounts have an associated _authority_ account, which is stored in -/// their account data, and can be changed with the [`authorize_nonce_account`] -/// instruction. The authority must sign transactions that include the -/// `advance_nonce_account`, `authorize_nonce_account` and -/// `withdraw_nonce_account` instructions. -/// -/// Nonce accounts are owned by the system program. -/// -/// This constructor creates a [`SystemInstruction::CreateAccount`] instruction -/// and a [`SystemInstruction::InitializeNonceAccount`] instruction. -/// -/// # Required signers -/// -/// The `from_pubkey` and `nonce_pubkey` signers must sign the transaction. -/// -/// # Examples -/// -/// Create a nonce account from an off-chain client: -/// -/// ``` -/// # use solana_program::example_mocks::solana_sdk; -/// # use solana_program::example_mocks::solana_rpc_client; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// # pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// transaction::Transaction, -/// nonce::State, -/// }; -/// use anyhow::Result; -/// -/// fn submit_create_nonce_account_tx( -/// client: &RpcClient, -/// payer: &Keypair, -/// ) -> Result<()> { -/// -/// let nonce_account = Keypair::new(); -/// -/// let nonce_rent = client.get_minimum_balance_for_rent_exemption(State::size())?; -/// let instr = system_instruction::create_nonce_account( -/// &payer.pubkey(), -/// &nonce_account.pubkey(), -/// &payer.pubkey(), // Make the fee payer the nonce account authority -/// nonce_rent, -/// ); -/// -/// let mut tx = Transaction::new_with_payer(&instr, Some(&payer.pubkey())); -/// -/// let blockhash = client.get_latest_blockhash()?; -/// tx.try_sign(&[&nonce_account, payer], blockhash)?; -/// -/// client.send_and_confirm_transaction(&tx)?; -/// -/// Ok(()) -/// } -/// # -/// # let client = RpcClient::new(String::new()); -/// # let payer = Keypair::new(); -/// # submit_create_nonce_account_tx(&client, &payer)?; -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn create_nonce_account( - from_pubkey: &Pubkey, - nonce_pubkey: &Pubkey, - authority: &Pubkey, - lamports: u64, -) -> Vec { - vec![ - create_account( - from_pubkey, - nonce_pubkey, - lamports, - nonce::State::size() as u64, - &system_program::id(), - ), - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::InitializeNonceAccount(*authority), - vec![ - AccountMeta::new(*nonce_pubkey, false), - #[allow(deprecated)] - AccountMeta::new_readonly(recent_blockhashes::id(), false), - AccountMeta::new_readonly(rent::id(), false), - ], - ), - ] -} - -/// Advance the value of a durable transaction nonce. -/// -/// This function produces an [`Instruction`] which must be submitted in a -/// [`Transaction`] or [invoked] to take effect, containing a serialized -/// [`SystemInstruction::AdvanceNonceAccount`]. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// Every transaction that relies on a durable transaction nonce must contain a -/// [`SystemInstruction::AdvanceNonceAccount`] instruction as the first -/// instruction in the [`Message`], as created by this function. When included -/// in the first position, the Solana runtime recognizes the transaction as one -/// that relies on a durable transaction nonce and processes it accordingly. The -/// [`Message::new_with_nonce`] function can be used to construct a `Message` in -/// the correct format without calling `advance_nonce_account` directly. -/// -/// When constructing a transaction that includes an `AdvanceNonceInstruction` -/// the [`recent_blockhash`] must be treated differently — instead of -/// setting it to a recent blockhash, the value of the nonce must be retrieved -/// and deserialized from the nonce account, and that value specified as the -/// "recent blockhash". A nonce account can be deserialized with the -/// [`solana_rpc_client_nonce_utils::data_from_account`][dfa] function. -/// -/// For further description of durable transaction nonces see -/// [`create_nonce_account`]. -/// -/// [`Message`]: crate::message::Message -/// [`Message::new_with_nonce`]: crate::message::Message::new_with_nonce -/// [`recent_blockhash`]: crate::message::Message::recent_blockhash -/// [dfa]: https://docs.rs/solana-rpc-client-nonce-utils/latest/solana_rpc_client_nonce_utils/fn.data_from_account.html -/// -/// # Required signers -/// -/// The `authorized_pubkey` signer must sign the transaction. -/// -/// # Examples -/// -/// Create and sign a transaction with a durable nonce: -/// -/// ``` -/// # use solana_program::example_mocks::solana_sdk; -/// # use solana_program::example_mocks::solana_rpc_client; -/// # use solana_program::example_mocks::solana_rpc_client_nonce_utils; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// message::Message, -/// pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// transaction::Transaction, -/// }; -/// # use solana_sdk::account::Account; -/// use std::path::Path; -/// use anyhow::Result; -/// # use anyhow::anyhow; -/// -/// fn create_transfer_tx_with_nonce( -/// client: &RpcClient, -/// nonce_account_pubkey: &Pubkey, -/// payer: &Keypair, -/// receiver: &Pubkey, -/// amount: u64, -/// tx_path: &Path, -/// ) -> Result<()> { -/// -/// let instr_transfer = system_instruction::transfer( -/// &payer.pubkey(), -/// receiver, -/// amount, -/// ); -/// -/// // In this example, `payer` is `nonce_account_pubkey`'s authority -/// let instr_advance_nonce_account = system_instruction::advance_nonce_account( -/// nonce_account_pubkey, -/// &payer.pubkey(), -/// ); -/// -/// // The `advance_nonce_account` instruction must be the first issued in -/// // the transaction. -/// let message = Message::new( -/// &[ -/// instr_advance_nonce_account, -/// instr_transfer -/// ], -/// Some(&payer.pubkey()), -/// ); -/// -/// let mut tx = Transaction::new_unsigned(message); -/// -/// // Sign the tx with nonce_account's `blockhash` instead of the -/// // network's latest blockhash. -/// # client.set_get_account_response(*nonce_account_pubkey, Account { -/// # lamports: 1, -/// # data: vec![0], -/// # owner: solana_sdk::system_program::ID, -/// # executable: false, -/// # rent_epoch: 1, -/// # }); -/// let nonce_account = client.get_account(nonce_account_pubkey)?; -/// let nonce_data = solana_rpc_client_nonce_utils::data_from_account(&nonce_account)?; -/// let blockhash = nonce_data.blockhash(); -/// -/// tx.try_sign(&[payer], blockhash)?; -/// -/// // Save the signed transaction locally for later submission. -/// save_tx_to_file(&tx_path, &tx)?; -/// -/// Ok(()) -/// } -/// # -/// # fn save_tx_to_file(path: &Path, tx: &Transaction) -> Result<()> { -/// # Ok(()) -/// # } -/// # -/// # let client = RpcClient::new(String::new()); -/// # let nonce_account_pubkey = Pubkey::new_unique(); -/// # let payer = Keypair::new(); -/// # let receiver = Pubkey::new_unique(); -/// # create_transfer_tx_with_nonce(&client, &nonce_account_pubkey, &payer, &receiver, 1024, Path::new("new_tx"))?; -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*nonce_pubkey, false), - #[allow(deprecated)] - AccountMeta::new_readonly(recent_blockhashes::id(), false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::AdvanceNonceAccount, - account_metas, - ) -} - -/// Withdraw lamports from a durable transaction nonce account. -/// -/// This function produces an [`Instruction`] which must be submitted in a -/// [`Transaction`] or [invoked] to take effect, containing a serialized -/// [`SystemInstruction::WithdrawNonceAccount`]. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// Withdrawing the entire balance of a nonce account will cause the runtime to -/// destroy it upon successful completion of the transaction. -/// -/// Otherwise, nonce accounts must maintain a balance greater than or equal to -/// the minimum required for [rent exemption]. If the result of this instruction -/// would leave the nonce account with a balance less than required for rent -/// exemption, but also greater than zero, then the transaction will fail. -/// -/// [rent exemption]: https://solana.com/docs/core/accounts#rent-exemption -/// -/// This constructor creates a [`SystemInstruction::WithdrawNonceAccount`] -/// instruction. -/// -/// # Required signers -/// -/// The `authorized_pubkey` signer must sign the transaction. -/// -/// # Examples -/// -/// ``` -/// # use solana_program::example_mocks::solana_sdk; -/// # use solana_program::example_mocks::solana_rpc_client; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// transaction::Transaction, -/// }; -/// use anyhow::Result; -/// -/// fn submit_withdraw_nonce_account_tx( -/// client: &RpcClient, -/// nonce_account_pubkey: &Pubkey, -/// authorized_account: &Keypair, -/// ) -> Result<()> { -/// -/// let nonce_balance = client.get_balance(nonce_account_pubkey)?; -/// -/// let instr = system_instruction::withdraw_nonce_account( -/// &nonce_account_pubkey, -/// &authorized_account.pubkey(), -/// &authorized_account.pubkey(), -/// nonce_balance, -/// ); -/// -/// let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey())); -/// -/// let blockhash = client.get_latest_blockhash()?; -/// tx.try_sign(&[authorized_account], blockhash)?; -/// -/// client.send_and_confirm_transaction(&tx)?; -/// -/// Ok(()) -/// } -/// # -/// # let client = RpcClient::new(String::new()); -/// # let nonce_account_pubkey = Pubkey::new_unique(); -/// # let payer = Keypair::new(); -/// # submit_withdraw_nonce_account_tx(&client, &nonce_account_pubkey, &payer)?; -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn withdraw_nonce_account( - nonce_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - to_pubkey: &Pubkey, - lamports: u64, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*nonce_pubkey, false), - AccountMeta::new(*to_pubkey, false), - #[allow(deprecated)] - AccountMeta::new_readonly(recent_blockhashes::id(), false), - AccountMeta::new_readonly(rent::id(), false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::WithdrawNonceAccount(lamports), - account_metas, - ) -} - -/// Change the authority of a durable transaction nonce account. -/// -/// This function produces an [`Instruction`] which must be submitted in a -/// [`Transaction`] or [invoked] to take effect, containing a serialized -/// [`SystemInstruction::AuthorizeNonceAccount`]. -/// -/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -/// [invoked]: crate::program::invoke -/// -/// This constructor creates a [`SystemInstruction::AuthorizeNonceAccount`] -/// instruction. -/// -/// # Required signers -/// -/// The `authorized_pubkey` signer must sign the transaction. -/// -/// # Examples -/// -/// ``` -/// # use solana_program::example_mocks::solana_sdk; -/// # use solana_program::example_mocks::solana_rpc_client; -/// use solana_rpc_client::rpc_client::RpcClient; -/// use solana_sdk::{ -/// pubkey::Pubkey, -/// signature::{Keypair, Signer}, -/// system_instruction, -/// transaction::Transaction, -/// }; -/// use anyhow::Result; -/// -/// fn authorize_nonce_account_tx( -/// client: &RpcClient, -/// nonce_account_pubkey: &Pubkey, -/// authorized_account: &Keypair, -/// new_authority_pubkey: &Pubkey, -/// ) -> Result<()> { -/// -/// let instr = system_instruction::authorize_nonce_account( -/// &nonce_account_pubkey, -/// &authorized_account.pubkey(), -/// &new_authority_pubkey, -/// ); -/// -/// let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey())); -/// -/// let blockhash = client.get_latest_blockhash()?; -/// tx.try_sign(&[authorized_account], blockhash)?; -/// -/// client.send_and_confirm_transaction(&tx)?; -/// -/// Ok(()) -/// } -/// # -/// # let client = RpcClient::new(String::new()); -/// # let nonce_account_pubkey = Pubkey::new_unique(); -/// # let payer = Keypair::new(); -/// # let new_authority_pubkey = Pubkey::new_unique(); -/// # authorize_nonce_account_tx(&client, &nonce_account_pubkey, &payer, &new_authority_pubkey)?; -/// # -/// # Ok::<(), anyhow::Error>(()) -/// ``` -pub fn authorize_nonce_account( - nonce_pubkey: &Pubkey, - authorized_pubkey: &Pubkey, - new_authority: &Pubkey, -) -> Instruction { - let account_metas = vec![ - AccountMeta::new(*nonce_pubkey, false), - AccountMeta::new_readonly(*authorized_pubkey, true), - ]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::AuthorizeNonceAccount(*new_authority), - account_metas, - ) -} - -/// One-time idempotent upgrade of legacy nonce versions in order to bump -/// them out of chain blockhash domain. -pub fn upgrade_nonce_account(nonce_pubkey: Pubkey) -> Instruction { - let account_metas = vec![AccountMeta::new(nonce_pubkey, /*is_signer:*/ false)]; - Instruction::new_with_bincode( - system_program::id(), - &SystemInstruction::UpgradeNonceAccount, - account_metas, - ) -} - -#[cfg(test)] -mod tests { - use {super::*, crate::instruction::Instruction}; - - fn get_keys(instruction: &Instruction) -> Vec { - instruction.accounts.iter().map(|x| x.pubkey).collect() - } - - #[test] - fn test_move_many() { - let alice_pubkey = Pubkey::new_unique(); - let bob_pubkey = Pubkey::new_unique(); - let carol_pubkey = Pubkey::new_unique(); - let to_lamports = vec![(bob_pubkey, 1), (carol_pubkey, 2)]; - - let instructions = transfer_many(&alice_pubkey, &to_lamports); - assert_eq!(instructions.len(), 2); - assert_eq!(get_keys(&instructions[0]), vec![alice_pubkey, bob_pubkey]); - assert_eq!(get_keys(&instructions[1]), vec![alice_pubkey, carol_pubkey]); - } - - #[test] - fn test_create_nonce_account() { - let from_pubkey = Pubkey::new_unique(); - let nonce_pubkey = Pubkey::new_unique(); - let authorized = nonce_pubkey; - let ixs = create_nonce_account(&from_pubkey, &nonce_pubkey, &authorized, 42); - assert_eq!(ixs.len(), 2); - let ix = &ixs[0]; - assert_eq!(ix.program_id, system_program::id()); - let pubkeys: Vec<_> = ix.accounts.iter().map(|am| am.pubkey).collect(); - assert!(pubkeys.contains(&from_pubkey)); - assert!(pubkeys.contains(&nonce_pubkey)); - } -}