solana/sdk/src/system_instruction.rs

514 lines
15 KiB
Rust
Raw Normal View History

2020-01-28 16:11:22 -08:00
use crate::{
decode_error::DecodeError,
2020-05-29 23:17:44 -07:00
instruction::{AccountMeta, Instruction},
nonce,
2020-01-28 16:11:22 -08:00
pubkey::Pubkey,
system_program,
sysvar::{recent_blockhashes, rent},
};
use num_derive::{FromPrimitive, ToPrimitive};
use thiserror::Error;
2018-11-16 08:04:46 -08:00
#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
2019-03-13 13:37:24 -07:00
pub enum SystemError {
#[error("an account with the same address already exists")]
2019-03-13 13:37:24 -07:00
AccountAlreadyInUse,
#[error("account does not have enough SOL to perform the operation")]
2019-03-13 13:37:24 -07:00
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,
2019-03-13 13:37:24 -07:00
}
2019-04-25 10:29:44 -07:00
impl<T> DecodeError<T> for SystemError {
fn type_of() -> &'static str {
2019-04-25 10:29:44 -07:00
"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;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
2018-11-16 08:04:46 -08:00
pub enum SystemInstruction {
/// Create a new account
2020-05-29 19:29:24 -07:00
///
/// # Account references
/// 0. [WRITE, SIGNER] Funding account
/// 1. [WRITE, SIGNER] New account
2018-11-16 08:04:46 -08:00
CreateAccount {
2020-05-29 19:29:24 -07:00
/// Number of lamports to transfer to the new account
2019-03-05 16:28:14 -08:00
lamports: u64,
2020-05-29 19:29:24 -07:00
/// Number of bytes of memory to allocate
2018-11-16 08:04:46 -08:00
space: u64,
2020-05-29 19:29:24 -07:00
/// Address of program that will own the new account
owner: Pubkey,
2018-11-16 08:04:46 -08:00
},
2020-05-29 19:29:24 -07:00
2018-11-16 08:04:46 -08:00
/// Assign account to a program
2020-05-29 19:29:24 -07:00
///
/// # Account references
/// 0. [WRITE, SIGNER] Assigned account public key
Assign {
/// Owner program account
owner: Pubkey,
},
/// Transfer lamports
2020-05-29 19:29:24 -07:00
///
/// # Account references
/// 0. [WRITE, SIGNER] Funding account
/// 1. [WRITE] Recipient account
Transfer { lamports: u64 },
2020-05-29 19:29:24 -07:00
/// 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 {
2020-05-29 19:29:24 -07:00
/// Base public key
base: Pubkey,
2020-05-29 19:29:24 -07:00
/// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN`
seed: String,
2020-05-29 19:29:24 -07:00
/// Number of lamports to transfer to the new account
lamports: u64,
2020-05-29 19:29:24 -07:00
/// Number of bytes of memory to allocate
space: u64,
2020-05-29 19:29:24 -07:00
/// Owner program account address
owner: Pubkey,
},
2020-05-29 19:29:24 -07:00
/// Consumes a stored nonce, replacing it with a successor
///
2020-05-29 19:29:24 -07:00
/// # Account references
/// 0. [WRITE, SIGNER] Nonce account
/// 1. [] RecentBlockhashes sysvar
/// 2. [SIGNER] Nonce authority
AdvanceNonceAccount,
2020-05-29 19:29:24 -07:00
/// Withdraw funds from a nonce account
///
2020-05-29 19:29:24 -07:00
/// # 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),
2020-05-29 19:29:24 -07:00
/// Drive state of Uninitalized nonce account to Initialized, setting the nonce value
///
2020-05-29 19:29:24 -07:00
/// # 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),
2020-05-29 19:29:24 -07:00
/// Change the entity authorized to execute nonce instructions on the account
///
2020-05-29 19:29:24 -07:00
/// # Account references
/// 0. [WRITE, SIGNER] Nonce account
///
/// The `Pubkey` parameter identifies the entity to authorize
AuthorizeNonceAccount(Pubkey),
2020-05-29 19:29:24 -07:00
/// Allocate space in a (possibly new) account without funding
2020-05-29 19:29:24 -07:00
///
/// # 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
2020-05-29 19:29:24 -07:00
/// derived from a base public key and a seed
///
/// # Account references
/// 0. [WRITE] Allocated account
/// 1. [SIGNER] Base account
AllocateWithSeed {
2020-05-29 19:29:24 -07:00
/// Base public key
base: Pubkey,
2020-05-29 19:29:24 -07:00
/// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN`
seed: String,
2020-05-29 19:29:24 -07:00
/// Number of bytes of memory to allocate
space: u64,
2020-05-29 19:29:24 -07:00
/// Owner program account
owner: Pubkey,
},
2020-05-29 19:29:24 -07:00
/// Assign account to a program based on a seed
2020-05-29 19:29:24 -07:00
///
/// # Account references
/// 0. [WRITE] Assigned account
/// 1. [SIGNER] Base account
AssignWithSeed {
2020-05-29 19:29:24 -07:00
/// Base public key
base: Pubkey,
2020-05-29 19:29:24 -07:00
/// String of ASCII chars, no longer than `pubkey::MAX_SEED_LEN`
seed: String,
2020-05-29 19:29:24 -07:00
/// Owner program account
owner: Pubkey,
},
2018-11-16 08:04:46 -08:00
}
2019-02-28 03:48:44 -08:00
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,
)
}
2019-03-03 14:43:51 -08:00
// 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),
2020-05-29 23:17:44 -07:00
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,
)
}
2019-03-21 09:03:50 -07:00
pub fn assign_with_seed(
address: &Pubkey, // must match create_address_with_seed(base, seed, owner)
base: &Pubkey,
seed: &str,
owner: &Pubkey,
) -> Instruction {
2020-05-29 23:17:44 -07:00
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 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 {
2020-05-29 23:17:44 -07:00
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),
2020-05-29 23:17:44 -07:00
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),
2020-05-29 23:17:44 -07:00
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 {
2020-05-29 23:17:44 -07:00
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_rand();
let bob_pubkey = Pubkey::new_rand();
let carol_pubkey = Pubkey::new_rand();
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_rand();
let nonce_pubkey = Pubkey::new_rand();
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,
{
2020-04-01 09:01:11 -07:00
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!(
2020-04-01 09:01:11 -07:00
"Custom(0): NonceError::NoRecentBlockhashes - recent blockhash list is empty",
pretty_err::<NonceError>(NonceError::NoRecentBlockhashes.into())
);
assert_eq!(
2020-04-01 09:01:11 -07:00
"Custom(1): NonceError::NotExpired - stored nonce is still in recent_blockhashes",
pretty_err::<NonceError>(NonceError::NotExpired.into())
);
assert_eq!(
2020-04-01 09:01:11 -07:00
"Custom(2): NonceError::UnexpectedValue - specified nonce does not match stored nonce",
pretty_err::<NonceError>(NonceError::UnexpectedValue.into())
);
assert_eq!(
2020-04-01 09:01:11 -07:00
"Custom(3): NonceError::BadAccountState - cannot handle request in current account state",
pretty_err::<NonceError>(NonceError::BadAccountState.into())
);
}
2019-02-28 03:48:44 -08:00
}