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, Hash, ScErrorCode, ScErrorType},
30 ConversionError, Host, HostError, Symbol, SymbolStr, TryIntoVal, Val, WasmiMarshal,
31};
32use std::{cell::RefCell, collections::BTreeSet, rc::Rc};
33
34use fuel_refillable::FuelRefillable;
35use func_info::HOST_FUNCTIONS;
36
37pub use module_cache::ModuleCache;
38pub use parsed_module::{ParsedModule, VersionedContractCodeCostInputs};
39
40use wasmi::{Instance, Linker, Memory, Store, Value};
41
42use crate::VmCaller;
43use wasmi::{Caller, StoreContextMut};
44
45impl wasmi::core::HostError for HostError {}
46
47const MAX_VM_ARGS: usize = 32;
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: Hash,
89 #[allow(dead_code)]
90 pub(crate) module: Rc<ParsedModule>,
91 store: RefCell<Store<Host>>,
92 instance: Instance,
93 pub(crate) memory: Option<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_linker(
104 engine: &wasmi::Engine,
105 symbols: &BTreeSet<(&str, &str)>,
106 ) -> Result<Linker<Host>, HostError> {
107 let mut linker = Linker::new(&engine);
108 for hf in HOST_FUNCTIONS {
109 if symbols.contains(&(hf.mod_str, hf.fn_str)) {
110 (hf.wrap)(&mut linker).map_err(|le| wasmi::Error::Linker(le))?;
111 }
112 }
113 Ok(linker)
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub(crate) enum ModuleParseCostMode {
123 Normal,
124 #[cfg(any(test, feature = "recording_mode"))]
125 PossiblyDeferredIfRecording,
126}
127
128impl Vm {
129 #[cfg(feature = "testutils")]
130 pub fn get_all_host_functions() -> Vec<(&'static str, &'static str, u32)> {
131 HOST_FUNCTIONS
132 .iter()
133 .map(|hf| (hf.mod_str, hf.fn_str, hf.arity))
134 .collect()
135 }
136
137 fn instantiate(
140 host: &Host,
141 contract_id: Hash,
142 parsed_module: Rc<ParsedModule>,
143 linker: &Linker<Host>,
144 ) -> Result<Rc<Self>, HostError> {
145 let _span = tracy_span!("Vm::instantiate");
146
147 let engine = parsed_module.module.engine();
148 let mut store = Store::new(engine, host.clone());
149
150 parsed_module.cost_inputs.charge_for_instantiation(host)?;
151
152 store.limiter(|host| host);
153
154 {
155 let _span0 = tracy_span!("define host functions");
172 let ledger_proto = host.with_ledger_info(|li| Ok(li.protocol_version))?;
173 parsed_module.with_import_symbols(host, |module_symbols| {
174 for hf in HOST_FUNCTIONS {
175 if !module_symbols.contains(&(hf.mod_str, hf.fn_str)) {
176 continue;
177 }
178 if let Some(min_proto) = hf.min_proto {
179 if parsed_module.proto_version < min_proto || ledger_proto < min_proto {
180 return Err(host.err(
181 ScErrorType::WasmVm,
182 ScErrorCode::InvalidAction,
183 "contract calls a host function not yet supported by current protocol",
184 &[],
185 ));
186 }
187 }
188 if let Some(max_proto) = hf.max_proto {
189 if parsed_module.proto_version > max_proto || ledger_proto > max_proto {
190 return Err(host.err(
191 ScErrorType::WasmVm,
192 ScErrorCode::InvalidAction,
193 "contract calls a host function no longer supported in the current protocol",
194 &[],
195 ));
196 }
197 }
198 }
199 Ok(())
200 })?;
201 }
202
203 let not_started_instance = {
204 let _span0 = tracy_span!("instantiate module");
205 host.map_err(linker.instantiate(&mut store, &parsed_module.module))?
206 };
207
208 let instance = host.map_err(
209 not_started_instance
210 .ensure_no_start(&mut store)
211 .map_err(|ie| wasmi::Error::Instantiation(ie)),
212 )?;
213
214 let memory = if let Some(ext) = instance.get_export(&mut store, "memory") {
215 ext.into_memory()
216 } else {
217 None
218 };
219
220 Ok(Rc::new(Self {
224 contract_id,
225 module: parsed_module,
226 store: RefCell::new(store),
227 instance,
228 memory,
229 }))
230 }
231
232 pub fn from_parsed_module(
233 host: &Host,
234 contract_id: Hash,
235 parsed_module: Rc<ParsedModule>,
236 ) -> Result<Rc<Self>, HostError> {
237 let _span = tracy_span!("Vm::from_parsed_module");
238 VmInstantiationTimer::new(host.clone());
239 if let Some(linker) = &*host.try_borrow_linker()? {
240 Self::instantiate(host, contract_id, parsed_module, linker)
241 } else {
242 let linker = parsed_module.make_linker(host)?;
243 Self::instantiate(host, contract_id, parsed_module, &linker)
244 }
245 }
246
247 pub fn new(host: &Host, contract_id: Hash, wasm: &[u8]) -> Result<Rc<Self>, HostError> {
268 let cost_inputs = VersionedContractCodeCostInputs::V0 {
269 wasm_bytes: wasm.len(),
270 };
271 Self::new_with_cost_inputs(
272 host,
273 contract_id,
274 wasm,
275 cost_inputs,
276 ModuleParseCostMode::Normal,
277 )
278 }
279
280 pub(crate) fn new_with_cost_inputs(
281 host: &Host,
282 contract_id: Hash,
283 wasm: &[u8],
284 cost_inputs: VersionedContractCodeCostInputs,
285 cost_mode: ModuleParseCostMode,
286 ) -> Result<Rc<Self>, HostError> {
287 let _span = tracy_span!("Vm::new");
288 VmInstantiationTimer::new(host.clone());
289 let parsed_module = Self::parse_module(host, wasm, cost_inputs, cost_mode)?;
290 let linker = parsed_module.make_linker(host)?;
291 Self::instantiate(host, contract_id, parsed_module, &linker)
292 }
293
294 #[cfg(not(any(test, feature = "recording_mode")))]
295 fn parse_module(
296 host: &Host,
297 wasm: &[u8],
298 cost_inputs: VersionedContractCodeCostInputs,
299 _cost_mode: ModuleParseCostMode,
300 ) -> Result<Rc<ParsedModule>, HostError> {
301 ParsedModule::new_with_isolated_engine(host, wasm, cost_inputs)
302 }
303
304 #[cfg(any(test, feature = "recording_mode"))]
340 fn parse_module(
341 host: &Host,
342 wasm: &[u8],
343 cost_inputs: VersionedContractCodeCostInputs,
344 cost_mode: ModuleParseCostMode,
345 ) -> Result<Rc<ParsedModule>, HostError> {
346 if cost_mode == ModuleParseCostMode::PossiblyDeferredIfRecording
347 && host.get_ledger_protocol_version()? >= ModuleCache::MIN_LEDGER_VERSION
348 {
349 if host.in_storage_recording_mode()? {
350 return host.budget_ref().with_observable_shadow_mode(|| {
351 ParsedModule::new_with_isolated_engine(host, wasm, cost_inputs)
352 });
353 }
354 }
355 ParsedModule::new_with_isolated_engine(host, wasm, cost_inputs)
356 }
357
358 pub(crate) fn get_memory(&self, host: &Host) -> Result<Memory, HostError> {
359 match self.memory {
360 Some(mem) => Ok(mem),
361 None => Err(host.err(
362 ScErrorType::WasmVm,
363 ScErrorCode::MissingValue,
364 "no linear memory named `memory`",
365 &[],
366 )),
367 }
368 }
369
370 pub(crate) fn metered_func_call(
375 self: &Rc<Self>,
376 host: &Host,
377 func_sym: &Symbol,
378 inputs: &[Value],
379 ) -> Result<Val, HostError> {
380 host.charge_budget(ContractCostType::InvokeVmFunction, None)?;
381
382 let func_ss: SymbolStr = func_sym.try_into_val(host)?;
384 let ext = match self
385 .instance
386 .get_export(&*self.store.try_borrow_or_err()?, func_ss.as_ref())
387 {
388 None => {
389 return Err(host.err(
390 ScErrorType::WasmVm,
391 ScErrorCode::MissingValue,
392 "invoking unknown export",
393 &[func_sym.to_val()],
394 ))
395 }
396 Some(e) => e,
397 };
398 let func = match ext.into_func() {
399 None => {
400 return Err(host.err(
401 ScErrorType::WasmVm,
402 ScErrorCode::UnexpectedType,
403 "export is not a function",
404 &[func_sym.to_val()],
405 ))
406 }
407 Some(e) => e,
408 };
409
410 if inputs.len() > MAX_VM_ARGS {
411 return Err(host.err(
412 ScErrorType::WasmVm,
413 ScErrorCode::InvalidInput,
414 "Too many arguments in wasm invocation",
415 &[func_sym.to_val()],
416 ));
417 }
418
419 let mut wasm_ret: [Value; 1] = [Value::I64(0)];
421 self.store.try_borrow_mut_or_err()?.add_fuel_to_vm(host)?;
422 let res = func.call(
426 &mut *self.store.try_borrow_mut_or_err()?,
427 inputs,
428 &mut wasm_ret,
429 );
430 self.store
436 .try_borrow_mut_or_err()?
437 .return_fuel_to_host(host)?;
438
439 if let Err(e) = res {
440 use std::borrow::Cow;
441
442 match e {
446 wasmi::Error::Trap(trap) => {
447 if let Some(code) = trap.trap_code() {
448 let err = code.into();
449 let mut msg = Cow::Borrowed("VM call trapped");
450 host.with_debug_mode(|| {
451 msg = Cow::Owned(format!("VM call trapped: {:?}", &code));
452 Ok(())
453 });
454 return Err(host.error(err, &msg, &[func_sym.to_val()]));
455 }
456 if let Some(he) = trap.downcast::<HostError>() {
457 host.log_diagnostics(
458 "VM call trapped with HostError",
459 &[func_sym.to_val(), he.error.to_val()],
460 );
461 return Err(he);
462 }
463 return Err(host.err(
464 ScErrorType::WasmVm,
465 ScErrorCode::InternalError,
466 "VM trapped but propagation failed",
467 &[],
468 ));
469 }
470 e => {
471 let mut msg = Cow::Borrowed("VM call failed");
472 host.with_debug_mode(|| {
473 msg = Cow::Owned(format!("VM call failed: {:?}", &e));
474 Ok(())
475 });
476 return Err(host.error(e.into(), &msg, &[func_sym.to_val()]));
477 }
478 }
479 }
480 host.relative_to_absolute(
481 Val::try_marshal_from_value(wasm_ret[0].clone()).ok_or(ConversionError)?,
482 )
483 }
484
485 pub(crate) fn invoke_function_raw(
486 self: &Rc<Self>,
487 host: &Host,
488 func_sym: &Symbol,
489 args: &[Val],
490 ) -> Result<Val, HostError> {
491 let _span = tracy_span!("Vm::invoke_function_raw");
492 Vec::<Value>::charge_bulk_init_cpy(args.len() as u64, host.as_budget())?;
493 let wasm_args: Vec<Value> = args
494 .iter()
495 .map(|i| host.absolute_to_relative(*i).map(|v| v.marshal_from_self()))
496 .collect::<Result<Vec<Value>, HostError>>()?;
497 self.metered_func_call(host, func_sym, wasm_args.as_slice())
498 }
499
500 pub fn custom_section(&self, name: impl AsRef<str>) -> Option<&[u8]> {
503 self.module.custom_section(name)
504 }
505
506 pub(crate) fn with_vmcaller<F, T>(&self, f: F) -> Result<T, HostError>
510 where
511 F: FnOnce(&mut VmCaller<Host>) -> Result<T, HostError>,
512 {
513 let store: &mut Store<Host> = &mut *self.store.try_borrow_mut_or_err()?;
514 let mut ctx: StoreContextMut<Host> = store.into();
515 let caller: Caller<Host> = Caller::new(&mut ctx, Some(&self.instance));
516 let mut vmcaller: VmCaller<Host> = VmCaller(Some(caller));
517 f(&mut vmcaller)
518 }
519
520 #[cfg(feature = "bench")]
521 pub(crate) fn with_caller<F, T>(&self, f: F) -> Result<T, HostError>
522 where
523 F: FnOnce(Caller<Host>) -> Result<T, HostError>,
524 {
525 let store: &mut Store<Host> = &mut *self.store.try_borrow_mut_or_err()?;
526 let mut ctx: StoreContextMut<Host> = store.into();
527 let caller: Caller<Host> = Caller::new(&mut ctx, Some(&self.instance));
528 f(caller)
529 }
530
531 pub(crate) fn memory_hash_and_size(&self, budget: &Budget) -> Result<(u64, usize), HostError> {
532 use std::hash::Hasher;
533 if let Some(mem) = self.memory {
534 self.with_vmcaller(|vmcaller| {
535 let mut state = CountingHasher::default();
536 let data = mem.data(vmcaller.try_ref()?);
537 data.metered_hash(&mut state, budget)?;
538 Ok((state.finish(), data.len()))
539 })
540 } else {
541 Ok((0, 0))
542 }
543 }
544
545 pub(crate) fn exports_hash_and_size(&self, budget: &Budget) -> Result<(u64, usize), HostError> {
549 use std::hash::Hasher;
550 use wasmi::{Extern, StoreContext};
551 self.with_vmcaller(|vmcaller| {
552 let ctx: StoreContext<'_, _> = vmcaller.try_ref()?.into();
553 let mut size: usize = 0;
554 let mut state = CountingHasher::default();
555 for export in self.instance.exports(vmcaller.try_ref()?) {
556 size = size.saturating_add(1);
557 export.name().metered_hash(&mut state, budget)?;
558
559 match export.into_extern() {
560 Extern::Func(_) | Extern::Memory(_) => (),
562
563 Extern::Table(t) => {
564 let sz = t.size(&ctx);
565 sz.metered_hash(&mut state, budget)?;
566 size = size.saturating_add(sz as usize);
567 for i in 0..sz {
568 if let Some(elem) = t.get(&ctx, i) {
569 let s = format!("{:?}", elem);
576 budget.charge(ContractCostType::MemAlloc, Some(s.len() as u64))?;
577 s.metered_hash(&mut state, budget)?;
578 }
579 }
580 }
581 Extern::Global(g) => {
582 let s = format!("{:?}", g.get(&ctx));
583 budget.charge(ContractCostType::MemAlloc, Some(s.len() as u64))?;
584 s.metered_hash(&mut state, budget)?;
585 }
586 }
587 }
588 Ok((state.finish(), size))
589 })
590 }
591}
592
593pub trait CustomContextVM {
596 fn read(&self, mem_pos: usize, buf: &mut [u8]);
598
599 fn data(&self) -> &[u8];
600
601 fn write(&mut self, pos: u32, slice: &[u8]) -> i64;
602
603 fn data_mut(&mut self) -> &mut [u8];
604}