use thiserror::Error;
use wasmtime::{
component::{bindgen, Component, Linker},
Config as WasmtimeConfig, Engine, Result, Store,
};
mod bindgen {
use super::bindgen;
bindgen!({
world: "program",
});
}
pub use bindgen::{Error as ProgramError, Program, SignatureRequest};
#[derive(Debug, Error)]
pub enum RuntimeError {
#[error("Bytecode length is zero")]
EmptyBytecode,
#[error("Invalid bytecode")]
InvalidBytecode,
#[error("Runtime error: {0}")]
Runtime(ProgramError),
#[error("Out of fuel")]
OutOfFuel,
}
pub struct Config {
pub fuel: u64,
}
impl Default for Config {
fn default() -> Self {
Self { fuel: 10_000 }
}
}
pub struct Runtime {
engine: Engine,
linker: Linker<()>,
store: Store<()>,
}
impl Default for Runtime {
fn default() -> Self {
Self::new(Config::default())
}
}
impl Runtime {
pub fn new(config: Config) -> Self {
let mut wasmtime_config = WasmtimeConfig::new();
wasmtime_config
.wasm_component_model(true)
.consume_fuel(true);
let engine = Engine::new(&wasmtime_config).unwrap();
let linker = Linker::new(&engine);
let mut store = Store::new(&engine, ());
store.add_fuel(config.fuel).unwrap();
Self {
engine,
linker,
store,
}
}
}
impl Runtime {
pub fn evaluate(
&mut self,
program: &[u8],
signature_request: &SignatureRequest,
config: Option<&[u8]>,
oracle_data: Option<&[u8]>,
) -> Result<(), RuntimeError> {
if program.len() == 0 {
return Err(RuntimeError::EmptyBytecode);
}
let component = Component::from_binary(&self.engine, program)
.map_err(|_| RuntimeError::InvalidBytecode)?;
let (bindings, _) = Program::instantiate(&mut self.store, &component, &self.linker)
.map_err(|_| RuntimeError::InvalidBytecode)?;
bindings
.call_evaluate(&mut self.store, signature_request, config, oracle_data)
.map_err(|_| RuntimeError::OutOfFuel)?
.map_err(RuntimeError::Runtime)
}
pub fn custom_hash(
&mut self,
program: &[u8],
message: &[u8],
) -> Result<[u8; 32], RuntimeError> {
if program.len() == 0 {
return Err(RuntimeError::EmptyBytecode);
}
let component = Component::from_binary(&self.engine, program)
.map_err(|_| RuntimeError::InvalidBytecode)?;
let (bindings, _) = Program::instantiate(&mut self.store, &component, &self.linker)
.map_err(|_| RuntimeError::InvalidBytecode)?;
let hash_as_vec = bindings
.call_custom_hash(&mut self.store, message)
.unwrap().ok_or(RuntimeError::Runtime(ProgramError::InvalidSignatureRequest("`custom-hash` returns `None`. Implement the hash function in your program, or select a predefined `hash` in your signature request.".to_string())))?;
if hash_as_vec.len() != 32 {
return Err(RuntimeError::Runtime(
ProgramError::InvalidSignatureRequest(format!(
"`custom-hash` must returns a Vec<u8> of length 32, not {}.",
hash_as_vec.len()
)),
));
}
let mut hash = [0u8; 32];
hash.copy_from_slice(&hash_as_vec);
Ok(hash)
}
}