calimero_runtime/
lib.rs

1use calimero_node_primitives::client::NodeClient;
2use calimero_primitives::context::ContextId;
3use calimero_primitives::identity::PublicKey;
4use tracing::{debug, error, info};
5use wasmer::{CompileError, DeserializeError, Instance, NativeEngineExt, SerializeError, Store};
6
7mod constants;
8mod constraint;
9pub mod errors;
10pub mod logic;
11mod memory;
12pub mod store;
13
14pub use constraint::Constraint;
15use errors::{FunctionCallError, VMRuntimeError};
16use logic::{Outcome, VMContext, VMLimits, VMLogic, VMLogicError};
17use memory::WasmerTunables;
18use store::Storage;
19
20pub type RuntimeResult<T, E = VMRuntimeError> = Result<T, E>;
21
22#[derive(Clone, Debug)]
23pub struct Engine {
24    limits: VMLimits,
25    engine: wasmer::Engine,
26}
27
28impl Default for Engine {
29    fn default() -> Self {
30        let limits = VMLimits::default();
31
32        let engine = wasmer::Engine::default();
33
34        Self::new(engine, limits)
35    }
36}
37
38impl Engine {
39    pub fn new(mut engine: wasmer::Engine, limits: VMLimits) -> Self {
40        engine.set_tunables(WasmerTunables::new(&limits));
41
42        Self { limits, engine }
43    }
44
45    pub fn headless() -> Self {
46        let limits = VMLimits::default();
47
48        let engine = wasmer::Engine::headless();
49
50        Self::new(engine, limits)
51    }
52
53    pub fn compile(&self, bytes: &[u8]) -> Result<Module, CompileError> {
54        // todo! apply a prepare step
55        // todo! - parse the wasm blob, validate and apply transformations
56        // todo!   - validations:
57        // todo!     - there is no memory import
58        // todo!     - there is no _start function
59        // todo!   - transformations:
60        // todo!     - remove memory export
61        // todo!     - remove memory section
62        // todo! cache the compiled module in storage for later
63
64        let module = wasmer::Module::new(&self.engine, bytes)?;
65
66        Ok(Module {
67            limits: self.limits.clone(),
68            engine: self.engine.clone(),
69            module,
70        })
71    }
72
73    pub unsafe fn from_precompiled(&self, bytes: &[u8]) -> Result<Module, DeserializeError> {
74        let module = wasmer::Module::deserialize(&self.engine, bytes)?;
75
76        Ok(Module {
77            limits: self.limits.clone(),
78            engine: self.engine.clone(),
79            module,
80        })
81    }
82}
83
84#[derive(Debug)]
85pub struct Module {
86    limits: VMLimits,
87    engine: wasmer::Engine,
88    module: wasmer::Module,
89}
90
91impl Module {
92    pub fn to_bytes(&self) -> Result<Box<[u8]>, SerializeError> {
93        let bytes = self.module.serialize()?;
94
95        Ok(Vec::into_boxed_slice(bytes.into()))
96    }
97
98    pub fn run(
99        &self,
100        context: ContextId,
101        executor: PublicKey,
102        method: &str,
103        input: &[u8],
104        storage: &mut dyn Storage,
105        node_client: Option<NodeClient>,
106    ) -> RuntimeResult<Outcome> {
107        let context_id = context;
108        info!(%context_id, method, "Running WASM method");
109        debug!(%context_id, method, input_len = input.len(), "WASM execution input");
110
111        let context = VMContext::new(input.into(), *context_id, *executor);
112
113        let mut logic = VMLogic::new(storage, context, &self.limits, node_client);
114
115        let mut store = Store::new(self.engine.clone());
116
117        let imports = logic.imports(&mut store);
118
119        let instance = match Instance::new(&mut store, &self.module, &imports) {
120            Ok(instance) => instance,
121            Err(err) => {
122                error!(%context_id, method, error=?err, "Failed to instantiate WASM module");
123                return Ok(logic.finish(Some(err.into())));
124            }
125        };
126
127        let _ = match instance.exports.get_memory("memory") {
128            Ok(memory) => logic.with_memory(memory.clone()),
129            // todo! test memory returns MethodNotFound
130            Err(err) => {
131                error!(%context_id, method, error=?err, "Failed to get WASM memory");
132                return Ok(logic.finish(Some(err.into())));
133            }
134        };
135
136        let function = match instance.exports.get_function(method) {
137            Ok(function) => function,
138            Err(err) => {
139                error!(%context_id, method, error=?err, "Method not found in WASM module");
140                return Ok(logic.finish(Some(err.into())));
141            }
142        };
143
144        let signature = function.ty(&store);
145
146        if !(signature.params().is_empty() && signature.results().is_empty()) {
147            error!(%context_id, method, "Invalid method signature");
148            return Ok(logic.finish(Some(FunctionCallError::MethodResolutionError(
149                errors::MethodResolutionError::InvalidSignature {
150                    name: method.to_owned(),
151                },
152            ))));
153        }
154
155        if let Err(err) = function.call(&mut store, &[]) {
156            error!(%context_id, method, error=?err, "WASM method execution failed");
157            return match err.downcast::<VMLogicError>() {
158                Ok(err) => Ok(logic.finish(Some(err.try_into()?))),
159                Err(err) => Ok(logic.finish(Some(err.into()))),
160            };
161        }
162
163        let outcome = logic.finish(None);
164        info!(%context_id, method, "WASM method execution completed");
165        debug!(
166            %context_id,
167            method,
168            has_return = outcome.returns.is_ok(),
169            logs_count = outcome.logs.len(),
170            events_count = outcome.events.len(),
171            "WASM execution outcome"
172        );
173
174        Ok(outcome)
175    }
176}
177
178#[cfg(test)]
179mod integration_tests_package_usage {
180    use {eyre as _, owo_colors as _, rand as _};
181}