solana-svm 4.2.0-beta.2

Solana SVM
Documentation
//! Shared setup helpers for the execution harnesses.

#[cfg(feature = "conformance")]
use solana_account::ReadableAccount;
use {
    crate::conformance::instr::context::InstrContext,
    solana_account::Account,
    solana_compute_budget::compute_budget::ComputeBudget,
    solana_hash::Hash,
    solana_instruction::Instruction,
    solana_message::SanitizedMessage,
    solana_program_runtime::{
        execution_budget::{SVMTransactionExecutionBudget, SVMTransactionExecutionCost},
        invoke_context::{EnvironmentConfig, mock_compile_message},
        loaded_programs::{ProgramRuntimeEnvironment, ProgramRuntimeEnvironments},
        sysvar_cache::SysvarCache,
    },
    solana_pubkey::Pubkey,
    solana_rent::Rent,
    solana_svm_callback::InvokeContextCallback,
    solana_svm_feature_set::SVMFeatureSet,
    solana_svm_log_collector::LogCollector,
    solana_svm_transaction::svm_message::SVMStaticMessage,
    solana_syscalls::create_program_runtime_environment,
    solana_transaction_context::transaction::TransactionContext,
    std::{cell::RefCell, rc::Rc},
};

/// Fields required by `InvokeContext::new`.
pub(crate) struct InvokeContextFields<'a, 'ix_data> {
    pub(crate) sanitized_message: SanitizedMessage,
    pub(crate) transaction_context: TransactionContext<'ix_data>,
    pub(crate) environment_config: EnvironmentConfig<'a>,
    pub(crate) log_collector: Rc<RefCell<LogCollector>>,
    pub(crate) execution_budget: SVMTransactionExecutionBudget,
    pub(crate) execution_cost: SVMTransactionExecutionCost,
}

/// Compile a sanitized transaction message then instantiate a transaction
/// context as well as the remaining fields required by `InvokeContext::new`.
pub(crate) fn prepare_invoke_context_fields<'a, C: InvokeContextCallback>(
    instr_context: &'a InstrContext,
    callback: &'a C,
    loader_key: &Pubkey,
    sysvar_cache: &'a SysvarCache,
    compute_budget: &ComputeBudget,
    program_runtime_environments: &'a ProgramRuntimeEnvironments,
) -> InvokeContextFields<'a, 'a> {
    let rent = sysvar_cache.get_rent().unwrap();

    let (sanitized_message, transaction_context) = compile_transaction_context(
        &instr_context.instruction,
        &instr_context.accounts,
        &instr_context.instruction.program_id,
        loader_key,
        compute_budget,
        (*rent).clone(),
    );

    let (blockhash, blockhash_lamports_per_signature) = recent_blockhash(sysvar_cache);
    let environment_config = EnvironmentConfig::new(
        blockhash,
        blockhash_lamports_per_signature,
        false,
        callback,
        &instr_context.feature_set,
        program_runtime_environments,
        sysvar_cache,
    );

    let log_collector = LogCollector::new_ref();
    let execution_budget = compute_budget.to_budget();
    let execution_cost = compute_budget.to_cost();

    InvokeContextFields {
        sanitized_message,
        transaction_context,
        environment_config,
        log_collector,
        execution_budget,
        execution_cost,
    }
}

// Create a compute budget from the given feature set.
pub(crate) fn compute_budget(feature_set: &SVMFeatureSet) -> ComputeBudget {
    let simd_0268_active = feature_set.raise_cpi_nesting_limit_to_8;
    ComputeBudget::new_with_defaults(simd_0268_active)
}

/// The loader that owns the program account in `accounts`, used as the program
/// account's owner when compiling the transaction. `None` if the program
/// account isn't present.
#[cfg(feature = "conformance")]
pub(crate) fn program_loader_key(accounts: &[(Pubkey, Account)], program_id: &Pubkey) -> Pubkey {
    accounts
        .iter()
        .find(|(key, _)| key == program_id)
        .map(|(_, account)| account.owner)
        .expect("program not found in accounts")
}

/// Compile `instruction` into a sanitized message and a fresh transaction
/// context sized for a single top-level instruction.
pub(crate) fn compile_transaction_context(
    instruction: &Instruction,
    accounts: &[(Pubkey, Account)],
    program_id: &Pubkey,
    loader_key: &Pubkey,
    compute_budget: &ComputeBudget,
    rent: Rent,
) -> (SanitizedMessage, TransactionContext<'static>) {
    let (sanitized_message, transaction_accounts) =
        mock_compile_message(instruction, accounts, program_id, loader_key);
    let transaction_context = TransactionContext::new(
        transaction_accounts,
        rent,
        compute_budget.max_instruction_stack_depth,
        compute_budget.max_instruction_trace_length,
        sanitized_message.num_instructions(),
    );
    (sanitized_message, transaction_context)
}

/// The paired (execution + deployment) program runtime environments for a
/// harness invocation. Both halves share one environment.
pub(crate) fn program_runtime_environments(
    feature_set: &SVMFeatureSet,
    compute_budget: &ComputeBudget,
) -> ProgramRuntimeEnvironments {
    let environment = create_program_runtime_environment(
        feature_set,
        &compute_budget.to_budget(),
        false, /* deployment */
        false, /* debugging_features */
    )
    .unwrap();
    ProgramRuntimeEnvironments::new(ProgramRuntimeEnvironment::clone(&environment), environment)
}

/// The most recent blockhash and its lamports-per-signature from the sysvar
/// cache, or defaults when unavailable.
pub(crate) fn recent_blockhash(sysvar_cache: &SysvarCache) -> (Hash, u64) {
    #[expect(deprecated)]
    sysvar_cache
        .get_recent_blockhashes()
        .ok()
        .and_then(|entries| entries.last().cloned())
        .map(|entry| (entry.blockhash, entry.fee_calculator.lamports_per_signature))
        .unwrap_or_default()
}

/// Build a sysvar cache populated from any sysvar accounts present in the
/// input account set.
#[cfg(feature = "conformance")]
pub(crate) fn sysvar_cache_from_accounts(accounts: &[(Pubkey, Account)]) -> SysvarCache {
    let mut cache = SysvarCache::default();
    cache.fill_missing_entries(|pubkey, set_sysvar| {
        if let Some(account) = accounts.iter().find(|(key, _)| key == pubkey)
            && account.1.lamports() > 0
        {
            set_sysvar(account.1.data());
        }
    });
    cache
}