#[cfg(feature = "fuzzing")]
use libfuzzer_sys::arbitrary;
use thiserror::Error;
use crate::program_error::*;
use crate::pubkey::Pubkey;
use crate::stake::instruction::StakeError;
use crate::system_instruction::SystemError;
use crate::vote::instruction::VoteError;
use crate::{account::AccountMeta, program_error::ProgramError};
use bitcode::{Decode, Encode};
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use sha256::digest;
#[derive(
Debug,
PartialEq,
Eq,
Clone,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
Encode,
Decode,
)]
#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
pub struct Instruction {
pub program_id: Pubkey,
pub accounts: Vec<AccountMeta>,
pub data: Vec<u8>,
}
impl Instruction {
pub fn new(program_id: Pubkey, data: Vec<u8>, accounts: Vec<AccountMeta>) -> Self {
Self {
program_id,
accounts,
data,
}
}
pub fn serialize(&self) -> Vec<u8> {
let mut serilized = vec![];
serilized.extend(self.program_id.serialize());
serilized.push(self.accounts.len() as u8);
for meta in self.accounts.iter() {
serilized.extend(&meta.serialize());
}
serilized.extend(self.data.len().to_le_bytes());
serilized.extend(&self.data);
serilized
}
pub fn from_slice(data: &[u8]) -> Self {
let mut size = 32;
let accounts_len = data[size] as usize;
size += 1;
let mut accounts = Vec::with_capacity(accounts_len);
for _ in 0..accounts_len {
accounts.push(AccountMeta::from_slice(&data[size..(size + 34)]).unwrap());
size += size_of::<AccountMeta>();
}
let data_len = u64::from_le_bytes(data[size..(size + 8)].try_into().unwrap());
size += size_of::<u64>();
Self {
program_id: Pubkey::from_slice(&data[..32]),
accounts,
data: (data[size..(size + data_len as usize)]).to_vec(),
}
}
pub fn hash(&self) -> String {
digest(digest(self.serialize()))
}
pub fn new_with_bincode<T: Serialize>(
program_id: Pubkey,
data: T,
accounts: Vec<AccountMeta>,
) -> Self {
Self {
program_id,
accounts,
data: bincode::serialize(&data).unwrap(),
}
}
pub fn new_with_borsh<T: borsh::BorshSerialize>(
program_id: Pubkey,
data: &T,
accounts: Vec<AccountMeta>,
) -> Self {
let data = borsh::to_vec(data).unwrap();
Self {
program_id,
accounts,
data,
}
}
}
#[derive(Debug, Error, PartialEq, Eq, Clone)]
pub enum InstructionError {
#[error("generic instruction error")]
GenericError,
#[error("invalid program argument")]
InvalidArgument,
#[error("invalid instruction data")]
InvalidInstructionData,
#[error("invalid account data for instruction")]
InvalidAccountData,
#[error("account data too small for instruction")]
AccountDataTooSmall,
#[error("insufficient funds for instruction")]
InsufficientFunds,
#[error("incorrect program id for instructions")]
IncorrectProgramId,
#[error("missing required signature for instruction")]
MissingRequiredSignature,
#[error("instruction requires an uninitialized account")]
AccountAlreadyInitialized,
#[error("instruction requires an initialized account")]
UninitializedAccount,
#[error("sum of account balances before and after instruction do not match")]
UnbalancedInstruction,
#[error("instruction illegally modified the program id of an account")]
ModifiedProgramId,
#[error("instruction spent from the balance of an account it does not own")]
ExternalAccountLamportSpend,
#[error("instruction modified data of an account it does not own, Account key: {0}, instruction program should own this account in order to modify it")]
ExternalAccountDataModified(String),
#[error("instruction modified data of a read-only account, Account key: {0}")]
ReadonlyDataModified(String),
#[error("instruction contains duplicate accounts")]
DuplicateAccountIndex,
#[error("instruction changed executable bit of an account")]
ExecutableModified,
#[error("insufficient account keys for instruction")]
NotEnoughAccountKeys,
#[error("program other than the account's owner changed the size of the account data")]
AccountDataSizeChanged,
#[error("instruction expected an executable account")]
AccountNotExecutable,
#[error("instruction tries to borrow reference for an account which is already borrowed")]
AccountBorrowFailed,
#[error("instruction left account with an outstanding borrowed reference")]
AccountBorrowOutstanding,
#[error("instruction modifications of multiply-passed account differ")]
DuplicateAccountOutOfSync,
#[error("custom program error: {0:#x}")]
Custom(u32),
#[error("program error: {0}")]
ProgramError(ProgramError),
#[error("program returned invalid error code")]
InvalidError,
#[error("instruction changed executable accounts data")]
ExecutableDataModified,
#[error("Unsupported program id")]
UnsupportedProgramId,
#[error("Cross-program invocation call depth too deep")]
CallDepth,
#[error("An account required by the instruction is missing")]
MissingAccount,
#[error("Cross-program invocation reentrancy not allowed for this instruction")]
ReentrancyNotAllowed,
#[error("Length of the seed is too long for address generation")]
MaxSeedLengthExceeded,
#[error("Provided seeds do not result in a valid address")]
InvalidSeeds,
#[error("Failed to reallocate account data")]
InvalidRealloc,
#[error("Computational budget exceeded")]
ComputationalBudgetExceeded,
#[error("Cross-program invocation with unauthorized signer or writable account")]
PrivilegeEscalation,
#[error("Failed to create program execution environment")]
ProgramEnvironmentSetupFailure,
#[error("Program failed to complete")]
ProgramFailedToComplete,
#[error("Program failed to compile")]
ProgramFailedToCompile,
#[error("Elf failed to parse")]
ElfFailedToParse,
#[error("Account is immutable")]
Immutable,
#[error("Incorrect authority provided")]
IncorrectAuthority,
#[error("Failed to serialize or deserialize account data: {0}")]
BorshIoError(String),
#[error("Invalid account owner")]
InvalidAccountOwner,
#[error("Program arithmetic overflowed")]
ArithmeticOverflow,
#[error("Unsupported sysvar")]
UnsupportedSysvar,
#[error("Provided owner is not allowed")]
IllegalOwner,
#[error("Accounts data allocations exceeded the maximum allowed per transaction")]
MaxAccountsDataAllocationsExceeded,
#[error("Max accounts exceeded")]
MaxAccountsExceeded,
#[error("Max instruction trace length exceeded")]
MaxInstructionTraceLengthExceeded,
#[error("unable to connect to bitcoin rpc")]
RPCError,
#[error("Builtin programs must consume compute units")]
BuiltinProgramsMustConsumeComputeUnits,
#[error("Vm failed while executing ebpf code {0}")]
EbpfError(String),
#[error("Invalid transaction to sign")]
InvalidTxToSign,
#[error("Invalid input to sign")]
InvalidInputToSign,
#[error("Error occured during running of System Program {0}")]
SystemError(SystemError), #[error("Account lamports cannot be negative")]
NegativeAccountLamports,
#[error("Readonly lamport change")]
ReadonlyLamportChange,
#[error("Executable lamport change")]
ExecutableLamportChange,
#[error("Bitcoin encoding error")]
BitcoinEncodingError,
#[error("Titan error")]
TitanError,
#[error("Invalid utxo owner")]
InvalidUtxoOwner,
#[error("Stake error: {0}")]
StakeError(StakeError),
#[error("Vote error: {0}")]
VoteError(VoteError),
#[error("Account is not anchored")]
AccountNotAnchored,
#[error("Not enough compute units")]
NotEnoughComputeUnits,
#[error("Transcript verification failed")]
TranscriptVerificationFailed,
#[error("Invalid chunk: {0}")]
InvalidChunk(String),
#[error("Transaction to sign empty")]
TransactionToSignEmpty,
#[error("Invalid utxo id")]
InvalidUtxoId,
#[error("Invalid utxo signer")]
InvalidUtxoSigner,
#[error("Unable to find valid utxo for given account")]
InvalidUtxo,
#[error("Unable to fetch Utxo Tx")]
UnableToFetchUtxoTx,
#[error("Build Account Address Error")]
BuildAccountAddressError,
}
impl From<SystemError> for InstructionError {
fn from(error: SystemError) -> Self {
InstructionError::SystemError(error)
}
}
#[allow(non_snake_case)]
impl From<u64> for InstructionError {
fn from(value: u64) -> Self {
match value {
CUSTOM_ZERO => Self::Custom(0),
INVALID_ARGUMENT => Self::InvalidArgument,
INVALID_INSTRUCTION_DATA => Self::InvalidInstructionData,
INVALID_ACCOUNT_DATA => Self::InvalidAccountData,
ACCOUNT_DATA_TOO_SMALL => Self::AccountDataTooSmall,
INSUFFICIENT_FUNDS => Self::InsufficientFunds,
INCORRECT_PROGRAM_ID => Self::IncorrectProgramId,
MISSING_REQUIRED_SIGNATURES => Self::MissingRequiredSignature,
ACCOUNT_ALREADY_INITIALIZED => Self::AccountAlreadyInitialized,
UNINITIALIZED_ACCOUNT => Self::UninitializedAccount,
NOT_ENOUGH_ACCOUNT_KEYS => Self::NotEnoughAccountKeys,
ACCOUNT_BORROW_FAILED => Self::AccountBorrowFailed,
MAX_SEED_LENGTH_EXCEEDED => Self::MaxSeedLengthExceeded,
INVALID_SEEDS => Self::InvalidSeeds,
BORSH_IO_ERROR => Self::BorshIoError("Unknown".to_string()),
UNSUPPORTED_SYSVAR => Self::UnsupportedSysvar,
ILLEGAL_OWNER => Self::IllegalOwner,
MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED => Self::MaxAccountsDataAllocationsExceeded,
INVALID_ACCOUNT_DATA_REALLOC => Self::InvalidRealloc,
MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED => Self::MaxInstructionTraceLengthExceeded,
BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS => {
Self::BuiltinProgramsMustConsumeComputeUnits
}
INVALID_ACCOUNT_OWNER => Self::InvalidAccountOwner,
ARITHMETIC_OVERFLOW => Self::ArithmeticOverflow,
IMMUTABLE => Self::Immutable,
INCORRECT_AUTHORITY => Self::IncorrectAuthority,
NEGATIVE_ACCOUNT_LAMPORTS => Self::NegativeAccountLamports,
READONLY_LAMPORT_CHANGE => Self::ReadonlyLamportChange,
EXECUTABLE_LAMPORT_CHANGE => Self::ExecutableLamportChange,
ACCOUNT_NOT_ANCHORED => Self::AccountNotAnchored,
NOT_ENOUGH_COMPUTE_UNITS => Self::NotEnoughComputeUnits,
TRANSCRIPT_VERIFICATION_FAILED => Self::TranscriptVerificationFailed,
INVALID_CHUNK => Self::InvalidChunk("Unknown".to_string()),
TRANSACTION_TO_SIGN_EMPTY => Self::TransactionToSignEmpty,
INVALID_UTXO_ID => Self::InvalidUtxoId,
INVALID_UTXO_SIGNER => Self::InvalidUtxoSigner,
_ => {
if value >> BUILTIN_BIT_SHIFT == 0 {
Self::Custom(value as u32)
} else {
Self::InvalidError
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{account::AccountMeta, pubkey::Pubkey};
use super::Instruction;
#[test]
fn test_serialize_deserialize() {
let instruction = Instruction {
program_id: Pubkey::system_program(),
accounts: vec![],
data: vec![],
};
assert_eq!(
instruction,
Instruction::from_slice(&instruction.serialize())
);
let instruction = Instruction {
program_id: Pubkey::system_program(),
accounts: vec![AccountMeta {
pubkey: Pubkey::system_program(),
is_signer: true,
is_writable: true,
}],
data: vec![10; 364],
};
assert_eq!(
instruction,
Instruction::from_slice(&instruction.serialize())
);
}
#[test]
fn test_error_converion_to_u64() {
let error = UNINITIALIZED_ACCOUNT;
let instruction_error = InstructionError::from(error);
assert_eq!(instruction_error, InstructionError::UninitializedAccount);
}
use proptest::prelude::*;
proptest! {
#[test]
fn fuzz_serialize_deserialize_instruction(
program_id in prop::array::uniform32(any::<u8>()),
account_pubkeys in prop::collection::vec(prop::array::uniform32(any::<u8>()), 0..10),
is_signer_flags in prop::collection::vec(any::<bool>(), 0..10),
is_writable_flags in prop::collection::vec(any::<bool>(), 0..10),
data in prop::collection::vec(any::<u8>(), 0..1024)
) {
let accounts: Vec<AccountMeta> = account_pubkeys.into_iter()
.zip(is_signer_flags.into_iter())
.zip(is_writable_flags.into_iter())
.map(|((pubkey, is_signer), is_writable)| AccountMeta {
pubkey: Pubkey::from(pubkey),
is_signer,
is_writable,
})
.collect();
let instruction = Instruction {
program_id: Pubkey::from(program_id),
accounts,
data: data.clone(),
};
let serialized = instruction.serialize();
let deserialized = Instruction::from_slice(&serialized);
assert_eq!(instruction, deserialized);
}
}
}