use vanta_core::{Area, VtaError, VtaResult};
use wasmtime::{Config, Engine, Instance, Module, Store, StoreLimitsBuilder};
const MAX_MEMORY_BYTES: usize = 256 * 1024 * 1024;
pub struct Sandbox {
engine: Engine,
}
impl Sandbox {
pub fn new() -> VtaResult<Sandbox> {
let mut config = Config::new();
config.consume_fuel(true);
let engine = Engine::new(&config).map_err(|e| err(format!("engine: {e}")))?;
Ok(Sandbox { engine })
}
pub fn run_i32(&self, wasm: &[u8], func: &str, arg: i32, fuel: u64) -> VtaResult<i32> {
let module = Module::new(&self.engine, wasm).map_err(|e| err(format!("compile: {e}")))?;
let limits = StoreLimitsBuilder::new()
.memory_size(MAX_MEMORY_BYTES)
.build();
let mut store = Store::new(&self.engine, limits);
store.limiter(|state| state as &mut dyn wasmtime::ResourceLimiter);
store
.set_fuel(fuel)
.map_err(|e| err(format!("set fuel: {e}")))?;
let instance = Instance::new(&mut store, &module, &[])
.map_err(|e| err(format!("instantiate (no capabilities granted): {e}")))?;
let typed = instance
.get_typed_func::<i32, i32>(&mut store, func)
.map_err(|e| err(format!("export `{func}`: {e}")))?;
typed
.call(&mut store, arg)
.map_err(|e| err(format!("guest trap: {e}")))
}
}
fn err(msg: String) -> VtaError {
VtaError::new(Area::Prov, 1, msg)
}
#[cfg(test)]
mod tests {
use super::*;
const DOUBLE: &str = r#"(module
(func (export "double") (param i32) (result i32)
local.get 0
i32.const 2
i32.mul))"#;
const SPIN: &str = r#"(module
(func (export "spin") (param i32) (result i32)
(loop (br 0))
i32.const 0))"#;
const NEEDS_IMPORT: &str = r#"(module
(import "env" "secret" (func $secret (result i32)))
(func (export "go") (param i32) (result i32) (call $secret)))"#;
#[test]
fn runs_pure_compute() {
let wasm = wat::parse_str(DOUBLE).unwrap();
let sb = Sandbox::new().unwrap();
assert_eq!(sb.run_i32(&wasm, "double", 21, 1_000_000).unwrap(), 42);
}
#[test]
fn fuel_exhaustion_traps_not_hangs() {
let wasm = wat::parse_str(SPIN).unwrap();
let sb = Sandbox::new().unwrap();
let err = sb.run_i32(&wasm, "spin", 0, 10_000).unwrap_err();
assert_eq!(err.area, Area::Prov); }
#[test]
fn imports_are_denied() {
let wasm = wat::parse_str(NEEDS_IMPORT).unwrap();
let sb = Sandbox::new().unwrap();
assert!(sb.run_i32(&wasm, "go", 0, 1_000_000).is_err());
}
}