casper_executor_wasmer_backend/
lib.rs

1pub(crate) mod imports;
2pub(crate) mod middleware;
3
4use std::{
5    collections::BinaryHeap,
6    sync::{Arc, LazyLock, Weak},
7};
8
9use bytes::Bytes;
10use casper_executor_wasm_common::error::TrapCode;
11use casper_executor_wasm_host::context::Context;
12use casper_executor_wasm_interface::{
13    executor::Executor, Caller, Config, ExportError, GasUsage, InterfaceVersion, MeteringPoints,
14    VMError, VMResult, WasmInstance, WasmPreparationError,
15};
16use casper_storage::global_state::GlobalStateReader;
17use middleware::{
18    gas_metering,
19    gatekeeper::{Gatekeeper, GatekeeperConfig},
20};
21use regex::Regex;
22use wasmer::{
23    AsStoreMut, AsStoreRef, CompilerConfig, Engine, Function, FunctionEnv, FunctionEnvMut,
24    Instance, Memory, MemoryView, Module, RuntimeError, Store, StoreMut, Table, TypedFunction,
25};
26use wasmer_compiler_singlepass::Singlepass;
27use wasmer_middlewares::metering;
28
29fn from_wasmer_memory_access_error(error: wasmer::MemoryAccessError) -> VMError {
30    let trap_code = match error {
31        wasmer::MemoryAccessError::HeapOutOfBounds | wasmer::MemoryAccessError::Overflow => {
32            // As according to Wasm spec section `Memory Instructions` any access to memory that
33            // is out of bounds of the memory's current size is a trap. Reference: https://webassembly.github.io/spec/core/syntax/instructions.html#memory-instructions
34            TrapCode::MemoryOutOfBounds
35        }
36        wasmer::MemoryAccessError::NonUtf8String => {
37            // This can happen only when using wasmer's utf8 reading routines which we don't
38            // need.
39            unreachable!("NonUtf8String")
40        }
41        _ => {
42            // All errors are handled and converted to a trap code, but we have to add this as
43            // wasmer's errors are #[non_exhaustive]
44            unreachable!("Unexpected error: {error:?}")
45        }
46    };
47    VMError::Trap(trap_code)
48}
49
50fn from_wasmer_trap_code(value: wasmer_types::TrapCode) -> TrapCode {
51    match value {
52        wasmer_types::TrapCode::StackOverflow => TrapCode::StackOverflow,
53        wasmer_types::TrapCode::HeapAccessOutOfBounds => TrapCode::MemoryOutOfBounds,
54        wasmer_types::TrapCode::HeapMisaligned => {
55            unreachable!("Atomic operations are not supported")
56        }
57        wasmer_types::TrapCode::TableAccessOutOfBounds => TrapCode::TableAccessOutOfBounds,
58        wasmer_types::TrapCode::IndirectCallToNull => TrapCode::IndirectCallToNull,
59        wasmer_types::TrapCode::BadSignature => TrapCode::BadSignature,
60        wasmer_types::TrapCode::IntegerOverflow => TrapCode::IntegerOverflow,
61        wasmer_types::TrapCode::IntegerDivisionByZero => TrapCode::IntegerDivisionByZero,
62        wasmer_types::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
63        wasmer_types::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
64        wasmer_types::TrapCode::UnalignedAtomic => {
65            todo!("Atomic memory extension is not supported")
66        }
67    }
68}
69
70fn from_wasmer_export_error(error: wasmer::ExportError) -> VMError {
71    let export_error = match error {
72        wasmer::ExportError::IncompatibleType => ExportError::IncompatibleType,
73        wasmer::ExportError::Missing(export_name) => ExportError::Missing(export_name),
74    };
75    VMError::Export(export_error)
76}
77
78#[derive(Default)]
79pub struct WasmerEngine(());
80
81impl WasmerEngine {
82    pub fn new() -> Self {
83        Self::default()
84    }
85
86    pub fn instantiate<T: Into<Bytes>, S: GlobalStateReader + 'static, E: Executor + 'static>(
87        &self,
88        wasm_bytes: T,
89        context: Context<S, E>,
90        config: Config,
91    ) -> Result<impl WasmInstance<Context = Context<S, E>>, WasmPreparationError> {
92        WasmerInstance::from_wasm_bytes(wasm_bytes, context, config)
93    }
94}
95
96struct WasmerEnv<S: GlobalStateReader, E: Executor> {
97    context: Context<S, E>,
98    instance: Weak<Instance>,
99    bytecode: Bytes,
100    exported_runtime: Option<ExportedRuntime>,
101    interface_version: InterfaceVersion,
102}
103
104pub(crate) struct WasmerCaller<'a, S: GlobalStateReader, E: Executor> {
105    env: FunctionEnvMut<'a, WasmerEnv<S, E>>,
106}
107
108impl<S: GlobalStateReader + 'static, E: Executor + 'static> WasmerCaller<'_, S, E> {
109    fn with_memory<T>(&self, f: impl FnOnce(MemoryView<'_>) -> T) -> T {
110        let mem = &self.env.data().exported_runtime().memory;
111        let binding = self.env.as_store_ref();
112        let view = mem.view(&binding);
113        f(view)
114    }
115
116    fn with_instance<Ret>(&self, f: impl FnOnce(&Instance) -> Ret) -> Ret {
117        let instance = self.env.data().instance.upgrade().expect("Valid instance");
118        f(&instance)
119    }
120
121    fn with_store_and_instance<Ret>(&mut self, f: impl FnOnce(StoreMut, &Instance) -> Ret) -> Ret {
122        let (data, store) = self.env.data_and_store_mut();
123        let instance = data.instance.upgrade().expect("Valid instance");
124        f(store, &instance)
125    }
126
127    /// Returns the amount of gas used.
128    fn get_remaining_points(&mut self) -> MeteringPoints {
129        self.with_store_and_instance(|mut store, instance| {
130            let metering_points = metering::get_remaining_points(&mut store, instance);
131            match metering_points {
132                metering::MeteringPoints::Remaining(points) => MeteringPoints::Remaining(points),
133                metering::MeteringPoints::Exhausted => MeteringPoints::Exhausted,
134            }
135        })
136    }
137    /// Set the amount of gas used.
138    fn set_remaining_points(&mut self, new_value: u64) {
139        self.with_store_and_instance(|mut store, instance| {
140            metering::set_remaining_points(&mut store, instance, new_value);
141        })
142    }
143}
144
145impl<S: GlobalStateReader + 'static, E: Executor + 'static> Caller for WasmerCaller<'_, S, E> {
146    type Context = Context<S, E>;
147
148    fn memory_write(&self, offset: u32, data: &[u8]) -> Result<(), VMError> {
149        self.with_memory(|mem| mem.write(offset.into(), data))
150            .map_err(from_wasmer_memory_access_error)
151    }
152
153    fn context(&self) -> &Context<S, E> {
154        &self.env.data().context
155    }
156
157    fn context_mut(&mut self) -> &mut Context<S, E> {
158        &mut self.env.data_mut().context
159    }
160
161    fn memory_read_into(&self, offset: u32, output: &mut [u8]) -> Result<(), VMError> {
162        self.with_memory(|mem| mem.read(offset.into(), output))
163            .map_err(from_wasmer_memory_access_error)
164    }
165
166    fn alloc(&mut self, idx: u32, size: usize, ctx: u32) -> VMResult<u32> {
167        let _interface_version = self.env.data().interface_version;
168
169        let (data, mut store) = self.env.data_and_store_mut();
170        let value = data
171            .exported_runtime()
172            .exported_table
173            .as_ref()
174            .expect("should have table exported") // TODO: if theres no table then no function pointer is stored in the wasm blob -
175            // probably safe
176            .get(&mut store.as_store_mut(), idx)
177            .expect("has entry in the table"); // TODO: better error handling - pass 0 as nullptr?
178        let funcref = value.funcref().expect("is funcref");
179        let valid_funcref = funcref.as_ref().expect("valid funcref");
180        let alloc_callback: TypedFunction<(u32, u32), u32> = valid_funcref
181            .typed(&store)
182            .unwrap_or_else(|error| panic!("{error:?}"));
183        let ptr = alloc_callback
184            .call(&mut store.as_store_mut(), size.try_into().unwrap(), ctx)
185            .map_err(handle_wasmer_runtime_error)?;
186        Ok(ptr)
187    }
188
189    fn bytecode(&self) -> Bytes {
190        self.env.data().bytecode.clone()
191    }
192
193    /// Returns the amount of gas used.
194    #[inline]
195    fn gas_consumed(&mut self) -> MeteringPoints {
196        self.get_remaining_points()
197    }
198
199    /// Set the amount of gas used.
200    ///
201    /// This method will cause the VM engine to stop in case remaining gas points are depleted.
202    fn consume_gas(&mut self, amount: u64) -> VMResult<()> {
203        let gas_consumed = self.gas_consumed();
204        match gas_consumed {
205            MeteringPoints::Remaining(remaining_points) => {
206                let remaining_points = remaining_points
207                    .checked_sub(amount)
208                    .ok_or(VMError::OutOfGas)?;
209                self.set_remaining_points(remaining_points);
210                Ok(())
211            }
212            MeteringPoints::Exhausted => Err(VMError::OutOfGas),
213        }
214    }
215
216    #[inline]
217    fn has_export(&self, name: &str) -> bool {
218        self.with_instance(|instance| instance.exports.contains(name))
219    }
220}
221
222impl<S: GlobalStateReader, E: Executor> WasmerEnv<S, E> {
223    fn new(context: Context<S, E>, code: Bytes, interface_version: InterfaceVersion) -> Self {
224        Self {
225            context,
226            instance: Weak::new(),
227            exported_runtime: None,
228            bytecode: code,
229            interface_version,
230        }
231    }
232    pub(crate) fn exported_runtime(&self) -> &ExportedRuntime {
233        self.exported_runtime
234            .as_ref()
235            .expect("Valid instance of exported runtime")
236    }
237}
238
239/// Container for Wasm-provided exports such as alloc, dealloc, etc.
240///
241/// Let's call it a "minimal runtime" that is expected to exist inside a Wasm.
242#[derive(Clone)]
243pub(crate) struct ExportedRuntime {
244    pub(crate) memory: Memory,
245    pub(crate) exported_table: Option<Table>,
246}
247
248pub(crate) struct WasmerInstance<S: GlobalStateReader, E: Executor + 'static> {
249    instance: Arc<Instance>,
250    env: FunctionEnv<WasmerEnv<S, E>>,
251    store: Store,
252    config: Config,
253}
254
255fn handle_wasmer_runtime_error(error: RuntimeError) -> VMError {
256    match error.downcast::<VMError>() {
257        Ok(vm_error) => vm_error,
258        Err(wasmer_runtime_error) => {
259            // NOTE: Can this be other variant than VMError and trap? This may indicate a bug in
260            // our code.
261            let wasmer_trap_code = wasmer_runtime_error.to_trap().expect("Trap code");
262            VMError::Trap(from_wasmer_trap_code(wasmer_trap_code))
263        }
264    }
265}
266
267impl<S, E> WasmerInstance<S, E>
268where
269    S: GlobalStateReader + 'static,
270    E: Executor + 'static,
271{
272    pub(crate) fn call_export(&mut self, name: &str) -> Result<(), VMError> {
273        let exported_call_func: TypedFunction<(), ()> = self
274            .instance
275            .exports
276            .get_typed_function(&self.store, name)
277            .map_err(from_wasmer_export_error)?;
278
279        exported_call_func
280            .call(&mut self.store.as_store_mut())
281            .map_err(handle_wasmer_runtime_error)?;
282        Ok(())
283    }
284
285    pub(crate) fn from_wasm_bytes<C: Into<Bytes>>(
286        wasm_bytes: C,
287        context: Context<S, E>,
288        config: Config,
289    ) -> Result<Self, WasmPreparationError> {
290        let engine = {
291            let mut singlepass_compiler = Singlepass::new();
292            let gatekeeper_config = GatekeeperConfig::default();
293            singlepass_compiler.push_middleware(Arc::new(Gatekeeper::new(gatekeeper_config)));
294            singlepass_compiler
295                .push_middleware(gas_metering::gas_metering_middleware(config.gas_limit()));
296            singlepass_compiler
297        };
298
299        let engine = Engine::from(engine);
300
301        let wasm_bytes: Bytes = wasm_bytes.into();
302
303        let module = Module::new(&engine, &wasm_bytes)
304            .map_err(|error| WasmPreparationError::Compile(error.to_string()))?;
305
306        let mut store = Store::new(engine);
307
308        let wasmer_env = WasmerEnv::new(context, wasm_bytes, InterfaceVersion::from(1u32));
309        let function_env = FunctionEnv::new(&mut store, wasmer_env);
310
311        let memory = Memory::new(
312            &mut store,
313            wasmer_types::MemoryType {
314                minimum: wasmer_types::Pages(17),
315                maximum: None,
316                shared: false,
317            },
318        )
319        .map_err(|error| WasmPreparationError::Memory(error.to_string()))?;
320
321        let imports = {
322            let mut imports = imports::generate_casper_imports(&mut store, &function_env);
323
324            imports.define("env", "memory", memory.clone());
325
326            imports.define(
327                "env",
328                "interface_version_1",
329                Function::new_typed(&mut store, || {}),
330            );
331
332            imports
333        };
334
335        // TODO: Deal with "start" section that executes actual Wasm - test, measure gas, etc. ->
336        // Instance::new may fail with RuntimError
337
338        let instance = {
339            let instance = Instance::new(&mut store, &module, &imports)
340                .map_err(|error| WasmPreparationError::Instantiation(error.to_string()))?;
341
342            // We don't necessarily need atomic counter. Arc's purpose is to be able to retrieve a
343            // Weak reference to the instance to be able to invoke recursive calls to the wasm
344            // itself from within a host function implementation.
345
346            // instance.exports.get_table(name)
347            Arc::new(instance)
348        };
349
350        let interface_version = {
351            static RE: LazyLock<Regex> =
352                LazyLock::new(|| Regex::new(r"^interface_version_(?P<version>\d+)$").unwrap());
353
354            let mut interface_versions = BinaryHeap::new();
355            for import in module.imports() {
356                if import.module() == "env" {
357                    if let Some(caps) = RE.captures(import.name()) {
358                        let version = &caps["version"];
359                        let version: u32 = version.parse().expect("valid number"); // SAFETY: regex guarantees this is a number, and imports table guarantees
360                                                                                   // limited set of values.
361                        interface_versions.push(InterfaceVersion::from(version));
362                    }
363                }
364            }
365
366            // Get the highest one assuming given Wasm can support all previous interface versions.
367            interface_versions.pop()
368        };
369
370        // TODO: get first export of type table as some compilers generate different names (i.e.
371        // rust __indirect_function_table, assemblyscript `table` etc). There's only one table
372        // allowed in a valid module.
373        let table = match instance.exports.get_table("__indirect_function_table") {
374            Ok(table) => Some(table.clone()),
375            Err(error @ wasmer::ExportError::IncompatibleType) => {
376                return Err(WasmPreparationError::MissingExport(error.to_string()))
377            }
378            Err(wasmer::ExportError::Missing(_)) => None,
379        };
380
381        {
382            let function_env_mut = function_env.as_mut(&mut store);
383            function_env_mut.instance = Arc::downgrade(&instance);
384            function_env_mut.exported_runtime = Some(ExportedRuntime {
385                memory,
386                exported_table: table,
387            });
388            if let Some(interface_version) = interface_version {
389                function_env_mut.interface_version = interface_version;
390            }
391        }
392
393        Ok(Self {
394            instance,
395            env: function_env,
396            store,
397            config,
398        })
399    }
400}
401
402impl<S, E> WasmInstance for WasmerInstance<S, E>
403where
404    S: GlobalStateReader + 'static,
405    E: Executor + 'static,
406{
407    type Context = Context<S, E>;
408    fn call_export(&mut self, name: &str) -> (Result<(), VMError>, GasUsage) {
409        let vm_result = self.call_export(name);
410        let remaining_points = metering::get_remaining_points(&mut self.store, &self.instance);
411        match remaining_points {
412            metering::MeteringPoints::Remaining(remaining_points) => {
413                let gas_usage = GasUsage::new(self.config.gas_limit(), remaining_points);
414                (vm_result, gas_usage)
415            }
416            metering::MeteringPoints::Exhausted => {
417                let gas_usage = GasUsage::new(self.config.gas_limit(), 0);
418                (Err(VMError::OutOfGas), gas_usage)
419            }
420        }
421    }
422
423    /// Consume instance object and retrieve the [`Context`] object.
424    fn teardown(self) -> Context<S, E> {
425        let WasmerInstance { env, mut store, .. } = self;
426
427        let mut env_mut = env.into_mut(&mut store);
428
429        let data = env_mut.data_mut();
430
431        // NOTE: There must be a better way than re-creating the object based on consumed fields.
432
433        Context {
434            initiator: data.context.initiator,
435            caller: data.context.caller,
436            callee: data.context.callee,
437            config: data.context.config,
438            storage_costs: data.context.storage_costs,
439            transferred_value: data.context.transferred_value,
440            tracking_copy: data.context.tracking_copy.fork2(),
441            executor: data.context.executor.clone(),
442            transaction_hash: data.context.transaction_hash,
443            address_generator: Arc::clone(&data.context.address_generator),
444            chain_name: data.context.chain_name.clone(),
445            input: data.context.input.clone(),
446            block_time: data.context.block_time,
447            message_limits: data.context.message_limits,
448        }
449    }
450}