solana/sdk/src/instruction.rs

229 lines
7.0 KiB
Rust

//! Defines a composable Instruction type and a memory-efficient CompiledInstruction.
use crate::pubkey::Pubkey;
use crate::short_vec;
use crate::system_instruction::SystemError;
use bincode::serialize;
use serde::Serialize;
/// Reasons the runtime might have rejected an instruction.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum InstructionError {
/// Deprecated! Use CustomError instead!
/// The program instruction returned an error
GenericError,
/// The arguments provided to a program instruction where invalid
InvalidArgument,
/// An instruction's data contents was invalid
InvalidInstructionData,
/// An account's data contents was invalid
InvalidAccountData,
/// An account's data was too small
AccountDataTooSmall,
/// An account's balance was too small to complete the instruction
InsufficientFunds,
/// The account did not have the expected program id
IncorrectProgramId,
/// A signature was required but not found
MissingRequiredSignature,
/// An initialize instruction was sent to an account that has already been initialized.
AccountAlreadyInitialized,
/// An attempt to operate on an account that hasn't been initialized.
UninitializedAccount,
/// Program's instruction lamport balance does not equal the balance after the instruction
UnbalancedInstruction,
/// Program modified an account's program id
ModifiedProgramId,
/// Program spent the lamports of an account that doesn't belong to it
ExternalAccountLamportSpend,
/// Program modified the data of an account that doesn't belong to it
ExternalAccountDataModified,
/// Read-only account modified lamports
ReadonlyLamportChange,
/// Read-only account modified data
ReadonlyDataModified,
/// An account was referenced more than once in a single instruction
DuplicateAccountIndex,
/// Executable bit on account changed, but shouldn't have
ExecutableModified,
/// Rent_epoch account changed, but shouldn't have
RentEpochModified,
/// The instruction expected additional account keys
NotEnoughAccountKeys,
/// A non-system program changed the size of the account data
AccountDataSizeChanged,
/// the instruction expected an executable account
AccountNotExecutable,
/// CustomError allows on-chain programs to implement program-specific error types and see
/// them returned by the Solana runtime. A CustomError may be any type that is represented
/// as or serialized to a u32 integer.
///
/// NOTE: u64 requires special serialization to avoid the loss of precision in JS clients and
/// so is not used for now.
CustomError(u32),
}
impl InstructionError {
pub fn new_result_with_negative_lamports() -> Self {
InstructionError::CustomError(SystemError::ResultWithNegativeLamports as u32)
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Instruction {
/// Pubkey of the instruction processor that executes this instruction
pub program_id: Pubkey,
/// Metadata for what accounts should be passed to the instruction processor
pub accounts: Vec<AccountMeta>,
/// Opaque data passed to the instruction processor
pub data: Vec<u8>,
}
impl Instruction {
pub fn new<T: Serialize>(program_id: Pubkey, data: &T, accounts: Vec<AccountMeta>) -> Self {
let data = serialize(data).unwrap();
Self {
program_id,
data,
accounts,
}
}
}
/// Account metadata used to define Instructions
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct AccountMeta {
/// An account's public key
pub pubkey: Pubkey,
/// True if an Instruction requires a Transaction signature matching `pubkey`.
pub is_signer: bool,
/// True if the `pubkey` can be loaded as a read-write account.
pub is_writable: bool,
}
impl AccountMeta {
pub fn new(pubkey: Pubkey, is_signer: bool) -> Self {
Self {
pubkey,
is_signer,
is_writable: true,
}
}
pub fn new_readonly(pubkey: Pubkey, is_signer: bool) -> Self {
Self {
pubkey,
is_signer,
is_writable: false,
}
}
}
/// Trait for adding a signer Pubkey to an existing data structure
pub trait WithSigner {
/// Add a signer Pubkey
fn with_signer(self, signer: &Pubkey) -> Self;
}
impl WithSigner for Vec<AccountMeta> {
fn with_signer(mut self, signer: &Pubkey) -> Self {
for meta in self.iter_mut() {
// signer might already appear in parameters
if &meta.pubkey == signer {
meta.is_signer = true; // found it, we're done
return self;
}
}
// signer wasn't in metas, append it after normal parameters
self.push(AccountMeta::new_readonly(*signer, true));
self
}
}
/// An instruction to execute a program
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CompiledInstruction {
/// Index into the transaction keys array indicating the program account that executes this instruction
pub program_id_index: u8,
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program
#[serde(with = "short_vec")]
pub accounts: Vec<u8>,
/// The program input data
#[serde(with = "short_vec")]
pub data: Vec<u8>,
}
impl CompiledInstruction {
pub fn new<T: Serialize>(program_ids_index: u8, data: &T, accounts: Vec<u8>) -> Self {
let data = serialize(data).unwrap();
Self {
program_id_index: program_ids_index,
data,
accounts,
}
}
pub fn program_id<'a>(&self, program_ids: &'a [Pubkey]) -> &'a Pubkey {
&program_ids[self.program_id_index as usize]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_account_meta_list_with_signer() {
let account_pubkey = Pubkey::new_rand();
let signer_pubkey = Pubkey::new_rand();
let account_meta = AccountMeta::new(account_pubkey, false);
let signer_account_meta = AccountMeta::new(signer_pubkey, false);
let metas = vec![].with_signer(&signer_pubkey);
assert_eq!(metas.len(), 1);
assert!(metas[0].is_signer);
let metas = vec![account_meta.clone()].with_signer(&signer_pubkey);
assert_eq!(metas.len(), 2);
assert!(!metas[0].is_signer);
assert!(metas[1].is_signer);
assert_eq!(metas[1].pubkey, signer_pubkey);
let metas = vec![signer_account_meta.clone()].with_signer(&signer_pubkey);
assert_eq!(metas.len(), 1);
assert!(metas[0].is_signer);
assert_eq!(metas[0].pubkey, signer_pubkey);
let metas = vec![account_meta, signer_account_meta].with_signer(&signer_pubkey);
assert_eq!(metas.len(), 2);
assert!(!metas[0].is_signer);
assert!(metas[1].is_signer);
assert_eq!(metas[1].pubkey, signer_pubkey);
}
}