use std::{marker::PhantomData, sync::Arc};
use agave_feature_set::FeatureSet;
use solana_compute_budget::compute_budget::ComputeBudget;
use solana_native_token::LAMPORTS_PER_SOL;
use solana_program_runtime::{
invoke_context::InvokeContext, solana_sbpf::program::BuiltinFunction,
};
use crate::{
AccountSource, CustomSyscallRegistration, HPSVM, Inspector, error::HPSVMError,
inspector::NoopInspector,
};
mod private {
pub trait Sealed {}
}
#[doc(hidden)]
pub struct FeatureConfigOpen;
#[doc(hidden)]
pub struct FeatureConfigSealed;
impl private::Sealed for FeatureConfigOpen {}
impl private::Sealed for FeatureConfigSealed {}
pub trait FeatureConfigState: private::Sealed {}
impl FeatureConfigState for FeatureConfigOpen {}
impl FeatureConfigState for FeatureConfigSealed {}
enum LogBytesLimitPlan {
Inherit,
Explicit(Option<usize>),
}
struct BuildPlan {
feature_set: Option<FeatureSet>,
compute_budget: Option<ComputeBudget>,
sigverify: Option<bool>,
blockhash_check: Option<bool>,
lamports: Option<u64>,
include_sysvars: bool,
include_feature_accounts: bool,
include_builtins: bool,
include_spl_programs: bool,
include_default_programs: bool,
#[cfg(feature = "precompiles")]
include_precompiles: bool,
transaction_history: Option<usize>,
account_source: Option<Arc<dyn AccountSource>>,
log_bytes_limit: LogBytesLimitPlan,
inspector: Arc<dyn Inspector>,
custom_syscalls: Vec<CustomSyscallRegistration>,
enable_register_tracing: bool,
}
impl BuildPlan {
fn new() -> Self {
Self {
feature_set: None,
compute_budget: None,
sigverify: None,
blockhash_check: None,
lamports: None,
include_sysvars: false,
include_feature_accounts: false,
include_builtins: false,
include_spl_programs: false,
include_default_programs: false,
#[cfg(feature = "precompiles")]
include_precompiles: false,
transaction_history: None,
account_source: None,
log_bytes_limit: LogBytesLimitPlan::Inherit,
inspector: Arc::new(NoopInspector),
custom_syscalls: Vec::new(),
enable_register_tracing: HPSVM::default_register_tracing_enabled(),
}
}
fn apply_program_test_defaults(&mut self) {
self.lamports.get_or_insert(1_000_000u64.wrapping_mul(LAMPORTS_PER_SOL));
self.include_sysvars = true;
self.include_feature_accounts = true;
self.include_builtins = true;
self.include_default_programs = true;
#[cfg(feature = "precompiles")]
{
self.include_precompiles = true;
}
self.sigverify.get_or_insert(true);
self.blockhash_check.get_or_insert(true);
}
}
#[must_use = "builders do nothing unless you call build()"]
pub struct HpsvmBuilder<State = FeatureConfigOpen> {
plan: BuildPlan,
state: PhantomData<State>,
}
impl Default for HpsvmBuilder<FeatureConfigOpen> {
fn default() -> Self {
Self::new()
}
}
impl HpsvmBuilder<FeatureConfigOpen> {
pub fn new() -> Self {
Self { plan: BuildPlan::new(), state: PhantomData }
}
pub fn with_feature_set(mut self, feature_set: FeatureSet) -> Self {
self.plan.feature_set = Some(feature_set);
self
}
pub fn with_feature_accounts(mut self) -> HpsvmBuilder<FeatureConfigSealed> {
self.plan.include_feature_accounts = true;
self.seal()
}
pub fn with_builtins(mut self) -> HpsvmBuilder<FeatureConfigSealed> {
self.plan.include_builtins = true;
self.seal()
}
pub fn with_default_programs(mut self) -> HpsvmBuilder<FeatureConfigSealed> {
self.plan.include_default_programs = true;
self.seal()
}
pub fn with_spl_programs(mut self) -> HpsvmBuilder<FeatureConfigSealed> {
self.plan.include_spl_programs = true;
self.seal()
}
#[cfg(feature = "precompiles")]
pub fn with_precompiles(mut self) -> HpsvmBuilder<FeatureConfigSealed> {
self.plan.include_precompiles = true;
self.seal()
}
pub fn with_program_test_defaults(mut self) -> HpsvmBuilder<FeatureConfigSealed> {
self.plan.feature_set.get_or_insert_with(FeatureSet::all_enabled);
self.plan.apply_program_test_defaults();
self.seal()
}
fn seal(self) -> HpsvmBuilder<FeatureConfigSealed> {
HpsvmBuilder { plan: self.plan, state: PhantomData }
}
}
impl HpsvmBuilder<FeatureConfigSealed> {
pub fn with_feature_accounts(mut self) -> Self {
self.plan.include_feature_accounts = true;
self
}
pub fn with_builtins(mut self) -> Self {
self.plan.include_builtins = true;
self
}
pub fn with_default_programs(mut self) -> Self {
self.plan.include_default_programs = true;
self
}
pub fn with_spl_programs(mut self) -> Self {
self.plan.include_spl_programs = true;
self
}
#[cfg(feature = "precompiles")]
pub fn with_precompiles(mut self) -> Self {
self.plan.include_precompiles = true;
self
}
pub fn with_program_test_defaults(mut self) -> Self {
self.plan.apply_program_test_defaults();
self
}
}
impl<State: FeatureConfigState> HpsvmBuilder<State> {
pub fn with_inspector<I: Inspector + 'static>(mut self, inspector: I) -> Self {
self.plan.inspector = Arc::new(inspector);
self
}
pub fn with_compute_budget(mut self, compute_budget: ComputeBudget) -> Self {
self.plan.compute_budget = Some(compute_budget);
self
}
pub fn with_sigverify(mut self, sigverify: bool) -> Self {
self.plan.sigverify = Some(sigverify);
self
}
pub fn with_blockhash_check(mut self, blockhash_check: bool) -> Self {
self.plan.blockhash_check = Some(blockhash_check);
self
}
pub fn with_lamports(mut self, lamports: u64) -> Self {
self.plan.lamports = Some(lamports);
self
}
pub fn with_sysvars(mut self) -> Self {
self.plan.include_sysvars = true;
self
}
pub fn with_transaction_history(mut self, capacity: usize) -> Self {
self.plan.transaction_history = Some(capacity);
self
}
pub fn with_account_source(mut self, source: impl AccountSource + 'static) -> Self {
self.plan.account_source = Some(Arc::new(source));
self
}
pub fn with_log_bytes_limit(mut self, limit: Option<usize>) -> Self {
self.plan.log_bytes_limit = LogBytesLimitPlan::Explicit(limit);
self
}
pub fn with_register_tracing(mut self, enable_register_tracing: bool) -> Self {
self.plan.enable_register_tracing = enable_register_tracing;
self
}
pub fn with_custom_syscall(
mut self,
name: &str,
syscall: BuiltinFunction<InvokeContext<'static, 'static>>,
) -> Self {
self.plan
.custom_syscalls
.push(CustomSyscallRegistration { name: name.to_owned(), function: syscall });
self
}
pub fn build(self) -> Result<HPSVM, HPSVMError> {
let BuildPlan {
feature_set,
compute_budget,
sigverify,
blockhash_check,
lamports,
include_sysvars,
include_feature_accounts,
include_builtins,
include_spl_programs,
include_default_programs,
#[cfg(feature = "precompiles")]
include_precompiles,
transaction_history,
account_source,
log_bytes_limit,
inspector,
custom_syscalls,
enable_register_tracing,
} = self.plan;
#[cfg(feature = "precompiles")]
let needs_precompiles = include_precompiles;
#[cfg(not(feature = "precompiles"))]
let needs_precompiles = false;
let needs_sysvars = include_builtins ||
include_default_programs ||
include_spl_programs ||
needs_precompiles ||
sigverify == Some(true) ||
blockhash_check == Some(true);
if needs_sysvars && !include_sysvars {
return Err(HPSVMError::MissingRuntimeComponent { component: "sysvars" });
}
let mut svm = HPSVM::new_inner(enable_register_tracing);
if let Some(feature_set) = feature_set {
svm.set_feature_set(feature_set)?;
}
if let Some(compute_budget) = compute_budget {
svm.set_compute_budget(compute_budget);
}
if let Some(transaction_history) = transaction_history {
svm.set_transaction_history(transaction_history);
}
if let Some(account_source) = account_source {
svm.accounts.set_account_source(account_source);
svm.invalidate_execution_outcomes();
}
if let LogBytesLimitPlan::Explicit(limit) = log_bytes_limit {
svm.set_log_bytes_limit(limit);
}
let needs_early_runtime_refresh = !custom_syscalls.is_empty() && !include_builtins;
for registration in custom_syscalls {
svm.runtime_registry.register_custom_syscall(registration);
}
if needs_early_runtime_refresh {
svm.try_refresh_runtime_environments()?;
svm.accounts.rebuild_program_cache().map_err(HPSVMError::from)?;
svm.invalidate_execution_outcomes();
}
if include_builtins {
svm.set_builtins();
}
if let Some(lamports) = lamports {
svm.set_lamports(lamports);
}
if include_sysvars {
svm.set_sysvars();
}
if include_feature_accounts {
svm.set_feature_accounts();
}
if include_default_programs {
svm.set_default_programs();
} else if include_spl_programs {
svm.set_spl_programs();
}
#[cfg(feature = "precompiles")]
if include_precompiles {
svm.set_precompiles();
}
if let Some(sigverify) = sigverify {
svm.set_sigverify(sigverify);
}
if let Some(blockhash_check) = blockhash_check {
svm.set_blockhash_check(blockhash_check);
}
svm.inspector = inspector;
svm.invalidate_execution_outcomes();
Ok(svm)
}
}