arch_program 0.6.4

A Rust library for building programs that run inside the Arch Virtual Machine. Provides core functionality for creating instructions, managing accounts, handling program errors, and interacting with the Arch runtime environment. Includes utilities for logging, transaction handling, and Bitcoin UTXO management.
Documentation
//! Error types and handling for Arch VM programs.
//!
//! This module defines the standard error types that can be returned by programs
//! running in the Arch VM environment. It provides a uniform way to represent,
//! decode, and display program errors, allowing for consistent error handling
//! across the platform.
//!
//! The error system is designed to be compatible with program-specific custom errors
//! while also providing a set of standard error types for common failure scenarios.
use num_traits::FromPrimitive;
use thiserror::Error;

use crate::{decode_error::DecodeError, msg};

/// Reasons the program may fail
///
/// This enum defines all standard error types that can be returned by programs
/// running in the Arch VM environment. Programs can also define their own custom
/// error types using the `Custom` variant.
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum ProgramError {
    /// 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),
    #[error("The arguments provided to a program instruction were invalid")]
    InvalidArgument,
    #[error("An instruction's data contents was invalid")]
    InvalidInstructionData,
    #[error("An account's data contents was invalid")]
    InvalidAccountData,
    #[error("An account's data was too small")]
    AccountDataTooSmall,
    #[error("An account's balance was too small to complete the instruction")]
    InsufficientFunds,
    #[error("The account did not have the expected program id")]
    IncorrectProgramId,
    #[error("A signature was required but not found")]
    MissingRequiredSignature,
    #[error("An initialize instruction was sent to an account that has already been initialized")]
    AccountAlreadyInitialized,
    #[error("An attempt to operate on an account that hasn't been initialized")]
    UninitializedAccount,
    #[error("The instruction expected additional account keys")]
    NotEnoughAccountKeys,
    #[error("Failed to borrow a reference to account data, already borrowed")]
    AccountBorrowFailed,
    #[error("Length of the seed is too long for address generation")]
    MaxSeedLengthExceeded,
    #[error("The number of seeds is greater than the maximum allowed")]
    MaxSeedsExceeded,
    #[error("Provided seeds do not result in a valid address")]
    InvalidSeeds,
    #[error("IO Error: {0}")]
    BorshIoError(String),
    #[error("Unsupported sysvar")]
    IllegalOwner,
    #[error("Accounts data allocations exceeded the maximum allowed per transaction")]
    MaxAccountsDataAllocationsExceeded,
    #[error("Account data reallocation was invalid")]
    InvalidRealloc,
    #[error("Instruction trace length exceeded the maximum allowed per transaction")]
    MaxInstructionTraceLengthExceeded,
    #[error("Builtin programs must consume compute units")]
    BuiltinProgramsMustConsumeComputeUnits,
    #[error("Invalid account owner")]
    InvalidAccountOwner,
    #[error("Program arithmetic overflowed")]
    ArithmeticOverflow,
    #[error("Account is immutable")]
    Immutable,
    #[error("Incorrect authority provided")]
    IncorrectAuthority,
    #[error("From hex error")]
    FromHexError,
    #[error("Account lamports cannot be negative")]
    NegativeAccountLamports,
    #[error("Readonly lamport change")]
    ReadonlyLamportChange,
    #[error("Executable lamport change")]
    ExecutableLamportChange,
    #[error("Account is not anchored")]
    AccountNotAnchored,
    #[error("Not enough compute units available to complete the instruction")]
    NotEnoughComputeUnits,
    #[error("Insufficient data length")]
    InsufficientDataLength,
    #[error("Incorrect length")]
    IncorrectLength,
    #[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("Invalid state transition: {0}")]
    InvalidStateTransition(String),
}

pub trait PrintProgramError {
    fn print<E>(&self)
    where
        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive;
}

impl PrintProgramError for ProgramError {
    fn print<E>(&self)
    where
        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
    {
        match self {
            Self::Custom(error) => {
                if let Some(custom_error) = E::decode_custom_error_to_enum(*error) {
                    custom_error.print::<E>();
                } else {
                    msg!("Error: Unknown");
                }
            }
            Self::InvalidArgument => msg!("Error: InvalidArgument"),
            Self::InvalidInstructionData => msg!("Error: InvalidInstructionData"),
            Self::InvalidAccountData => msg!("Error: InvalidAccountData"),
            Self::AccountDataTooSmall => msg!("Error: AccountDataTooSmall"),
            Self::InsufficientFunds => msg!("Error: InsufficientFunds"),
            Self::IncorrectProgramId => msg!("Error: IncorrectProgramId"),
            Self::MissingRequiredSignature => msg!("Error: MissingRequiredSignature"),
            Self::AccountAlreadyInitialized => msg!("Error: AccountAlreadyInitialized"),
            Self::UninitializedAccount => msg!("Error: UninitializedAccount"),
            Self::NotEnoughAccountKeys => msg!("Error: NotEnoughAccountKeys"),
            Self::AccountBorrowFailed => msg!("Error: AccountBorrowFailed"),
            Self::MaxSeedLengthExceeded => msg!("Error: MaxSeedLengthExceeded"),
            Self::MaxSeedsExceeded => msg!("Error: MaxSeedsExceeded"),
            Self::InvalidSeeds => msg!("Error: InvalidSeeds"),
            Self::BorshIoError(_) => msg!("Error: BorshIoError"),
            Self::IllegalOwner => msg!("Error: IllegalOwner"),
            Self::MaxAccountsDataAllocationsExceeded => {
                msg!("Error: MaxAccountsDataAllocationsExceeded")
            }
            Self::InvalidRealloc => msg!("Error: InvalidRealloc"),
            Self::MaxInstructionTraceLengthExceeded => {
                msg!("Error: MaxInstructionTraceLengthExceeded")
            }
            Self::BuiltinProgramsMustConsumeComputeUnits => {
                msg!("Error: BuiltinProgramsMustConsumeComputeUnits")
            }
            Self::InvalidAccountOwner => msg!("Error: InvalidAccountOwner"),
            Self::ArithmeticOverflow => msg!("Error: ArithmeticOverflow"),
            Self::Immutable => msg!("Error: Immutable"),
            Self::IncorrectAuthority => msg!("Error: IncorrectAuthority"),
            Self::FromHexError => msg!("Error: FromHexError"),
            Self::NegativeAccountLamports => msg!("Error: NegativeAccountLamports"),
            Self::ReadonlyLamportChange => msg!("Error: ReadonlyLamportChange"),
            Self::ExecutableLamportChange => msg!("Error: ExecutableLamportChange"),
            Self::AccountNotAnchored => msg!("Error: AccountNotAnchored"),
            Self::NotEnoughComputeUnits => msg!("Error: NotEnoughComputeUnits"),
            Self::InsufficientDataLength => msg!("Error: InsufficientDataLength"),
            Self::IncorrectLength => msg!("Error: IncorrectLength"),
            Self::TranscriptVerificationFailed => msg!("Error: TranscriptVerificationFailed"),
            Self::InvalidChunk(_) => msg!("Error: invalid chunk"),
            Self::TransactionToSignEmpty => msg!("Error: TransactionToSignEmpty"),
            Self::InvalidUtxoId => msg!("Error: InvalidUtxoId"),
            Self::InvalidUtxoSigner => msg!("Error: InvalidUtxoSigner"),
            Self::InvalidStateTransition(_) => msg!("Error: InvalidStateTransition"),
        }
    }
}

/// Builtin return values occupy the upper 32 bits
pub const BUILTIN_BIT_SHIFT: usize = 32;
macro_rules! to_builtin {
    ($error:expr) => {
        ($error as u64) << BUILTIN_BIT_SHIFT
    };
}

pub const CUSTOM_ZERO: u64 = to_builtin!(1);
pub const INVALID_ARGUMENT: u64 = to_builtin!(2);
pub const INVALID_INSTRUCTION_DATA: u64 = to_builtin!(3);
pub const INVALID_ACCOUNT_DATA: u64 = to_builtin!(4);
pub const ACCOUNT_DATA_TOO_SMALL: u64 = to_builtin!(5);
pub const INSUFFICIENT_FUNDS: u64 = to_builtin!(6);
pub const INCORRECT_PROGRAM_ID: u64 = to_builtin!(7);
pub const MISSING_REQUIRED_SIGNATURES: u64 = to_builtin!(8);
pub const ACCOUNT_ALREADY_INITIALIZED: u64 = to_builtin!(9);
pub const UNINITIALIZED_ACCOUNT: u64 = to_builtin!(10);
pub const NOT_ENOUGH_ACCOUNT_KEYS: u64 = to_builtin!(11);
pub const ACCOUNT_BORROW_FAILED: u64 = to_builtin!(12);
pub const MAX_SEED_LENGTH_EXCEEDED: u64 = to_builtin!(13);
pub const MAX_SEEDS_EXCEEDED: u64 = to_builtin!(14);
pub const INVALID_SEEDS: u64 = to_builtin!(15);
pub const BORSH_IO_ERROR: u64 = to_builtin!(16);
pub const ACCOUNT_NOT_RENT_EXEMPT: u64 = to_builtin!(17);
pub const UNSUPPORTED_SYSVAR: u64 = to_builtin!(18);
pub const ILLEGAL_OWNER: u64 = to_builtin!(19);
pub const MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED: u64 = to_builtin!(20);
pub const INVALID_ACCOUNT_DATA_REALLOC: u64 = to_builtin!(21);
pub const MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED: u64 = to_builtin!(22);
pub const BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS: u64 = to_builtin!(23);
pub const INVALID_ACCOUNT_OWNER: u64 = to_builtin!(24);
pub const ARITHMETIC_OVERFLOW: u64 = to_builtin!(25);
pub const IMMUTABLE: u64 = to_builtin!(26);
pub const INCORRECT_AUTHORITY: u64 = to_builtin!(27);
pub const FROM_HEX_ERROR: u64 = to_builtin!(28);
pub const NEGATIVE_ACCOUNT_LAMPORTS: u64 = to_builtin!(29);
pub const READONLY_LAMPORT_CHANGE: u64 = to_builtin!(30);
pub const EXECUTABLE_LAMPORT_CHANGE: u64 = to_builtin!(31);
pub const ACCOUNT_NOT_ANCHORED: u64 = to_builtin!(32);
pub const NOT_ENOUGH_COMPUTE_UNITS: u64 = to_builtin!(33);
pub const INSUFFICIENT_DATA_LENGTH: u64 = to_builtin!(35);
pub const INCORRECT_LENGTH: u64 = to_builtin!(36);
pub const TRANSCRIPT_VERIFICATION_FAILED: u64 = to_builtin!(37);
pub const INVALID_CHUNK: u64 = to_builtin!(38);
pub const TRANSACTION_TO_SIGN_EMPTY: u64 = to_builtin!(39);
pub const INVALID_UTXO_ID: u64 = to_builtin!(40);
pub const INVALID_UTXO_SIGNER: u64 = to_builtin!(41);
pub const INVALID_STATE_TRANSITION: u64 = to_builtin!(42);
// Warning: Any new program errors added here must also be:
// - Added to the below conversions
// - Added as an equivalent to InstructionError
// - Be featureized in the BPF loader to return `InstructionError::InvalidError`
//   until the feature is activated

impl From<ProgramError> for u64 {
    fn from(error: ProgramError) -> Self {
        match error {
            ProgramError::InvalidArgument => INVALID_ARGUMENT,
            ProgramError::InvalidInstructionData => INVALID_INSTRUCTION_DATA,
            ProgramError::InvalidAccountData => INVALID_ACCOUNT_DATA,
            ProgramError::AccountDataTooSmall => ACCOUNT_DATA_TOO_SMALL,
            ProgramError::InsufficientFunds => INSUFFICIENT_FUNDS,
            ProgramError::IncorrectProgramId => INCORRECT_PROGRAM_ID,
            ProgramError::MissingRequiredSignature => MISSING_REQUIRED_SIGNATURES,
            ProgramError::AccountAlreadyInitialized => ACCOUNT_ALREADY_INITIALIZED,
            ProgramError::UninitializedAccount => UNINITIALIZED_ACCOUNT,
            ProgramError::NotEnoughAccountKeys => NOT_ENOUGH_ACCOUNT_KEYS,
            ProgramError::AccountBorrowFailed => ACCOUNT_BORROW_FAILED,
            ProgramError::MaxSeedLengthExceeded => MAX_SEED_LENGTH_EXCEEDED,
            ProgramError::MaxSeedsExceeded => MAX_SEEDS_EXCEEDED,
            ProgramError::InvalidSeeds => INVALID_SEEDS,
            ProgramError::BorshIoError(_) => BORSH_IO_ERROR,
            ProgramError::IllegalOwner => ILLEGAL_OWNER,
            ProgramError::MaxAccountsDataAllocationsExceeded => {
                MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED
            }
            ProgramError::InvalidRealloc => INVALID_ACCOUNT_DATA_REALLOC,
            ProgramError::MaxInstructionTraceLengthExceeded => {
                MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED
            }
            ProgramError::BuiltinProgramsMustConsumeComputeUnits => {
                BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS
            }
            ProgramError::InvalidAccountOwner => INVALID_ACCOUNT_OWNER,
            ProgramError::ArithmeticOverflow => ARITHMETIC_OVERFLOW,
            ProgramError::Immutable => IMMUTABLE,
            ProgramError::IncorrectAuthority => INCORRECT_AUTHORITY,
            ProgramError::FromHexError => FROM_HEX_ERROR,
            ProgramError::NegativeAccountLamports => NEGATIVE_ACCOUNT_LAMPORTS,
            ProgramError::ReadonlyLamportChange => READONLY_LAMPORT_CHANGE,
            ProgramError::ExecutableLamportChange => EXECUTABLE_LAMPORT_CHANGE,
            ProgramError::AccountNotAnchored => ACCOUNT_NOT_ANCHORED,
            ProgramError::NotEnoughComputeUnits => NOT_ENOUGH_COMPUTE_UNITS,
            ProgramError::InsufficientDataLength => INSUFFICIENT_DATA_LENGTH,
            ProgramError::IncorrectLength => INCORRECT_LENGTH,
            ProgramError::TranscriptVerificationFailed => TRANSCRIPT_VERIFICATION_FAILED,
            ProgramError::InvalidChunk(_) => INVALID_CHUNK,
            ProgramError::TransactionToSignEmpty => TRANSACTION_TO_SIGN_EMPTY,
            ProgramError::InvalidUtxoId => INVALID_UTXO_ID,
            ProgramError::InvalidUtxoSigner => INVALID_UTXO_SIGNER,
            ProgramError::InvalidStateTransition(_) => INVALID_STATE_TRANSITION,
            ProgramError::Custom(error) => {
                if error == 0 {
                    CUSTOM_ZERO
                } else {
                    error as u64
                }
            }
        }
    }
}

impl From<u64> for ProgramError {
    fn from(error: u64) -> Self {
        match error {
            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()),
            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,
            FROM_HEX_ERROR => Self::FromHexError,
            ACCOUNT_NOT_ANCHORED => Self::AccountNotAnchored,
            NOT_ENOUGH_COMPUTE_UNITS => Self::NotEnoughComputeUnits,
            INSUFFICIENT_DATA_LENGTH => Self::InsufficientDataLength,
            INCORRECT_LENGTH => Self::IncorrectLength,
            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,
            _ => Self::Custom(error as u32),
        }
    }
}

impl From<hex::FromHexError> for ProgramError {
    fn from(_: hex::FromHexError) -> Self {
        Self::FromHexError
    }
}

impl From<std::io::Error> for ProgramError {
    fn from(error: std::io::Error) -> Self {
        Self::BorshIoError(error.to_string())
    }
}