Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions harness/src/fuzz/firedancer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use {
crate::{
compile_accounts::{compile_accounts, CompiledAccounts},
vm::SolanaVM,
Mollusk, DEFAULT_LOADER_KEY,
},
agave_feature_set::FeatureSet,
Expand Down Expand Up @@ -235,8 +236,8 @@ fn instruction_metadata() -> FuzzMetadata {
}
}

pub fn build_fixture_from_mollusk_test(
mollusk: &Mollusk,
pub fn build_fixture_from_mollusk_test<VM: SolanaVM>(
mollusk: &Mollusk<VM>,
instruction: &Instruction,
accounts: &[(Pubkey, Account)],
result: &InstructionResult,
Expand Down
12 changes: 8 additions & 4 deletions harness/src/fuzz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ pub mod firedancer;
pub mod mollusk;

use {
crate::Mollusk, mollusk_svm_fuzz_fs::FsHandler, mollusk_svm_result::InstructionResult,
solana_account::Account, solana_instruction::Instruction, solana_pubkey::Pubkey,
crate::{vm::SolanaVM, Mollusk},
mollusk_svm_fuzz_fs::FsHandler,
mollusk_svm_result::InstructionResult,
solana_account::Account,
solana_instruction::Instruction,
solana_pubkey::Pubkey,
};

pub fn generate_fixtures_from_mollusk_test(
mollusk: &Mollusk,
pub fn generate_fixtures_from_mollusk_test<VM: SolanaVM>(
mollusk: &Mollusk<VM>,
instruction: &Instruction,
accounts: &[(Pubkey, Account)],
result: &InstructionResult,
Expand Down
6 changes: 3 additions & 3 deletions harness/src/fuzz/mollusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! Only available when the `fuzz` feature is enabled.

use {
crate::{sysvar::Sysvars, Mollusk},
crate::{sysvar::Sysvars, vm::SolanaVM, Mollusk},
agave_feature_set::FeatureSet,
mollusk_svm_fuzz_fixture::{
context::Context as FuzzContext, effects::Effects as FuzzEffects,
Expand Down Expand Up @@ -102,8 +102,8 @@ pub(crate) fn parse_fixture_context(context: &FuzzContext) -> ParsedFixtureConte
}
}

pub fn build_fixture_from_mollusk_test(
mollusk: &Mollusk,
pub fn build_fixture_from_mollusk_test<VM: SolanaVM>(
mollusk: &Mollusk<VM>,
instruction: &Instruction,
accounts: &[(Pubkey, Account)],
result: &InstructionResult,
Expand Down
166 changes: 114 additions & 52 deletions harness/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -447,19 +447,22 @@ pub mod file;
pub mod fuzz;
pub mod program;
pub mod sysvar;
pub mod vm;

// Re-export result module from mollusk-svm-result crate
pub use mollusk_svm_result as result;
#[cfg(any(feature = "fuzz", feature = "fuzz-fd"))]
use mollusk_svm_result::Compare;
#[cfg(feature = "precompiles")]
use solana_precompile_error::PrecompileError;
#[cfg(feature = "invocation-inspect-callback")]
use solana_transaction_context::InstructionAccount;
use {
crate::{
account_store::AccountStore, compile_accounts::CompiledAccounts, epoch_stake::EpochStake,
program::ProgramCache, sysvar::Sysvars,
account_store::AccountStore,
compile_accounts::CompiledAccounts,
epoch_stake::EpochStake,
program::ProgramCache,
sysvar::Sysvars,
vm::{agave::AgaveVM, SolanaVM, SolanaVMContext, SolanaVMInstruction, SolanaVMTrace},
},
agave_feature_set::FeatureSet,
mollusk_svm_error::error::{MolluskError, MolluskPanic},
Expand All @@ -468,13 +471,18 @@ use {
solana_compute_budget::compute_budget::ComputeBudget,
solana_hash::Hash,
solana_instruction::{AccountMeta, Instruction},
solana_program_runtime::invoke_context::{EnvironmentConfig, InvokeContext},
solana_program_runtime::invoke_context::EnvironmentConfig,
solana_pubkey::Pubkey,
solana_svm_callback::InvokeContextCallback,
solana_svm_log_collector::LogCollector,
solana_svm_timings::ExecuteTimings,
solana_transaction_context::TransactionContext,
std::{cell::RefCell, collections::HashSet, iter::once, rc::Rc},
std::{cell::RefCell, collections::HashSet, iter::once, marker::PhantomData, rc::Rc},
};
#[cfg(feature = "invocation-inspect-callback")]
use {
solana_program_runtime::invoke_context::InvokeContext,
solana_transaction_context::InstructionAccount,
};

pub(crate) const DEFAULT_LOADER_KEY: Pubkey = solana_sdk_ids::bpf_loader_upgradeable::id();
Expand All @@ -483,7 +491,7 @@ pub(crate) const DEFAULT_LOADER_KEY: Pubkey = solana_sdk_ids::bpf_loader_upgrade
///
/// All fields can be manipulated through a handful of helper methods, but
/// users can also directly access and modify them if they desire more control.
pub struct Mollusk {
pub struct Mollusk<VM: SolanaVM = AgaveVM> {
pub config: Config,
pub compute_budget: ComputeBudget,
pub epoch_stake: EpochStake,
Expand All @@ -492,6 +500,8 @@ pub struct Mollusk {
pub program_cache: ProgramCache,
pub sysvars: Sysvars,

vm: PhantomData<VM>,

/// The callback which can be used to inspect invoke_context
/// and extract low-level information such as bpf traces, transaction
/// context, detailed timings, etc.
Expand Down Expand Up @@ -531,7 +541,24 @@ impl InvocationInspectCallback for EmptyInvocationInspectCallback {
fn after_invocation(&self, _: &InvokeContext) {}
}

impl Default for Mollusk {
// Fields from `Mollusk`, minus the generic VM.
struct MolluskFields {
config: Config,
compute_budget: ComputeBudget,
epoch_stake: EpochStake,
feature_set: FeatureSet,
logger: Option<Rc<RefCell<LogCollector>>>,
program_cache: ProgramCache,
sysvars: Sysvars,

#[cfg(feature = "invocation-inspect-callback")]
invocation_inspect_callback: Box<dyn InvocationInspectCallback>,

#[cfg(feature = "fuzz-fd")]
slot: u64,
}

impl Default for MolluskFields {
fn default() -> Self {
#[rustfmt::skip]
solana_logger::setup_with_default(
Expand Down Expand Up @@ -572,7 +599,35 @@ impl Default for Mollusk {
}
}

impl CheckContext for Mollusk {
impl MolluskFields {
fn vend<VM: SolanaVM>(self) -> Mollusk<VM> {
Mollusk {
config: self.config,
compute_budget: self.compute_budget,
epoch_stake: self.epoch_stake,
feature_set: self.feature_set,
logger: self.logger,
program_cache: self.program_cache,
sysvars: self.sysvars,

vm: PhantomData,

#[cfg(feature = "invocation-inspect-callback")]
invocation_inspect_callback: self.invocation_inspect_callback,

#[cfg(feature = "fuzz-fd")]
slot: self.slot,
}
}
}

impl Default for Mollusk {
fn default() -> Self {
MolluskFields::default().vend()
}
}

impl<VM: SolanaVM> CheckContext for Mollusk<VM> {
fn is_rent_exempt(&self, lamports: u64, space: usize, owner: Pubkey) -> bool {
owner.eq(&Pubkey::default()) && lamports == 0
|| self.sysvars.rent.is_exempt(lamports, space)
Expand Down Expand Up @@ -649,7 +704,29 @@ impl Mollusk {
/// - The directory specified by the `SBF_OUT_DIR` environment variable
/// - The current working directory
pub fn new(program_id: &Pubkey, program_name: &str) -> Self {
let mut mollusk = Self::default();
let mut mollusk = MolluskFields::default().vend();
mollusk.add_program(program_id, program_name, &DEFAULT_LOADER_KEY);
mollusk
}
}

impl<VM: SolanaVM> Mollusk<VM> {
/// Create a new Mollusk instance with a custom VM type.
///
/// Attempts to load the program's ELF file from the default search paths.
/// Once loaded, adds the program to the program cache and returns the
/// newly created Mollusk instance.
///
/// # Default Search Paths
///
/// The following locations are checked in order:
///
/// - `tests/fixtures`
/// - The directory specified by the `BPF_OUT_DIR` environment variable
/// - The directory specified by the `SBF_OUT_DIR` environment variable
/// - The current working directory
pub fn new_with_vm(program_id: &Pubkey, program_name: &str) -> Self {
let mut mollusk = MolluskFields::default().vend();
mollusk.add_program(program_id, program_name, &DEFAULT_LOADER_KEY);
mollusk
}
Expand Down Expand Up @@ -721,54 +798,39 @@ impl Mollusk {
};
let runtime_features = self.feature_set.runtime_features();
let sysvar_cache = self.sysvars.setup_sysvar_cache(accounts);
let mut invoke_context = InvokeContext::new(
&mut transaction_context,
&mut program_cache,
EnvironmentConfig::new(

let context = SolanaVMContext {
transaction_context: &mut transaction_context,
program_cache: &mut program_cache,
compute_budget: self.compute_budget,
environment_config: EnvironmentConfig::new(
Hash::default(),
/* blockhash_lamports_per_signature */ 5000, // The default value
&callback,
&runtime_features,
&sysvar_cache,
),
self.logger.clone(),
self.compute_budget.to_budget(),
self.compute_budget.to_cost(),
);

// Configure the next instruction frame for this invocation.
invoke_context
.transaction_context
.configure_next_instruction_for_tests(
program_id_index,
instruction_accounts.clone(),
&instruction.data,
)
.expect("failed to configure next instruction");

#[cfg(feature = "invocation-inspect-callback")]
self.invocation_inspect_callback.before_invocation(
&instruction.program_id,
&instruction.data,
&instruction_accounts,
&invoke_context,
);
};

let result = if invoke_context.is_precompile(&instruction.program_id) {
invoke_context.process_precompile(
&instruction.program_id,
&instruction.data,
std::iter::once(instruction.data.as_ref()),
)
} else {
invoke_context.process_instruction(&mut compute_units_consumed, &mut timings)
let instruction = SolanaVMInstruction {
program_id_index,
accounts: instruction_accounts,
data: &instruction.data,
};

#[cfg(feature = "invocation-inspect-callback")]
self.invocation_inspect_callback
.after_invocation(&invoke_context);
let trace = SolanaVMTrace {
compute_units_consumed: &mut compute_units_consumed,
execute_timings: &mut timings,
log_collector: self.logger.clone(),
};

result
VM::process_instruction(
context,
instruction,
trace,
#[cfg(feature = "invocation-inspect-callback")]
self.invocation_inspect_callback.as_ref(),
)
};

let return_data = transaction_context.get_return_data().1.to_vec();
Expand Down Expand Up @@ -1163,7 +1225,7 @@ impl Mollusk {
/// instruction executions, starting with the provided account store.
///
/// See [`MolluskContext`] for more details on how to use it.
pub fn with_context<AS: AccountStore>(self, mut account_store: AS) -> MolluskContext<AS> {
pub fn with_context<AS: AccountStore>(self, mut account_store: AS) -> MolluskContext<AS, VM> {
// For convenience, load all program accounts into the account store,
// but only if they don't exist.
self.program_cache
Expand Down Expand Up @@ -1199,13 +1261,13 @@ impl Mollusk {
/// management and a streamlined interface. Namely, the input `accounts` slice
/// is no longer required, and the returned result does not contain a
/// `resulting_accounts` field.
pub struct MolluskContext<AS: AccountStore> {
pub mollusk: Mollusk,
pub struct MolluskContext<AS: AccountStore, VM: SolanaVM> {
pub mollusk: Mollusk<VM>,
pub account_store: Rc<RefCell<AS>>,
pub hydrate_store: bool,
}

impl<AS: AccountStore> MolluskContext<AS> {
impl<AS: AccountStore, VM: SolanaVM> MolluskContext<AS, VM> {
fn load_accounts_for_instructions<'a>(
&self,
instructions: impl Iterator<Item = &'a Instruction>,
Expand Down
69 changes: 69 additions & 0 deletions harness/src/vm/agave.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//! The SBPF virtual machine used in Anza's Agave validator.
#[cfg(feature = "invocation-inspect-callback")]
use crate::InvocationInspectCallback;
use {
super::{SolanaVM, SolanaVMContext, SolanaVMInstruction, SolanaVMTrace},
solana_instruction_error::InstructionError,
solana_program_runtime::invoke_context::InvokeContext,
};

/// The SBPF virtual machine used in Anza's Agave validator.
pub struct AgaveVM {}

impl SolanaVM for AgaveVM {
fn process_instruction(
context: SolanaVMContext,
instruction: SolanaVMInstruction,
trace: SolanaVMTrace,
#[cfg(feature = "invocation-inspect-callback")]
invocation_inspect_callback: &dyn InvocationInspectCallback,
) -> Result<(), InstructionError> {
let mut invoke_context = InvokeContext::new(
context.transaction_context,
context.program_cache,
context.environment_config,
trace.log_collector,
context.compute_budget.to_budget(),
context.compute_budget.to_cost(),
);

// Configure the next instruction frame for this invocation.
invoke_context
.transaction_context
.configure_next_instruction_for_tests(
instruction.program_id_index,
instruction.accounts.clone(),
instruction.data,
)
.expect("failed to configure next instruction");

let program_id = invoke_context
.transaction_context
.get_key_of_account_at_index(instruction.program_id_index)
.cloned()?;

#[cfg(feature = "invocation-inspect-callback")]
invocation_inspect_callback.before_invocation(
&program_id,
instruction.data,
&instruction.accounts,
&invoke_context,
);

let result = if invoke_context.is_precompile(&program_id) {
invoke_context.process_precompile(
&program_id,
instruction.data,
std::iter::once(instruction.data),
)
} else {
invoke_context.process_instruction(trace.compute_units_consumed, trace.execute_timings)
};

#[cfg(feature = "invocation-inspect-callback")]
invocation_inspect_callback.after_invocation(&invoke_context);

result
}
}
Loading