gear_sandbox/
embedded_executor.rs

1// This file is part of Gear.
2
3// Copyright (C) Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! An embedded WASM executor utilizing `wasmer`.
20
21use 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
41/// The target used for logging.
42const 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            // We acquire workspace root dir during runtime and compile-time.
49            //
50            // During development, runtime workspace dir equals to compile-time one,
51            // so all compiled WASMs are cached in the usual ` OUT_DIR `
52            // like we don't rewrite it.
53            //
54            // During cross-compilation, the runtime workspace dir differs from the compile-time one,
55            // and accordingly, `OUT_DIR` beginning differs too,
56            // so we change its beginning to successfully run tests.
57            //
58            // `OUT_DIR` is used for caching instead of some platform-specific project folder to
59            // not maintain the ever-growing number of cached WASMs
60
61            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                // `GEAR_WORKSPACE_DIR` is not present in user code,
69                // so we return `OUT_DIR` without any changes
70                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
210/// [`AsContextExt`] extension.
211pub 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/// wasmer store wrapper.
229#[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            // make stack size bigger for fuzzer
246            .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
285/// wasmer function env wrapper.
286pub 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/// The linear memory used by the sandbox.
315#[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
409/// A builder for the environment of the sandboxed WASM module.
410pub 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
440/// Sandboxed instance of a WASM module.
441pub 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, &params)
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            // gas global is optional during some benchmarks
581            .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
660/// Convert the substrate value type to the wasmer value type.
661fn 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
670/// Convert the wasmer value type to the substrate value type.
671fn 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        /// Function that takes one argument of any type and returns that value.
732        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        // It succeeds since we are able to import functions with types we want.
852        let mut instance = Instance::new(&mut store, &code, &env_builder).unwrap();
853
854        // But this fails since we imported a function that returns i32 as if it returned i64.
855        assert_matches!(
856            instance.invoke(&mut store, "call", &[]),
857            Err(Error::Execution)
858        );
859    }
860}