use crate::{
error::VmError, security::SecurityLimits, types::FuelAmount, vm::runtime::default_runtime,
Context, Error, Instance, PreparedModule, Runtime,
};
use std::sync::Arc;
use wasmtime::Store;
pub struct Builder {
fuel: Option<FuelAmount>,
bytes: Vec<u8>,
context: Option<Context>,
security_limits: SecurityLimits,
runtime: Option<Arc<Runtime>>,
prepared_module: Option<PreparedModule>,
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl Builder {
#[must_use]
pub fn new() -> Self {
Self {
fuel: Some(FuelAmount::default()),
bytes: Vec::default(),
context: None,
security_limits: SecurityLimits::PRODUCTION,
runtime: None,
prepared_module: None,
}
}
#[must_use]
pub fn new_strict() -> Self {
Self {
fuel: Some(FuelAmount::new(100_000)),
bytes: Vec::default(),
context: None,
security_limits: SecurityLimits::STRICT,
runtime: None,
prepared_module: None,
}
}
#[must_use]
pub fn new_dev() -> Self {
Self {
fuel: Some(FuelAmount::new(10 * FuelAmount::DEFAULT)),
bytes: Vec::default(),
context: None,
security_limits: SecurityLimits::DEVELOPMENT,
runtime: None,
prepared_module: None,
}
}
#[must_use]
pub const fn with_security_limits(mut self, limits: SecurityLimits) -> Self {
self.security_limits = limits;
if self.fuel.is_none() {
self.fuel = Some(limits.max_fuel);
}
self
}
#[must_use]
pub const fn with_fuel(mut self, fuel: FuelAmount) -> Self {
self.fuel = Some(fuel);
self
}
pub fn with_bytes(mut self, bytes: impl AsRef<[u8]>) -> Self {
self.bytes = bytes.as_ref().to_vec();
self
}
#[must_use]
pub fn with_context(mut self, context: Context) -> Self {
self.context = Some(context);
self
}
pub fn with_runtime(mut self, runtime: Arc<Runtime>) -> Self {
self.runtime = Some(runtime);
self
}
#[must_use]
pub fn with_prepared_module(mut self, prepared: PreparedModule) -> Self {
self.runtime = Some(Arc::clone(&prepared.runtime));
self.prepared_module = Some(prepared);
self
}
pub fn try_build(self) -> Result<Instance, Error> {
let fuel = match self.fuel {
Some(f) => {
if f.as_u64() > self.security_limits.max_fuel.as_u64() {
return Err(Error::custom(&format!(
"fuel {} exceeds security limit {}",
f, self.security_limits.max_fuel
)));
}
f
}
None => {
self.security_limits.max_fuel
}
};
let (runtime, module) = if let Some(prepared) = self.prepared_module {
if self
.runtime
.as_ref()
.is_some_and(|runtime| !Arc::ptr_eq(runtime, &prepared.runtime))
{
return Err(Error::custom(
&"prepared module belongs to a different runtime",
));
}
(prepared.runtime, prepared.module)
} else {
let runtime = match self.runtime {
Some(runtime) => runtime,
None => default_runtime()?,
};
let module = runtime.compile(&self.bytes)?;
(runtime, module)
};
let context = match self.context {
Some(ctx) => ctx,
None => {
return Err(VmError::MissingContext {
context: "Builder requires context to be set via with_context()".to_string(),
}
.into())
}
};
let mut store = Store::new(runtime.engine(), context);
store
.set_fuel(fuel.as_u64())
.map_err(Error::from_wasmtime)?;
store.limiter(|state| &mut state.limiter);
Ok(Instance {
linker: runtime.linker(),
module: module.as_ref().clone(),
store,
})
}
}