Initial population of solana-program-sdk
This commit is contained in:
556
sdk/program/src/system_instruction.rs
Normal file
556
sdk/program/src/system_instruction.rs
Normal file
@ -0,0 +1,556 @@
|
||||
use crate::{
|
||||
decode_error::DecodeError,
|
||||
instruction::{AccountMeta, Instruction},
|
||||
nonce,
|
||||
pubkey::Pubkey,
|
||||
system_program,
|
||||
sysvar::{recent_blockhashes, rent},
|
||||
};
|
||||
use num_derive::{FromPrimitive, ToPrimitive};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, Serialize, Clone, PartialEq, 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,
|
||||
}
|
||||
|
||||
impl<T> DecodeError<T> for SystemError {
|
||||
fn type_of() -> &'static str {
|
||||
"SystemError"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Clone, PartialEq, FromPrimitive, ToPrimitive)]
|
||||
pub enum NonceError {
|
||||
#[error("recent blockhash list is empty")]
|
||||
NoRecentBlockhashes,
|
||||
#[error("stored nonce is still in recent_blockhashes")]
|
||||
NotExpired,
|
||||
#[error("specified nonce does not match stored nonce")]
|
||||
UnexpectedValue,
|
||||
#[error("cannot handle request in current account state")]
|
||||
BadAccountState,
|
||||
}
|
||||
|
||||
impl<E> DecodeError<E> for NonceError {
|
||||
fn type_of() -> &'static str {
|
||||
"NonceError"
|
||||
}
|
||||
}
|
||||
|
||||
/// maximum permitted size of data: 10 MB
|
||||
pub const MAX_PERMITTED_DATA_LENGTH: u64 = 10 * 1024 * 1024;
|
||||
|
||||
#[frozen_abi(digest = "EpsptsKTYzMoQGSdoWRfPbwT3odGNfK3imEUTrxpLF1i")]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, AbiExample, AbiEnumVisitor)]
|
||||
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] Base account
|
||||
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, SIGNER] 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 Uninitalized 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,
|
||||
},
|
||||
}
|
||||
|
||||
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(
|
||||
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_address_with_seed()
|
||||
pub fn create_account_with_seed(
|
||||
from_pubkey: &Pubkey,
|
||||
to_pubkey: &Pubkey, // must match create_address_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(
|
||||
system_program::id(),
|
||||
&SystemInstruction::CreateAccountWithSeed {
|
||||
base: *base,
|
||||
seed: seed.to_string(),
|
||||
lamports,
|
||||
space,
|
||||
owner: *owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
|
||||
let account_metas = vec![AccountMeta::new(*pubkey, true)];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::Assign { owner: *owner },
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn assign_with_seed(
|
||||
address: &Pubkey, // must match create_address_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(
|
||||
system_program::id(),
|
||||
&SystemInstruction::AssignWithSeed {
|
||||
base: *base,
|
||||
seed: seed.to_string(),
|
||||
owner: *owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
system_program::id(),
|
||||
&SystemInstruction::Transfer { lamports },
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn transfer_with_seed(
|
||||
from_pubkey: &Pubkey, // must match create_address_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(
|
||||
system_program::id(),
|
||||
&SystemInstruction::TransferWithSeed {
|
||||
lamports,
|
||||
from_seed,
|
||||
from_owner: *from_owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
|
||||
let account_metas = vec![AccountMeta::new(*pubkey, true)];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::Allocate { space },
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn allocate_with_seed(
|
||||
address: &Pubkey, // must match create_address_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(
|
||||
system_program::id(),
|
||||
&SystemInstruction::AllocateWithSeed {
|
||||
base: *base,
|
||||
seed: seed.to_string(),
|
||||
space,
|
||||
owner: *owner,
|
||||
},
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create and sign new SystemInstruction::Transfer transaction to many destinations
|
||||
pub fn transfer_many(from_pubkey: &Pubkey, to_lamports: &[(Pubkey, u64)]) -> Vec<Instruction> {
|
||||
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<Instruction> {
|
||||
vec![
|
||||
create_account_with_seed(
|
||||
from_pubkey,
|
||||
nonce_pubkey,
|
||||
base,
|
||||
seed,
|
||||
lamports,
|
||||
nonce::State::size() as u64,
|
||||
&system_program::id(),
|
||||
),
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::InitializeNonceAccount(*authority),
|
||||
vec![
|
||||
AccountMeta::new(*nonce_pubkey, false),
|
||||
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||
AccountMeta::new_readonly(rent::id(), false),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn create_nonce_account(
|
||||
from_pubkey: &Pubkey,
|
||||
nonce_pubkey: &Pubkey,
|
||||
authority: &Pubkey,
|
||||
lamports: u64,
|
||||
) -> Vec<Instruction> {
|
||||
vec![
|
||||
create_account(
|
||||
from_pubkey,
|
||||
nonce_pubkey,
|
||||
lamports,
|
||||
nonce::State::size() as u64,
|
||||
&system_program::id(),
|
||||
),
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::InitializeNonceAccount(*authority),
|
||||
vec![
|
||||
AccountMeta::new(*nonce_pubkey, false),
|
||||
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||
AccountMeta::new_readonly(rent::id(), false),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn advance_nonce_account(nonce_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> Instruction {
|
||||
let account_metas = vec![
|
||||
AccountMeta::new(*nonce_pubkey, false),
|
||||
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||
AccountMeta::new_readonly(*authorized_pubkey, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::AdvanceNonceAccount,
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
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),
|
||||
AccountMeta::new_readonly(recent_blockhashes::id(), false),
|
||||
AccountMeta::new_readonly(rent::id(), false),
|
||||
AccountMeta::new_readonly(*authorized_pubkey, true),
|
||||
];
|
||||
Instruction::new(
|
||||
system_program::id(),
|
||||
&SystemInstruction::WithdrawNonceAccount(lamports),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
system_program::id(),
|
||||
&SystemInstruction::AuthorizeNonceAccount(*new_authority),
|
||||
account_metas,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::instruction::{Instruction, InstructionError};
|
||||
|
||||
fn get_keys(instruction: &Instruction) -> Vec<Pubkey> {
|
||||
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));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonce_error_decode() {
|
||||
use num_traits::FromPrimitive;
|
||||
fn pretty_err<T>(err: InstructionError) -> String
|
||||
where
|
||||
T: 'static + std::error::Error + DecodeError<T> + FromPrimitive,
|
||||
{
|
||||
if let InstructionError::Custom(code) = err {
|
||||
let specific_error: T = T::decode_custom_error_to_enum(code).unwrap();
|
||||
format!(
|
||||
"{:?}: {}::{:?} - {}",
|
||||
err,
|
||||
T::type_of(),
|
||||
specific_error,
|
||||
specific_error,
|
||||
)
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
assert_eq!(
|
||||
"Custom(0): NonceError::NoRecentBlockhashes - recent blockhash list is empty",
|
||||
pretty_err::<NonceError>(NonceError::NoRecentBlockhashes.into())
|
||||
);
|
||||
assert_eq!(
|
||||
"Custom(1): NonceError::NotExpired - stored nonce is still in recent_blockhashes",
|
||||
pretty_err::<NonceError>(NonceError::NotExpired.into())
|
||||
);
|
||||
assert_eq!(
|
||||
"Custom(2): NonceError::UnexpectedValue - specified nonce does not match stored nonce",
|
||||
pretty_err::<NonceError>(NonceError::UnexpectedValue.into())
|
||||
);
|
||||
assert_eq!(
|
||||
"Custom(3): NonceError::BadAccountState - cannot handle request in current account state",
|
||||
pretty_err::<NonceError>(NonceError::BadAccountState.into())
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user