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 TrapCode::MemoryOutOfBounds
35 }
36 wasmer::MemoryAccessError::NonUtf8String => {
37 unreachable!("NonUtf8String")
40 }
41 _ => {
42 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 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 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") .get(&mut store.as_store_mut(), idx)
177 .expect("has entry in the table"); 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 #[inline]
195 fn gas_consumed(&mut self) -> MeteringPoints {
196 self.get_remaining_points()
197 }
198
199 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#[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 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 let instance = {
339 let instance = Instance::new(&mut store, &module, &imports)
340 .map_err(|error| WasmPreparationError::Instantiation(error.to_string()))?;
341
342 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"); interface_versions.push(InterfaceVersion::from(version));
362 }
363 }
364 }
365
366 interface_versions.pop()
368 };
369
370 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 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 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}