384 lines
13 KiB
Rust
384 lines
13 KiB
Rust
#![allow(clippy::integer_arithmetic)]
|
|
//! Defines a composable Instruction type and a memory-efficient CompiledInstruction.
|
|
|
|
use crate::sanitize::Sanitize;
|
|
use crate::{pubkey::Pubkey, short_vec};
|
|
use bincode::serialize;
|
|
use borsh::BorshSerialize;
|
|
use serde::Serialize;
|
|
use thiserror::Error;
|
|
|
|
/// Reasons the runtime might have rejected an instruction.
|
|
#[derive(
|
|
Serialize, Deserialize, Debug, Error, PartialEq, Eq, Clone, AbiExample, AbiEnumVisitor,
|
|
)]
|
|
pub enum InstructionError {
|
|
/// Deprecated! Use CustomError instead!
|
|
/// The program instruction returned an error
|
|
#[error("generic instruction error")]
|
|
GenericError,
|
|
|
|
/// The arguments provided to a program were invalid
|
|
#[error("invalid program argument")]
|
|
InvalidArgument,
|
|
|
|
/// An instruction's data contents were invalid
|
|
#[error("invalid instruction data")]
|
|
InvalidInstructionData,
|
|
|
|
/// An account's data contents was invalid
|
|
#[error("invalid account data for instruction")]
|
|
InvalidAccountData,
|
|
|
|
/// An account's data was too small
|
|
#[error("account data too small for instruction")]
|
|
AccountDataTooSmall,
|
|
|
|
/// An account's balance was too small to complete the instruction
|
|
#[error("insufficient funds for instruction")]
|
|
InsufficientFunds,
|
|
|
|
/// The account did not have the expected program id
|
|
#[error("incorrect program id for instruction")]
|
|
IncorrectProgramId,
|
|
|
|
/// A signature was required but not found
|
|
#[error("missing required signature for instruction")]
|
|
MissingRequiredSignature,
|
|
|
|
/// An initialize instruction was sent to an account that has already been initialized.
|
|
#[error("instruction requires an uninitialized account")]
|
|
AccountAlreadyInitialized,
|
|
|
|
/// An attempt to operate on an account that hasn't been initialized.
|
|
#[error("instruction requires an initialized account")]
|
|
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")]
|
|
UnbalancedInstruction,
|
|
|
|
/// Program modified an account's program id
|
|
#[error("instruction modified the program id of an account")]
|
|
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")]
|
|
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")]
|
|
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,
|
|
|
|
/// An account was referenced more than once in a single instruction
|
|
// Deprecated, instructions can now contain duplicate accounts
|
|
#[error("instruction contains duplicate accounts")]
|
|
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
|
|
#[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,
|
|
|
|
/// 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.
|
|
#[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,
|
|
|
|
/// Unsupported program id
|
|
#[error("Unsupported program id")]
|
|
UnsupportedProgramId,
|
|
|
|
/// 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,
|
|
|
|
/// Failed to reallocate account data of this length
|
|
#[error("Failed to reallocate account data")]
|
|
InvalidRealloc,
|
|
|
|
/// Computational budget exceeded
|
|
#[error("Computational budget exceeded")]
|
|
ComputationalBudgetExceeded,
|
|
|
|
/// Cross-program invocation with unauthorized signer or writable account
|
|
#[error("Cross-program invocation with unauthorized signer or writable account")]
|
|
PrivilegeEscalation,
|
|
|
|
/// Failed to create program execution environment
|
|
#[error("Failed to create program execution environment")]
|
|
ProgramEnvironmentSetupFailure,
|
|
|
|
/// Program failed to complete
|
|
#[error("Program failed to complete")]
|
|
ProgramFailedToComplete,
|
|
|
|
/// Program failed to compile
|
|
#[error("Program failed to compile")]
|
|
ProgramFailedToCompile,
|
|
|
|
/// Account is immutable
|
|
#[error("Account is immutable")]
|
|
Immutable,
|
|
|
|
/// Incorrect authority provided
|
|
#[error("Incorrect authority provided")]
|
|
IncorrectAuthority,
|
|
|
|
/// Failed to serialize or deserialize account data
|
|
#[error("Failed to serialize or deserialize account data: {0}")]
|
|
BorshIoError(String),
|
|
|
|
/// An account does not have enough lamports to be rent-exempt
|
|
#[error("An account does not have enough lamports to be rent-exempt")]
|
|
AccountNotRentExempt,
|
|
|
|
/// Invalid account owner
|
|
#[error("Invalid account owner")]
|
|
InvalidAccountOwner,
|
|
|
|
/// Program arithmetic overflowed
|
|
#[error("Program arithmetic overflowed")]
|
|
ArithmeticOverflow,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
|
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 {
|
|
#[deprecated(
|
|
since = "1.6.0",
|
|
note = "Please use another Instruction constructor instead, such as `Instruction::new_with_bincode`"
|
|
)]
|
|
pub fn new<T: Serialize>(program_id: Pubkey, data: &T, accounts: Vec<AccountMeta>) -> Self {
|
|
Self::new_with_bincode(program_id, data, accounts)
|
|
}
|
|
|
|
pub fn new_with_bincode<T: Serialize>(
|
|
program_id: Pubkey,
|
|
data: &T,
|
|
accounts: Vec<AccountMeta>,
|
|
) -> Self {
|
|
let data = serialize(data).unwrap();
|
|
Self {
|
|
program_id,
|
|
data,
|
|
accounts,
|
|
}
|
|
}
|
|
|
|
pub fn new_with_borsh<T: BorshSerialize>(
|
|
program_id: Pubkey,
|
|
data: &T,
|
|
accounts: Vec<AccountMeta>,
|
|
) -> Self {
|
|
let data = data.try_to_vec().unwrap();
|
|
Self {
|
|
program_id,
|
|
data,
|
|
accounts,
|
|
}
|
|
}
|
|
|
|
pub fn new_with_bytes(program_id: Pubkey, data: &[u8], accounts: Vec<AccountMeta>) -> Self {
|
|
Self {
|
|
program_id,
|
|
data: data.to_vec(),
|
|
accounts,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn checked_add(a: u64, b: u64) -> Result<u64, InstructionError> {
|
|
match a.checked_add(b) {
|
|
Some(sum) => Ok(sum),
|
|
None => Err(InstructionError::InsufficientFunds),
|
|
}
|
|
}
|
|
|
|
/// 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,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An instruction to execute a program
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
|
|
#[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 Sanitize for CompiledInstruction {}
|
|
|
|
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]
|
|
}
|
|
|
|
/// 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(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[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]));
|
|
}
|
|
}
|