1use crate::{
22 AsContextExt, Error, GlobalsSetError, HostError, HostFuncType, ReturnValue, SandboxStore, Value,
23};
24use alloc::string::String;
25use gear_sandbox_env::GLOBAL_NAME_GAS;
26use sp_wasm_interface_common::HostPointer;
27use std::{
28 collections::btree_map::BTreeMap, env, fs, marker::PhantomData, path::PathBuf, ptr::NonNull,
29 sync::OnceLock,
30};
31use wasmer::{
32 Engine, FunctionEnv, Global, GlobalType, Imports, MemoryError, MemoryStyle, MemoryType,
33 RuntimeError, StoreMut, StoreObjects, StoreRef, TableStyle, TableType, Value as RuntimeValue,
34 sys::{
35 BaseTunables, NativeEngineExt, Target, Tunables,
36 vm::{VMConfig, VMGlobal, VMMemory, VMMemoryDefinition, VMTable, VMTableDefinition},
37 },
38};
39use wasmer_types::ExternType;
40
41const TARGET: &str = "runtime::sandbox";
43
44fn cache_base_path() -> PathBuf {
45 static CACHE_DIR: OnceLock<PathBuf> = OnceLock::new();
46 CACHE_DIR
47 .get_or_init(|| {
48 let out_dir = PathBuf::from(env!("OUT_DIR"));
62
63 let runtime_workspace_dir = env::var_os("GEAR_WORKSPACE_DIR").map(PathBuf::from);
64 let compiled_workspace_dir = option_env!("GEAR_WORKSPACE_DIR").map(PathBuf::from);
65 let (Some(runtime_workspace_dir), Some(compiled_workspace_dir)) =
66 (runtime_workspace_dir, compiled_workspace_dir)
67 else {
68 return out_dir;
71 };
72
73 let out_dir = pathdiff::diff_paths(out_dir, compiled_workspace_dir).unwrap();
74 let out_dir = runtime_workspace_dir.join(out_dir);
75
76 let cache = out_dir.join("wasmer-cache");
77 fs::create_dir_all(&cache).unwrap();
78 cache
79 })
80 .into()
81}
82
83struct CustomTunables {
84 inner: BaseTunables,
85 vmconfig: VMConfig,
86}
87
88impl CustomTunables {
89 fn for_target(target: &Target) -> Self {
90 Self {
91 inner: BaseTunables::for_target(target),
92 vmconfig: VMConfig {
93 wasm_stack_size: None,
94 },
95 }
96 }
97
98 fn with_wasm_stack_size(mut self, wasm_stack_size: impl Into<Option<usize>>) -> Self {
99 self.vmconfig.wasm_stack_size = wasm_stack_size.into();
100 self
101 }
102}
103
104impl Tunables for CustomTunables {
105 fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
106 self.inner.memory_style(memory)
107 }
108
109 fn table_style(&self, table: &TableType) -> TableStyle {
110 self.inner.table_style(table)
111 }
112
113 fn create_host_memory(
114 &self,
115 ty: &MemoryType,
116 style: &MemoryStyle,
117 ) -> Result<VMMemory, MemoryError> {
118 self.inner.create_host_memory(ty, style)
119 }
120
121 unsafe fn create_vm_memory(
122 &self,
123 ty: &MemoryType,
124 style: &MemoryStyle,
125 vm_definition_location: NonNull<VMMemoryDefinition>,
126 ) -> Result<VMMemory, MemoryError> {
127 unsafe {
128 self.inner
129 .create_vm_memory(ty, style, vm_definition_location)
130 }
131 }
132
133 fn create_host_table(&self, ty: &TableType, style: &TableStyle) -> Result<VMTable, String> {
134 self.inner.create_host_table(ty, style)
135 }
136
137 unsafe fn create_vm_table(
138 &self,
139 ty: &TableType,
140 style: &TableStyle,
141 vm_definition_location: NonNull<VMTableDefinition>,
142 ) -> Result<VMTable, String> {
143 unsafe {
144 self.inner
145 .create_vm_table(ty, style, vm_definition_location)
146 }
147 }
148
149 fn create_global(&self, ty: GlobalType) -> Result<VMGlobal, String> {
150 self.inner.create_global(ty)
151 }
152
153 unsafe fn create_memories(
154 &self,
155 context: &mut wasmer::sys::vm::StoreObjects,
156 module: &wasmer_types::ModuleInfo,
157 memory_styles: &wasmer_types::entity::PrimaryMap<wasmer_types::MemoryIndex, MemoryStyle>,
158 memory_definition_locations: &[NonNull<VMMemoryDefinition>],
159 ) -> Result<
160 wasmer_types::entity::PrimaryMap<
161 wasmer_types::LocalMemoryIndex,
162 wasmer_vm::InternalStoreHandle<VMMemory>,
163 >,
164 wasmer_compiler::LinkError,
165 > {
166 unsafe {
167 self.inner
168 .create_memories(context, module, memory_styles, memory_definition_locations)
169 }
170 }
171
172 unsafe fn create_tables(
173 &self,
174 context: &mut wasmer::sys::vm::StoreObjects,
175 module: &wasmer_types::ModuleInfo,
176 table_styles: &wasmer_types::entity::PrimaryMap<wasmer_types::TableIndex, TableStyle>,
177 table_definition_locations: &[NonNull<VMTableDefinition>],
178 ) -> Result<
179 wasmer_types::entity::PrimaryMap<
180 wasmer_types::LocalTableIndex,
181 wasmer_vm::InternalStoreHandle<VMTable>,
182 >,
183 wasmer_compiler::LinkError,
184 > {
185 unsafe {
186 self.inner
187 .create_tables(context, module, table_styles, table_definition_locations)
188 }
189 }
190
191 fn create_globals(
192 &self,
193 context: &mut wasmer::sys::vm::StoreObjects,
194 module: &wasmer_types::ModuleInfo,
195 ) -> Result<
196 wasmer_types::entity::PrimaryMap<
197 wasmer_types::LocalGlobalIndex,
198 wasmer_vm::InternalStoreHandle<VMGlobal>,
199 >,
200 wasmer_compiler::LinkError,
201 > {
202 self.inner.create_globals(context, module)
203 }
204
205 fn vmconfig(&self) -> &VMConfig {
206 &self.vmconfig
207 }
208}
209
210pub trait AsContext: wasmer::AsStoreRef + wasmer::AsStoreMut {}
212
213#[derive(Debug)]
214struct InnerState<T> {
215 inner: T,
216 gas_global: Option<Global>,
217}
218
219impl<T> InnerState<T> {
220 fn new(inner: T) -> Self {
221 Self {
222 inner,
223 gas_global: None,
224 }
225 }
226}
227
228#[derive(Debug)]
230pub struct Store<T> {
231 inner: wasmer::Store,
232 state: FunctionEnv<InnerState<T>>,
233}
234
235impl<T> Store<T> {
236 fn engine(&self) -> &Engine {
237 self.inner.engine()
238 }
239}
240
241impl<T: Send + 'static> SandboxStore for Store<T> {
242 fn new(state: T) -> Self {
243 let mut engine = Engine::from(wasmer::sys::Singlepass::new());
244 let tunables = CustomTunables::for_target(engine.target())
245 .with_wasm_stack_size(16 * 1024 * 1024);
247 engine.set_tunables(tunables);
248 let mut store = wasmer::Store::new(engine);
249
250 let state = FunctionEnv::new(&mut store, InnerState::new(state));
251
252 Self {
253 inner: store,
254 state,
255 }
256 }
257}
258
259impl<T> wasmer::AsStoreRef for Store<T> {
260 fn as_store_ref(&self) -> StoreRef<'_> {
261 self.inner.as_store_ref()
262 }
263}
264
265impl<T> wasmer::AsStoreMut for Store<T> {
266 fn as_store_mut(&mut self) -> StoreMut<'_> {
267 self.inner.as_store_mut()
268 }
269
270 fn objects_mut(&mut self) -> &mut StoreObjects {
271 self.inner.objects_mut()
272 }
273}
274
275impl<T: Send + 'static> AsContextExt for Store<T> {
276 type State = T;
277
278 fn data_mut(&mut self) -> &mut Self::State {
279 &mut self.state.as_mut(&mut self.inner).inner
280 }
281}
282
283impl<T> AsContext for Store<T> {}
284
285pub struct Caller<'a, T>(wasmer::FunctionEnvMut<'a, InnerState<T>>);
287
288impl<T> wasmer::AsStoreRef for Caller<'_, T> {
289 fn as_store_ref(&self) -> StoreRef<'_> {
290 self.0.as_store_ref()
291 }
292}
293
294impl<T> wasmer::AsStoreMut for Caller<'_, T> {
295 fn as_store_mut(&mut self) -> StoreMut<'_> {
296 self.0.as_store_mut()
297 }
298
299 fn objects_mut(&mut self) -> &mut StoreObjects {
300 self.0.objects_mut()
301 }
302}
303
304impl<T: Send + 'static> AsContextExt for Caller<'_, T> {
305 type State = T;
306
307 fn data_mut(&mut self) -> &mut Self::State {
308 &mut self.0.data_mut().inner
309 }
310}
311
312impl<T> AsContext for Caller<'_, T> {}
313
314#[derive(Clone)]
316pub struct Memory {
317 memref: wasmer::Memory,
318 base: usize,
319}
320
321impl<T> super::SandboxMemory<T> for Memory {
322 fn new(store: &mut Store<T>, initial: u32, maximum: Option<u32>) -> Result<Memory, Error> {
323 let ty = MemoryType::new(initial, maximum, false);
324 let memref = wasmer::Memory::new(store, ty).map_err(|e| {
325 log::trace!("Failed to create memory: {e}");
326 Error::Module
327 })?;
328 let base = memref.view(store).data_ptr() as usize;
329 Ok(Memory { memref, base })
330 }
331
332 fn read<Context>(&self, ctx: &Context, ptr: u32, buf: &mut [u8]) -> Result<(), Error>
333 where
334 Context: AsContextExt<State = T>,
335 {
336 self.memref
337 .view(ctx)
338 .read(ptr as u64, buf)
339 .map_err(|_| Error::OutOfBounds)?;
340 Ok(())
341 }
342
343 fn write<Context>(&self, ctx: &mut Context, ptr: u32, value: &[u8]) -> Result<(), Error>
344 where
345 Context: AsContextExt<State = T>,
346 {
347 self.memref
348 .view(ctx)
349 .write(ptr as u64, value)
350 .map_err(|_| Error::OutOfBounds)?;
351 Ok(())
352 }
353
354 fn grow<Context>(&self, ctx: &mut Context, pages: u32) -> Result<u32, Error>
355 where
356 Context: AsContextExt<State = T>,
357 {
358 self.memref
359 .grow(ctx, pages)
360 .map(|pages| pages.0)
361 .map_err(|_| Error::MemoryGrow)
362 }
363
364 fn size<Context>(&self, ctx: &Context) -> u32
365 where
366 Context: AsContextExt<State = T>,
367 {
368 self.memref.view(ctx).size().0
369 }
370
371 unsafe fn get_buff<Context>(&self, _ctx: &Context) -> u64
372 where
373 Context: AsContextExt<State = T>,
374 {
375 self.base as u64
376 }
377}
378
379enum ExternVal<T> {
380 HostFunc(HostFuncType<T>),
381 Memory(Memory),
382}
383
384impl<T> ExternVal<T> {
385 fn host_func(self) -> Option<HostFuncType<T>> {
386 match self {
387 ExternVal::HostFunc(ptr) => Some(ptr),
388 ExternVal::Memory(_) => None,
389 }
390 }
391
392 fn memory(self) -> Option<Memory> {
393 match self {
394 ExternVal::HostFunc(_) => None,
395 ExternVal::Memory(mem) => Some(mem),
396 }
397 }
398}
399
400impl<T> Clone for ExternVal<T> {
401 fn clone(&self) -> Self {
402 match self {
403 ExternVal::HostFunc(func) => ExternVal::HostFunc(*func),
404 ExternVal::Memory(mem) => ExternVal::Memory(mem.clone()),
405 }
406 }
407}
408
409pub struct EnvironmentDefinitionBuilder<T> {
411 map: BTreeMap<(String, String), ExternVal<T>>,
412}
413
414impl<T> super::SandboxEnvironmentBuilder<T, Memory> for EnvironmentDefinitionBuilder<T> {
415 fn new() -> Self {
416 EnvironmentDefinitionBuilder {
417 map: BTreeMap::new(),
418 }
419 }
420
421 fn add_host_func<N1, N2>(&mut self, module: N1, field: N2, f: HostFuncType<T>)
422 where
423 N1: Into<String>,
424 N2: Into<String>,
425 {
426 self.map
427 .insert((module.into(), field.into()), ExternVal::HostFunc(f));
428 }
429
430 fn add_memory<N1, N2>(&mut self, module: N1, field: N2, mem: Memory)
431 where
432 N1: Into<String>,
433 N2: Into<String>,
434 {
435 self.map
436 .insert((module.into(), field.into()), ExternVal::Memory(mem));
437 }
438}
439
440pub struct Instance<State> {
442 instance: wasmer::Instance,
443 _marker: PhantomData<State>,
444}
445
446impl<State> Clone for Instance<State> {
447 fn clone(&self) -> Self {
448 Self {
449 instance: self.instance.clone(),
450 _marker: PhantomData,
451 }
452 }
453}
454
455impl<State: Send + 'static> super::SandboxInstance<State> for Instance<State> {
456 type Memory = Memory;
457 type EnvironmentBuilder = EnvironmentDefinitionBuilder<State>;
458
459 fn new(
460 store: &mut Store<State>,
461 code: &[u8],
462 env_def_builder: &Self::EnvironmentBuilder,
463 ) -> Result<Instance<State>, Error> {
464 let module = gear_wasmer_cache::get(store.engine(), code, cache_base_path())
465 .inspect_err(|e| log::trace!(target: TARGET, "Failed to create module: {e}"))
466 .map_err(|_e| Error::Module)?;
467 let mut imports = Imports::new();
468
469 for import in module.imports() {
470 let module = import.module().to_string();
471 let name = import.name().to_string();
472 let key = (module.clone(), name.clone());
473
474 match import.ty() {
475 ExternType::Global(_) | ExternType::Table(_) | wasmer::ExternType::Tag(_) => {}
476 ExternType::Memory(_mem_ty) => {
477 let mem = env_def_builder
478 .map
479 .get(&key)
480 .cloned()
481 .and_then(|val| val.memory())
482 .ok_or_else(|| {
483 log::trace!("Memory import for `{module}::{name}` not found");
484 Error::Module
485 })?
486 .memref;
487 imports.define(&module, &name, mem);
488 }
489 ExternType::Function(func_ty) => {
490 let func_ptr = env_def_builder
491 .map
492 .get(&key)
493 .cloned()
494 .and_then(|val| val.host_func())
495 .ok_or_else(|| {
496 log::trace!("Function import for `{module}::{name}` not found");
497 Error::Module
498 })?;
499
500 let func_ty = func_ty.clone();
501
502 let func = wasmer::Function::new_with_env(
503 &mut store.inner,
504 &store.state,
505 func_ty.clone(),
506 move |mut env, params| {
507 let (inner_state, mut store) = env.data_and_store_mut();
508 let gas = inner_state
509 .gas_global
510 .as_ref()
511 .unwrap_or_else(|| {
512 unreachable!(
513 "`{GLOBAL_NAME_GAS}` global should be set to `Some(...)`"
514 )
515 })
516 .clone();
517
518 let params: Vec<_> = Some(gas.get(&mut store))
519 .into_iter()
520 .chain(params.iter().cloned())
521 .map(to_interface)
522 .map(|val| {
523 val.ok_or_else(|| {
524 RuntimeError::new(
525 "`externref` or `funcref` are not supported",
526 )
527 })
528 })
529 .collect::<Result<_, _>>()?;
530
531 let mut caller = Caller(env);
532 let val = (func_ptr)(&mut caller, ¶ms)
533 .map_err(|HostError| RuntimeError::new("function error"))?;
534
535 let return_val = match (val.inner, func_ty.results()) {
536 (ReturnValue::Unit, []) => None,
537 (ReturnValue::Value(val), [ret]) => {
538 let val = to_wasmer(val);
539
540 if val.ty() != *ret {
541 return Err(RuntimeError::new("mismatching return types"));
542 }
543
544 Some(val)
545 }
546 _results => {
547 let err_msg = format!(
548 "Instance::new: embedded executor doesn't support multi-value return. \
549 Function name - {key:?}, params - {params:?}, results - {_results:?}"
550 );
551
552 log::error!("{err_msg}");
553 unreachable!("{err_msg}")
554 }
555 };
556
557 gas.set(&mut caller.0, RuntimeValue::I64(val.gas))
558 .map_err(|e| {
559 RuntimeError::new(format!(
560 "failed to set `{GLOBAL_NAME_GAS}` global: {e}"
561 ))
562 })?;
563
564 Ok(Vec::from_iter(return_val))
565 },
566 );
567 imports.define(&module, &name, func);
568 }
569 }
570 }
571
572 let instance = wasmer::Instance::new(store, &module, &imports).map_err(|e| {
573 log::trace!(target: TARGET, "Error instantiating module: {e:?}");
574 Error::Module
575 })?;
576
577 store.state.as_mut(&mut store.inner).gas_global = instance
578 .exports
579 .get_global(GLOBAL_NAME_GAS)
580 .ok()
582 .cloned();
583
584 Ok(Instance {
585 instance,
586 _marker: PhantomData,
587 })
588 }
589
590 fn invoke(
591 &mut self,
592 mut store: &mut Store<State>,
593 name: &str,
594 args: &[Value],
595 ) -> Result<ReturnValue, Error> {
596 let args = args.iter().cloned().map(to_wasmer).collect::<Vec<_>>();
597
598 let func = self.instance.exports.get_function(name).map_err(|e| {
599 log::trace!(target: TARGET, "function `{name}` not found: {e}");
600 Error::Execution
601 })?;
602
603 let results = func.call(&mut store, &args).map_err(|e| {
604 log::trace!(target: TARGET, "invocation error: {e}");
605 Error::Execution
606 })?;
607
608 match results.as_ref() {
609 [] => Ok(ReturnValue::Unit),
610 [val] => {
611 let val = to_interface(val.clone()).ok_or_else(|| {
612 log::trace!(target: TARGET, "error converting return value to interface: {val:?}");
613 Error::Execution
614 })?;
615 Ok(ReturnValue::Value(val))
616 }
617 _results => {
618 let err_msg = format!(
619 "Instance::invoke: embedded executor doesn't support multi-value return. \
620 Function name - {name:?}, params - {args:?}, results - {_results:?}"
621 );
622
623 log::error!("{err_msg}");
624 unreachable!("{err_msg}")
625 }
626 }
627 }
628
629 fn get_global_val(&self, store: &mut Store<State>, name: &str) -> Option<Value> {
630 let global = self.instance.exports.get_global(name).ok()?;
631 let global = global.get(store);
632 to_interface(global)
633 }
634
635 fn set_global_val(
636 &self,
637 mut store: &mut Store<State>,
638 name: &str,
639 value: Value,
640 ) -> Result<(), GlobalsSetError> {
641 let global = self
642 .instance
643 .exports
644 .get_global(name)
645 .map_err(|_| GlobalsSetError::NotFound)?;
646 global
647 .set(&mut store, to_wasmer(value))
648 .map_err(|_| GlobalsSetError::Other)?;
649 Ok(())
650 }
651
652 fn get_instance_ptr(&self) -> HostPointer {
653 let err_msg = "Must not be called for embedded executor";
654
655 log::error!("{err_msg}");
656 unreachable!("{err_msg}")
657 }
658}
659
660fn to_wasmer(value: Value) -> RuntimeValue {
662 match value {
663 Value::I32(val) => RuntimeValue::I32(val),
664 Value::I64(val) => RuntimeValue::I64(val),
665 Value::F32(val) => RuntimeValue::F32(f32::from_bits(val)),
666 Value::F64(val) => RuntimeValue::F64(f64::from_bits(val)),
667 }
668}
669
670fn to_interface(value: RuntimeValue) -> Option<Value> {
672 match value {
673 RuntimeValue::I32(val) => Some(Value::I32(val)),
674 RuntimeValue::I64(val) => Some(Value::I64(val)),
675 RuntimeValue::F32(val) => Some(Value::F32(val.to_bits())),
676 RuntimeValue::F64(val) => Some(Value::F64(val.to_bits())),
677 RuntimeValue::V128(_)
678 | RuntimeValue::FuncRef(_)
679 | RuntimeValue::ExternRef(_)
680 | RuntimeValue::ExceptionRef(_) => None,
681 }
682}
683
684#[cfg(test)]
685mod tests {
686 use super::{Caller, EnvironmentDefinitionBuilder, Instance};
687 use crate::{
688 AsContextExt, Error, HostError, ReturnValue, SandboxEnvironmentBuilder, SandboxInstance,
689 SandboxStore, Value, default_executor::Store,
690 };
691 use assert_matches::assert_matches;
692 use gear_sandbox_env::{GLOBAL_NAME_GAS, WasmReturnValue};
693
694 fn execute_sandboxed(code: &[u8], args: &[Value]) -> Result<ReturnValue, Error> {
695 struct State {
696 counter: u32,
697 }
698
699 fn env_assert(
700 _c: &mut Caller<'_, State>,
701 args: &[Value],
702 ) -> Result<WasmReturnValue, HostError> {
703 if args.len() != 2 {
704 return Err(HostError);
705 }
706 let condition = args[1].as_i32().ok_or(HostError)?;
707 if condition != 0 {
708 Ok(WasmReturnValue {
709 gas: 0,
710 inner: ReturnValue::Unit,
711 })
712 } else {
713 Err(HostError)
714 }
715 }
716 fn env_inc_counter(
717 e: &mut Caller<'_, State>,
718 args: &[Value],
719 ) -> Result<WasmReturnValue, HostError> {
720 let e = e.data_mut();
721 if args.len() != 1 {
722 return Err(HostError);
723 }
724 let inc_by = args[0].as_i32().ok_or(HostError)?;
725 e.counter += inc_by as u32;
726 Ok(WasmReturnValue {
727 gas: 0,
728 inner: ReturnValue::Value(Value::I32(e.counter as i32)),
729 })
730 }
731 fn env_polymorphic_id(
733 _c: &mut Caller<'_, State>,
734 args: &[Value],
735 ) -> Result<WasmReturnValue, HostError> {
736 if args.len() != 1 {
737 return Err(HostError);
738 }
739 Ok(WasmReturnValue {
740 gas: 0,
741 inner: ReturnValue::Value(args[0]),
742 })
743 }
744
745 let state = State { counter: 0 };
746
747 let mut env_builder = EnvironmentDefinitionBuilder::new();
748 env_builder.add_host_func("env", "assert", env_assert);
749 env_builder.add_host_func("env", "inc_counter", env_inc_counter);
750 env_builder.add_host_func("env", "polymorphic_id", env_polymorphic_id);
751
752 let mut store = Store::new(state);
753 let mut instance = Instance::new(&mut store, code, &env_builder)?;
754 instance.invoke(&mut store, "call", args)
755 }
756
757 #[test]
758 fn invoke_args() {
759 let code = wat::parse_str(format!(
760 r#"
761 (module
762 (import "env" "assert" (func $assert (param i32)))
763 (global (;0;) (mut i64) (i64.const 0x20000))
764 (export "{GLOBAL_NAME_GAS}" (global 0))
765
766 (func (export "call") (param $x i32) (param $y i64)
767 ;; assert that $x = 0x12345678
768 (call $assert
769 (i32.eq
770 (local.get $x)
771 (i32.const 0x12345678)
772 )
773 )
774
775 (call $assert
776 (i64.eq
777 (local.get $y)
778 (i64.const 0x1234567887654321)
779 )
780 )
781 )
782 )
783 "#
784 ))
785 .unwrap();
786
787 execute_sandboxed(
788 &code,
789 &[Value::I32(0x12345678), Value::I64(0x1234567887654321)],
790 )
791 .unwrap();
792 }
793
794 #[test]
795 fn return_value() {
796 let code = wat::parse_str(format!(
797 r#"
798 (module
799 (global (;0;) (mut i64) (i64.const 0x20000))
800 (export "{GLOBAL_NAME_GAS}" (global 0))
801
802 (func (export "call") (param $x i32) (result i32)
803 (i32.add
804 (local.get $x)
805 (i32.const 1)
806 )
807 )
808 )
809 "#
810 ))
811 .unwrap();
812
813 let return_val = execute_sandboxed(&code, &[Value::I32(0x1336)]).unwrap();
814 assert_eq!(return_val, ReturnValue::Value(Value::I32(0x1337)));
815 }
816
817 #[test]
818 fn cant_return_unmatching_type() {
819 fn env_returns_i32(
820 _e: &mut Caller<'_, ()>,
821 _args: &[Value],
822 ) -> Result<WasmReturnValue, HostError> {
823 Ok(WasmReturnValue {
824 gas: 0,
825 inner: ReturnValue::Value(Value::I32(42)),
826 })
827 }
828
829 let mut env_builder = EnvironmentDefinitionBuilder::new();
830 env_builder.add_host_func("env", "returns_i32", env_returns_i32);
831
832 let code = wat::parse_str(format!(
833 r#"
834 (module
835 ;; It's actually returns i32, but imported as if it returned i64
836 (import "env" "returns_i32" (func $returns_i32 (result i64)))
837 (global (;0;) (mut i64) (i64.const 0x20000))
838 (export "{GLOBAL_NAME_GAS}" (global 0))
839
840 (func (export "call")
841 (drop
842 (call $returns_i32)
843 )
844 )
845 )
846 "#
847 ))
848 .unwrap();
849
850 let mut store = Store::new(());
851 let mut instance = Instance::new(&mut store, &code, &env_builder).unwrap();
853
854 assert_matches!(
856 instance.invoke(&mut store, "call", &[]),
857 Err(Error::Execution)
858 );
859 }
860}