solana/sdk/src/instruction.rs

298 lines
10 KiB
Rust
Raw Normal View History

2019-03-23 20:12:27 -07:00
//! Defines a composable Instruction type and a memory-efficient CompiledInstruction.
use crate::sanitize::Sanitize;
2020-01-28 16:11:22 -08:00
use crate::{pubkey::Pubkey, short_vec, system_instruction::SystemError};
2019-03-24 22:51:56 -07:00
use bincode::serialize;
2019-03-23 20:12:27 -07:00
use serde::Serialize;
use thiserror::Error;
2019-03-23 20:12:27 -07:00
/// Reasons the runtime might have rejected an instruction.
#[derive(Serialize, Deserialize, Debug, Error, PartialEq, Eq, Clone, AbiExample, AbiEnumVisitor)]
2019-03-23 20:12:27 -07:00
pub enum InstructionError {
/// Deprecated! Use CustomError instead!
/// The program instruction returned an error
#[error("generic instruction error")]
2019-03-23 20:12:27 -07:00
GenericError,
/// The arguments provided to a program were invalid
#[error("invalid program argument")]
2019-03-23 20:12:27 -07:00
InvalidArgument,
/// An instruction's data contents were invalid
#[error("invalid instruction data")]
2019-03-23 20:12:27 -07:00
InvalidInstructionData,
/// An account's data contents was invalid
#[error("invalid account data for instruction")]
2019-03-23 20:12:27 -07:00
InvalidAccountData,
/// An account's data was too small
#[error("account data too small for instruction")]
2019-03-23 20:12:27 -07:00
AccountDataTooSmall,
/// An account's balance was too small to complete the instruction
#[error("insufficient funds for instruction")]
InsufficientFunds,
2019-03-23 20:12:27 -07:00
/// The account did not have the expected program id
#[error("incorrect program id for instruction")]
2019-03-23 20:12:27 -07:00
IncorrectProgramId,
/// A signature was required but not found
#[error("missing required signature for instruction")]
2019-03-23 20:12:27 -07:00
MissingRequiredSignature,
/// An initialize instruction was sent to an account that has already been initialized.
#[error("instruction requires an uninitialized account")]
2019-03-23 20:12:27 -07:00
AccountAlreadyInitialized,
/// An attempt to operate on an account that hasn't been initialized.
#[error("instruction requires an initialized account")]
2019-03-23 20:12:27 -07:00
UninitializedAccount,
/// Program's instruction lamport balance does not equal the balance after the instruction
#[error("sum of account balances before and after instruction do not match")]
2019-03-23 20:12:27 -07:00
UnbalancedInstruction,
/// Program modified an account's program id
#[error("instruction modified the program id of an account")]
2019-03-23 20:12:27 -07:00
ModifiedProgramId,
/// Program spent the lamports of an account that doesn't belong to it
#[error("instruction spent from the balance of an account it does not own")]
2019-03-23 20:12:27 -07:00
ExternalAccountLamportSpend,
/// Program modified the data of an account that doesn't belong to it
#[error("instruction modified data of an account it does not own")]
2019-03-23 20:12:27 -07:00
ExternalAccountDataModified,
/// Read-only account's lamports modified
#[error("instruction changed the balance of a read-only account")]
ReadonlyLamportChange,
/// Read-only account's data was modified
#[error("instruction modified data of a read-only account")]
ReadonlyDataModified,
2019-03-23 20:12:27 -07:00
/// An account was referenced more than once in a single instruction
// Deprecated, instructions can now contain duplicate accounts
#[error("instruction contains duplicate accounts")]
2019-03-23 20:12:27 -07:00
DuplicateAccountIndex,
/// Executable bit on account changed, but shouldn't have
#[error("instruction changed executable bit of an account")]
ExecutableModified,
/// Rent_epoch account changed, but shouldn't have
#[error("instruction modified rent epoch of an account")]
RentEpochModified,
/// The instruction expected additional account keys
2020-04-28 14:33:56 -07:00
#[error("insufficient account keys for instruction")]
NotEnoughAccountKeys,
/// A non-system program changed the size of the account data
#[error("non-system instruction changed account size")]
AccountDataSizeChanged,
2020-01-20 15:27:36 -08:00
/// The instruction expected an executable account
#[error("instruction expected an executable account")]
AccountNotExecutable,
/// Failed to borrow a reference to account data, already borrowed
#[error("instruction tries to borrow reference for an account which is already borrowed")]
AccountBorrowFailed,
/// Account data has an outstanding reference after a program's execution
#[error("instruction left account with an outstanding reference borrowed")]
AccountBorrowOutstanding,
/// The same account was multiply passed to an on-chain program's entrypoint, but the program
/// modified them differently. A program can only modify one instance of the account because
/// the runtime cannot determine which changes to pick or how to merge them if both are modified
#[error("instruction modifications of multiply-passed account differ")]
DuplicateAccountOutOfSync,
/// Allows on-chain programs to implement program-specific error types and see them returned
/// by the Solana runtime. A program-specific error may be any type that is represented as
/// or serialized to a u32 integer.
2020-04-01 09:01:11 -07:00
#[error("custom program error: {0:#x}")]
Custom(u32),
/// The return value from the program was invalid. Valid errors are either a defined builtin
/// error value or a user-defined error in the lower 32 bits.
#[error("program returned invalid error code")]
InvalidError,
/// Executable account's data was modified
#[error("instruction changed executable accounts data")]
ExecutableDataModified,
/// Executable account's lamports modified
#[error("instruction changed the balance of a executable account")]
ExecutableLamportChange,
/// Executable accounts must be rent exempt
#[error("executable accounts must be rent exempt")]
ExecutableAccountNotRentExempt,
2020-04-15 09:41:29 -07:00
/// Unsupported program id
#[error("Unsupported program id")]
UnsupportedProgramId,
2020-04-28 14:33:56 -07:00
/// Cross-program invocation call depth too deep
#[error("Cross-program invocation call depth too deep")]
CallDepth,
/// An account required by the instruction is missing
#[error("An account required by the instruction is missing")]
MissingAccount,
/// Cross-program invocation reentrancy not allowed for this instruction
#[error("Cross-program invocation reentrancy not allowed for this instruction")]
ReentrancyNotAllowed,
/// Length of the seed is too long for address generation
#[error("Length of the seed is too long for address generation")]
MaxSeedLengthExceeded,
/// Provided seeds do not result in a valid address
#[error("Provided seeds do not result in a valid address")]
InvalidSeeds,
2019-03-23 20:12:27 -07:00
}
impl InstructionError {
pub fn new_result_with_negative_lamports() -> Self {
SystemError::ResultWithNegativeLamports.into()
2019-03-23 20:12:27 -07:00
}
}
#[derive(Debug, PartialEq, Clone)]
2019-03-27 16:06:50 -07:00
pub struct Instruction {
/// Pubkey of the instruction processor that executes this instruction
pub program_id: Pubkey,
2019-03-27 16:06:50 -07:00
/// Metadata for what accounts should be passed to the instruction processor
pub accounts: Vec<AccountMeta>,
/// Opaque data passed to the instruction processor
2019-03-23 20:12:27 -07:00
pub data: Vec<u8>,
}
2019-03-27 16:06:50 -07:00
impl Instruction {
pub fn new<T: Serialize>(program_id: Pubkey, data: &T, accounts: Vec<AccountMeta>) -> Self {
2019-03-23 20:12:27 -07:00
let data = serialize(data).unwrap();
Self {
program_id,
2019-03-23 20:12:27 -07:00
data,
accounts,
}
}
}
/// Account metadata used to define Instructions
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
2019-03-23 20:12:27 -07:00
pub struct AccountMeta {
/// An account's public key
pub pubkey: Pubkey,
/// True if an Instruction requires a Transaction signature matching `pubkey`.
2019-03-23 20:12:27 -07:00
pub is_signer: bool,
/// True if the `pubkey` can be loaded as a read-write account.
pub is_writable: bool,
2019-03-23 20:12:27 -07:00
}
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,
}
2019-03-23 20:12:27 -07:00
}
}
2019-03-27 16:06:50 -07:00
/// An instruction to execute a program
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
2019-11-19 14:55:32 -08:00
#[serde(rename_all = "camelCase")]
2019-03-27 16:06:50 -07:00
pub struct CompiledInstruction {
/// Index into the transaction keys array indicating the program account that executes this instruction
pub program_id_index: u8,
2019-03-27 16:06:50 -07:00
/// Ordered indices into the transaction keys array indicating which accounts to pass to the program
#[serde(with = "short_vec")]
2019-03-27 16:06:50 -07:00
pub accounts: Vec<u8>,
/// The program input data
#[serde(with = "short_vec")]
2019-03-27 16:06:50 -07:00
pub data: Vec<u8>,
}
2019-03-23 20:12:27 -07:00
impl Sanitize for CompiledInstruction {}
2019-03-23 20:12:27 -07:00
impl CompiledInstruction {
2019-03-27 16:06:50 -07:00
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,
2019-03-27 16:06:50 -07:00
data,
accounts,
}
}
pub fn program_id<'a>(&self, program_ids: &'a [Pubkey]) -> &'a Pubkey {
&program_ids[self.program_id_index as usize]
}
2020-03-12 09:08:39 -07:00
/// Visit each unique instruction account index once
pub fn visit_each_account(
&self,
work: &mut dyn FnMut(usize, usize) -> Result<(), InstructionError>,
) -> Result<(), InstructionError> {
let mut unique_index = 0;
'root: for (i, account_index) in self.accounts.iter().enumerate() {
// Note: This is an O(n^2) algorithm,
// but performed on a very small slice and requires no heap allocations
for account_index_before in self.accounts[..i].iter() {
if account_index_before == account_index {
continue 'root; // skip dups
}
}
work(unique_index, *account_index as usize)?;
unique_index += 1;
}
Ok(())
}
2019-03-23 20:12:27 -07:00
}
#[cfg(test)]
mod test {
use super::*;
2020-03-12 09:08:39 -07:00
#[test]
fn test_visit_each_account() {
let do_work = |accounts: &[u8]| -> (usize, usize) {
let mut unique_total = 0;
let mut account_total = 0;
let mut work = |unique_index: usize, account_index: usize| {
unique_total += unique_index;
account_total += account_index;
Ok(())
};
let instruction = CompiledInstruction::new(0, &[0], accounts.to_vec());
instruction.visit_each_account(&mut work).unwrap();
(unique_total, account_total)
};
assert_eq!((6, 6), do_work(&[0, 1, 2, 3]));
assert_eq!((6, 6), do_work(&[0, 1, 1, 2, 3]));
assert_eq!((6, 6), do_work(&[0, 1, 2, 3, 3]));
assert_eq!((6, 6), do_work(&[0, 0, 1, 1, 2, 2, 3, 3]));
assert_eq!((0, 2), do_work(&[2, 2]));
}
}