entropy_programs_runtime/
lib.rs1use thiserror::Error;
4use wasmtime::{
5 component::{bindgen, Component, Linker},
6 Config as WasmtimeConfig, Engine, Result, Store,
7};
8
9mod bindgen {
11 use super::bindgen;
12
13 bindgen!({
14 world: "program",
15 });
16}
17pub use bindgen::{Error as ProgramError, Program, SignatureRequest};
18
19#[derive(Debug, Error)]
21pub enum RuntimeError {
22 #[error("Bytecode length is zero")]
24 EmptyBytecode,
25 #[error("Invalid bytecode")]
27 InvalidBytecode,
28 #[error("Runtime error: {0}")]
30 Runtime(ProgramError),
31 #[error("Out of fuel")]
33 OutOfFuel,
34}
35
36pub struct Config {
38 pub fuel: u64,
40}
41
42impl Default for Config {
43 fn default() -> Self {
44 Self { fuel: 10_000 }
45 }
46}
47
48pub struct Runtime {
50 engine: Engine,
51 linker: Linker<()>,
52 store: Store<()>,
53}
54
55impl Default for Runtime {
56 fn default() -> Self {
57 Self::new(Config::default())
58 }
59}
60
61impl Runtime {
62 pub fn new(config: Config) -> Self {
63 let mut wasmtime_config = WasmtimeConfig::new();
64 wasmtime_config
65 .wasm_component_model(true)
66 .consume_fuel(true);
67
68 let engine = Engine::new(&wasmtime_config).unwrap();
69 let linker = Linker::new(&engine);
70 let mut store = Store::new(&engine, ());
71
72 store.add_fuel(config.fuel).unwrap();
73 Self {
74 engine,
75 linker,
76 store,
77 }
78 }
79}
80
81impl Runtime {
82 pub fn evaluate(
84 &mut self,
85 program: &[u8],
86 signature_request: &SignatureRequest,
87 config: Option<&[u8]>,
88 oracle_data: Option<&[Vec<u8>]>,
89 ) -> Result<(), RuntimeError> {
90 if program.len() == 0 {
91 return Err(RuntimeError::EmptyBytecode);
92 }
93
94 let component = Component::from_binary(&self.engine, program)
95 .map_err(|_| RuntimeError::InvalidBytecode)?;
96 let (bindings, _) = Program::instantiate(&mut self.store, &component, &self.linker)
97 .map_err(|_| RuntimeError::InvalidBytecode)?;
98
99 bindings
100 .call_evaluate(&mut self.store, signature_request, config, oracle_data)
101 .map_err(|_| RuntimeError::OutOfFuel)?
102 .map_err(RuntimeError::Runtime)
103 }
104
105 pub fn custom_hash(
107 &mut self,
108 program: &[u8],
109 message: &[u8],
110 ) -> Result<[u8; 32], RuntimeError> {
111 if program.len() == 0 {
112 return Err(RuntimeError::EmptyBytecode);
113 }
114
115 let component = Component::from_binary(&self.engine, program)
116 .map_err(|_| RuntimeError::InvalidBytecode)?;
117 let (bindings, _) = Program::instantiate(&mut self.store, &component, &self.linker)
118 .map_err(|_| RuntimeError::InvalidBytecode)?;
119
120 let hash_as_vec = bindings
121 .call_custom_hash(&mut self.store, message)
122 .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())))?;
123 if hash_as_vec.len() != 32 {
124 return Err(RuntimeError::Runtime(
125 ProgramError::InvalidSignatureRequest(format!(
126 "`custom-hash` must returns a Vec<u8> of length 32, not {}.",
127 hash_as_vec.len()
128 )),
129 ));
130 }
131
132 let mut hash = [0u8; 32];
133 hash.copy_from_slice(&hash_as_vec);
134 Ok(hash)
135 }
136}