1mod dispatch;
12mod fuel_refillable;
13mod func_info;
14mod module_cache;
15mod parsed_module;
16
17#[cfg(feature = "bench")]
18pub(crate) use dispatch::dummy0;
19#[cfg(test)]
20pub(crate) use dispatch::protocol_gated_dummy;
21
22use crate::{
23 budget::{get_wasmi_config, AsBudget, Budget},
24 host::{
25 error::TryBorrowOrErr,
26 metered_clone::MeteredContainer,
27 metered_hash::{CountingHasher, MeteredHash},
28 },
29 xdr::{ContractCostType, ContractId, ScErrorCode, ScErrorType},
30 ConversionError, ErrorHandler, Host, HostError, Symbol, SymbolStr, TryIntoVal, Val,
31 WasmiMarshal,
32};
33use std::{cell::RefCell, collections::BTreeSet, rc::Rc, sync::Arc};
34
35use fuel_refillable::FuelRefillable;
36use func_info::HOST_FUNCTIONS;
37
38pub use module_cache::ModuleCache;
39pub use parsed_module::{
40 wasm_module_memory_cost, CompilationContext, ParsedModule, VersionedContractCodeCostInputs,
41};
42
43use crate::VmCaller;
44use wasmi::{Caller, StoreContextMut};
45
46impl wasmi::core::HostError for HostError {}
47
48const WASM_STD_MEM_PAGE_SIZE_IN_BYTES: u32 = 0x10000;
49
50struct VmInstantiationTimer {
51 #[cfg(not(target_family = "wasm"))]
52 host: Host,
53 #[cfg(not(target_family = "wasm"))]
54 start: std::time::Instant,
55}
56impl VmInstantiationTimer {
57 fn new(_host: Host) -> Self {
58 VmInstantiationTimer {
59 #[cfg(not(target_family = "wasm"))]
60 host: _host,
61 #[cfg(not(target_family = "wasm"))]
62 start: std::time::Instant::now(),
63 }
64 }
65}
66#[cfg(not(target_family = "wasm"))]
67impl Drop for VmInstantiationTimer {
68 fn drop(&mut self) {
69 let _ = self.host.as_budget().track_time(
70 ContractCostType::VmInstantiation,
71 self.start.elapsed().as_nanos() as u64,
72 );
73 }
74}
75
76pub struct Vm {
88 pub(crate) contract_id: ContractId,
89 #[allow(dead_code)]
90 pub(crate) module: Arc<ParsedModule>,
91 wasmi_store: RefCell<wasmi::Store<Host>>,
92 wasmi_instance: wasmi::Instance,
93 pub(crate) wasmi_memory: Option<wasmi::Memory>,
94}
95
96impl std::hash::Hash for Vm {
97 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
98 self.contract_id.hash(state);
99 }
100}
101
102impl Host {
103 pub(crate) fn make_minimal_wasmi_linker_for_symbols<Ctx: ErrorHandler>(
106 context: &Ctx,
107 engine: &wasmi::Engine,
108 symbols: &BTreeSet<(&str, &str)>,
109 ) -> Result<wasmi::Linker<Host>, HostError> {
110 let mut linker = wasmi::Linker::new(&engine);
111 for hf in HOST_FUNCTIONS {
112 if symbols.contains(&(hf.mod_str, hf.fn_str)) {
113 context.map_err((hf.wrap)(&mut linker).map_err(|le| wasmi::Error::Linker(le)))?;
114 }
115 }
116 Ok(linker)
117 }
118
119 pub(crate) fn make_maximal_wasmi_linker<Ctx: ErrorHandler>(
121 context: &Ctx,
122 engine: &wasmi::Engine,
123 ) -> Result<wasmi::Linker<Host>, HostError> {
124 let mut linker = wasmi::Linker::new(&engine);
125 for hf in HOST_FUNCTIONS {
126 context.map_err((hf.wrap)(&mut linker).map_err(|le| wasmi::Error::Linker(le)))?;
127 }
128 Ok(linker)
129 }
130}
131
132impl Vm {
133 pub const MAX_VM_ARGS: usize = 32;
135
136 #[cfg(feature = "testutils")]
137 pub fn get_all_host_functions() -> Vec<(&'static str, &'static str, u32)> {
138 HOST_FUNCTIONS
139 .iter()
140 .map(|hf| (hf.mod_str, hf.fn_str, hf.arity))
141 .collect()
142 }
143
144 #[cfg(feature = "testutils")]
145 #[allow(clippy::type_complexity)]
146 pub fn get_all_host_functions_with_supported_protocol_range(
147 ) -> Vec<(&'static str, &'static str, u32, Option<u32>, Option<u32>)> {
148 HOST_FUNCTIONS
149 .iter()
150 .map(|hf| (hf.mod_str, hf.fn_str, hf.arity, hf.min_proto, hf.max_proto))
151 .collect()
152 }
153
154 fn instantiate_wasmi(
156 host: &Host,
157 parsed_module: &Arc<ParsedModule>,
158 wasmi_linker: &wasmi::Linker<Host>,
159 ) -> Result<(wasmi::Store<Host>, wasmi::Instance, Option<wasmi::Memory>), HostError> {
160 let _span = tracy_span!("Vm::instantiate_wasmi");
161
162 let wasmi_engine = parsed_module.wasmi_module.engine();
163 let mut store = {
164 let _span = tracy_span!("Vm::instantiate_wasmi - store");
165 wasmi::Store::new(wasmi_engine, host.clone())
166 };
167 parsed_module.cost_inputs.charge_for_instantiation(host)?;
168 store.limiter(|host| host);
169 parsed_module.check_contract_imports_match_host_protocol(host)?;
170 let not_started_instance = {
171 let _span = tracy_span!("Vm::instantiate_wasmi - instantiate");
172 host.map_err(wasmi_linker.instantiate(&mut store, &parsed_module.wasmi_module))?
173 };
174
175 let instance = host.map_err(
176 not_started_instance
177 .ensure_no_start(&mut store)
178 .map_err(|ie| wasmi::Error::Instantiation(ie)),
179 )?;
180
181 let memory = if let Some(ext) = instance.get_export(&mut store, "memory") {
182 ext.into_memory()
183 } else {
184 None
185 };
186 Ok((store, instance, memory))
187 }
188
189 pub fn from_parsed_module_and_wasmi_linker(
192 host: &Host,
193 contract_id: ContractId,
194 parsed_module: Arc<ParsedModule>,
195 wasmi_linker: &wasmi::Linker<Host>,
196 ) -> Result<Rc<Self>, HostError> {
197 let _span = tracy_span!("Vm::instantiate");
198
199 host.check_ledger_protocol_supported()?;
204
205 let (wasmi_store, wasmi_instance, wasmi_memory) =
206 Self::instantiate_wasmi(host, &parsed_module, wasmi_linker)?;
207
208 Ok(Rc::new(Self {
212 contract_id,
213 module: parsed_module,
214 wasmi_store: RefCell::new(wasmi_store),
215 wasmi_instance,
216 wasmi_memory,
217 }))
218 }
219
220 pub fn new(host: &Host, contract_id: ContractId, wasm: &[u8]) -> Result<Rc<Self>, HostError> {
240 let cost_inputs = VersionedContractCodeCostInputs::V0 {
241 wasm_bytes: wasm.len(),
242 };
243 Self::new_with_cost_inputs(host, contract_id, wasm, cost_inputs)
244 }
245
246 pub(crate) fn new_with_cost_inputs(
247 host: &Host,
248 contract_id: ContractId,
249 wasm: &[u8],
250 cost_inputs: VersionedContractCodeCostInputs,
251 ) -> Result<Rc<Self>, HostError> {
252 let _span = tracy_span!("Vm::new");
253 VmInstantiationTimer::new(host.clone());
254 let parsed_module = ParsedModule::new_with_isolated_engine(host, wasm, cost_inputs)?;
255 let wasmi_linker = parsed_module.make_wasmi_linker(host)?;
256 Self::from_parsed_module_and_wasmi_linker(host, contract_id, parsed_module, &wasmi_linker)
257 }
258
259 pub(crate) fn get_memory(&self, host: &Host) -> Result<wasmi::Memory, HostError> {
260 match self.wasmi_memory {
261 Some(mem) => Ok(mem),
262 None => Err(host.err(
263 ScErrorType::WasmVm,
264 ScErrorCode::MissingValue,
265 "no linear memory named `memory`",
266 &[],
267 )),
268 }
269 }
270
271 pub(crate) fn metered_func_call(
276 self: &Rc<Self>,
277 host: &Host,
278 func_sym: &Symbol,
279 inputs: &[wasmi::Value],
280 treat_missing_function_as_noop: bool,
281 ) -> Result<Val, HostError> {
282 host.charge_budget(ContractCostType::InvokeVmFunction, None)?;
283
284 let func_ss: SymbolStr = func_sym.try_into_val(host)?;
286 let ext = match self
287 .wasmi_instance
288 .get_export(&*self.wasmi_store.try_borrow_or_err()?, func_ss.as_ref())
289 {
290 None => {
291 if treat_missing_function_as_noop {
292 return Ok(Val::VOID.into());
293 } else {
294 return Err(host.err(
295 ScErrorType::WasmVm,
296 ScErrorCode::MissingValue,
297 "trying to invoke non-existent contract function",
298 &[func_sym.to_val()],
299 ));
300 }
301 }
302 Some(e) => e,
303 };
304 let func = match ext.into_func() {
305 None => {
306 return Err(host.err(
307 ScErrorType::WasmVm,
308 ScErrorCode::UnexpectedType,
309 "trying to invoke Wasm export that is not a function",
310 &[func_sym.to_val()],
311 ))
312 }
313 Some(e) => e,
314 };
315
316 if inputs.len() > Vm::MAX_VM_ARGS {
317 return Err(host.err(
318 ScErrorType::WasmVm,
319 ScErrorCode::InvalidInput,
320 "Too many arguments in Wasm invocation",
321 &[func_sym.to_val()],
322 ));
323 }
324
325 let mut wasm_ret: [wasmi::Value; 1] = [wasmi::Value::I64(0)];
327 self.wasmi_store
328 .try_borrow_mut_or_err()?
329 .add_fuel_to_vm(host)?;
330 let res = func.call(
334 &mut *self.wasmi_store.try_borrow_mut_or_err()?,
335 inputs,
336 &mut wasm_ret,
337 );
338 self.wasmi_store
344 .try_borrow_mut_or_err()?
345 .return_fuel_to_host(host)?;
346
347 if let Err(e) = res {
348 use std::borrow::Cow;
349
350 match e {
354 wasmi::Error::Trap(trap) => {
355 if let Some(code) = trap.trap_code() {
356 let err = code.into();
357 let mut msg = Cow::Borrowed("VM call trapped");
358 host.with_debug_mode(|| {
359 msg = Cow::Owned(format!("VM call trapped: {:?}", &code));
360 Ok(())
361 });
362 return Err(host.error(err, &msg, &[func_sym.to_val()]));
363 }
364 if let Some(he) = trap.downcast::<HostError>() {
365 host.log_diagnostics(
366 "VM call trapped with HostError",
367 &[func_sym.to_val(), he.error.to_val()],
368 );
369 return Err(he);
370 }
371 return Err(host.err(
372 ScErrorType::WasmVm,
373 ScErrorCode::InternalError,
374 "VM trapped but propagation failed",
375 &[],
376 ));
377 }
378 e => {
379 let mut msg = Cow::Borrowed("VM call failed");
380 host.with_debug_mode(|| {
381 msg = Cow::Owned(format!("VM call failed: {:?}", &e));
382 Ok(())
383 });
384 return Err(host.error(e.into(), &msg, &[func_sym.to_val()]));
385 }
386 }
387 }
388 host.relative_to_absolute(
389 Val::try_marshal_from_value(wasm_ret[0].clone()).ok_or(ConversionError)?,
390 )
391 }
392
393 pub(crate) fn invoke_function_raw(
394 self: &Rc<Self>,
395 host: &Host,
396 func_sym: &Symbol,
397 args: &[Val],
398 treat_missing_function_as_noop: bool,
399 ) -> Result<Val, HostError> {
400 let _span = tracy_span!("Vm::invoke_function_raw");
401 Vec::<wasmi::Value>::charge_bulk_init_cpy(args.len() as u64, host.as_budget())?;
402 let wasm_args: Vec<wasmi::Value> = args
403 .iter()
404 .map(|i| host.absolute_to_relative(*i).map(|v| v.marshal_from_self()))
405 .collect::<Result<Vec<wasmi::Value>, HostError>>()?;
406 self.metered_func_call(
407 host,
408 func_sym,
409 wasm_args.as_slice(),
410 treat_missing_function_as_noop,
411 )
412 }
413
414 pub fn custom_section(&self, name: impl AsRef<str>) -> Option<&[u8]> {
417 self.module.custom_section(name)
418 }
419
420 pub(crate) fn with_vmcaller<F, T>(&self, f: F) -> Result<T, HostError>
424 where
425 F: FnOnce(&mut VmCaller<Host>) -> Result<T, HostError>,
426 {
427 let store: &mut wasmi::Store<Host> = &mut *self.wasmi_store.try_borrow_mut_or_err()?;
428 let mut ctx: StoreContextMut<Host> = store.into();
429 let caller: Caller<Host> = Caller::new(&mut ctx, Some(&self.wasmi_instance));
430 let mut vmcaller: VmCaller<Host> = VmCaller(Some(caller));
431 f(&mut vmcaller)
432 }
433
434 #[cfg(feature = "bench")]
435 pub(crate) fn with_caller<F, T>(&self, f: F) -> Result<T, HostError>
436 where
437 F: FnOnce(Caller<Host>) -> Result<T, HostError>,
438 {
439 let store: &mut wasmi::Store<Host> = &mut *self.wasmi_store.try_borrow_mut_or_err()?;
440 let mut ctx: StoreContextMut<Host> = store.into();
441 let caller: Caller<Host> = Caller::new(&mut ctx, Some(&self.wasmi_instance));
442 f(caller)
443 }
444
445 pub(crate) fn memory_hash_and_size(&self, budget: &Budget) -> Result<(u64, usize), HostError> {
446 use std::hash::Hasher;
447 if let Some(mem) = self.wasmi_memory {
448 self.with_vmcaller(|vmcaller| {
449 let mut state = CountingHasher::default();
450 let data = mem.data(vmcaller.try_ref()?);
451 data.metered_hash(&mut state, budget)?;
452 Ok((state.finish(), data.len()))
453 })
454 } else {
455 Ok((0, 0))
456 }
457 }
458
459 pub(crate) fn exports_hash_and_size(&self, budget: &Budget) -> Result<(u64, usize), HostError> {
463 use std::hash::Hasher;
464 use wasmi::{Extern, StoreContext};
465 self.with_vmcaller(|vmcaller| {
466 let ctx: StoreContext<'_, _> = vmcaller.try_ref()?.into();
467 let mut size: usize = 0;
468 let mut state = CountingHasher::default();
469 for export in self.wasmi_instance.exports(vmcaller.try_ref()?) {
470 size = size.saturating_add(1);
471 export.name().metered_hash(&mut state, budget)?;
472
473 match export.into_extern() {
474 Extern::Func(_) | Extern::Memory(_) => (),
476
477 Extern::Table(t) => {
478 let sz = t.size(&ctx);
479 sz.metered_hash(&mut state, budget)?;
480 size = size.saturating_add(sz as usize);
481 for i in 0..sz {
482 if let Some(elem) = t.get(&ctx, i) {
483 let s = format!("{:?}", elem);
490 budget.charge(ContractCostType::MemAlloc, Some(s.len() as u64))?;
491 s.metered_hash(&mut state, budget)?;
492 }
493 }
494 }
495 Extern::Global(g) => {
496 let s = format!("{:?}", g.get(&ctx));
497 budget.charge(ContractCostType::MemAlloc, Some(s.len() as u64))?;
498 s.metered_hash(&mut state, budget)?;
499 }
500 }
501 }
502 Ok((state.finish(), size))
503 })
504 }
505}