//! 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, /// Opaque data passed to the instruction processor pub data: Vec, } impl Instruction { pub fn new(program_id: Pubkey, data: &T, accounts: Vec) -> 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 { 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, /// The program input data #[serde(with = "short_vec")] pub data: Vec, } impl CompiledInstruction { pub fn new(program_ids_index: u8, data: &T, accounts: Vec) -> 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); } }