Repository: stellar/rs-soroban-env
Pull request #422 (2022-09-09 23:45:20)

Migrate to wasmi_v1 API
This upgrades the env crates to use the new wasmi_v1 API that arrives with [the upgrade to wasmi 0.16.0](https://github.com/stellar/wasmi/pull/2).

Unfortunately it's quite invasive: every host function signature changes inside the host. The fallout _should_ be limited to the host (and people who embed and invoke it). Most of the changes have to do with the fact that wasmi now requires us to thread a `&mut Store` through to the host function if we want to use anything stored _in_ its store (such as linear memory). We can't dynamically acquire mutable access to it elsewhere, as it's already mutably borrowed during execution.

Everything else is mostly just "same task, different API" sort of stuff. Module parsing and dispatch functions actually got a little simpler.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5eae91ef..306e95ec 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -276,6 +276,12 @@ dependencies = [
  "version_check",
 ]
 
+[[package]]
+name = "indexmap-nostd"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590"
+
 [[package]]
 name = "itertools"
 version = "0.10.3"
@@ -688,35 +694,32 @@ version = "0.0.5"
 
 [[package]]
 name = "soroban-wasmi"
-version = "0.11.0"
-source = "git+https://github.com/stellar/wasmi?rev=7b1f2355#7b1f2355ba26d09daf0241f953eb16f7afe16057"
+version = "0.16.0"
+source = "git+https://github.com/stellar/wasmi?rev=a61b6df#a61b6dffa0a13aca70b046fc11e379a3fde31147"
 dependencies = [
- "parity-wasm",
- "soroban-wasmi-validation",
  "soroban-wasmi_core",
-]
-
-[[package]]
-name = "soroban-wasmi-validation"
-version = "0.4.1"
-source = "git+https://github.com/stellar/wasmi?rev=7b1f2355#7b1f2355ba26d09daf0241f953eb16f7afe16057"
-dependencies = [
- "parity-wasm",
+ "spin",
+ "wasmparser-nostd",
 ]
 
 [[package]]
 name = "soroban-wasmi_core"
-version = "0.1.0"
-source = "git+https://github.com/stellar/wasmi?rev=7b1f2355#7b1f2355ba26d09daf0241f953eb16f7afe16057"
+version = "0.2.0"
+source = "git+https://github.com/stellar/wasmi?rev=a61b6df#a61b6dffa0a13aca70b046fc11e379a3fde31147"
 dependencies = [
  "downcast-rs",
  "libm",
  "memory_units",
  "num-rational",
  "num-traits",
- "parity-wasm",
 ]
 
+[[package]]
+name = "spin"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09"
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
@@ -875,6 +878,15 @@ version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
+[[package]]
+name = "wasmparser-nostd"
+version = "0.90.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92a94fbf4c521b038f41382df2056cf47099d3b7a0faa5a6e46f7771fd7c84a6"
+dependencies = [
+ "indexmap-nostd",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index 95749cb1..95bdb60c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,4 +20,4 @@ soroban-env-host = { path = "soroban-env-host" }
 soroban-env-macros = { path = "soroban-env-macros" }
 soroban-native-sdk-macros = { path = "soroban-native-sdk-macros" }
 stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "fee9a436" }
-wasmi = { package = "soroban-wasmi", git = "https://github.com/stellar/wasmi", rev = "7b1f2355" }
+wasmi = { package = "soroban-wasmi", git = "https://github.com/stellar/wasmi", rev = "a61b6df" }
diff --git a/soroban-env-common/Cargo.toml b/soroban-env-common/Cargo.toml
index 65246b7d..e30f6013 100644
--- a/soroban-env-common/Cargo.toml
+++ b/soroban-env-common/Cargo.toml
@@ -13,7 +13,7 @@ rust-version = "1.63"
 [dependencies]
 soroban-env-macros = { version = "0.0.5" }
 stellar-xdr = { version = "0.0.2", default-features = false, features = [ "next" ] }
-wasmi = { package = "soroban-wasmi", version = "0.11.0", optional = true }
+wasmi = { package = "soroban-wasmi", version = "0.16.0", optional = true }
 static_assertions = "1.1.0"
 
 [features]
diff --git a/soroban-env-common/src/checked_env.rs b/soroban-env-common/src/checked_env.rs
index 552ca8ba..d7d7baee 100644
--- a/soroban-env-common/src/checked_env.rs
+++ b/soroban-env-common/src/checked_env.rs
@@ -178,5 +178,5 @@ macro_rules! impl_env_for_checked_env {
     };
 }
 
-// Here we invoke the x-macro passing generate_env_trait as its callback macro.
+// Here we invoke the x-macro passing impl_env_for_checked_env as its callback macro.
 call_macro_with_all_host_functions! { impl_env_for_checked_env }
diff --git a/soroban-env-common/src/lib.rs b/soroban-env-common/src/lib.rs
index f5adbbe9..673b4d34 100644
--- a/soroban-env-common/src/lib.rs
+++ b/soroban-env-common/src/lib.rs
@@ -36,6 +36,7 @@ mod symbol;
 mod tuple;
 mod unimplemented_env;
 mod val;
+mod vmcaller_checked_env;
 
 // Re-export the XDR definitions
 pub use stellar_xdr as xdr;
@@ -50,6 +51,7 @@ pub use convert::TryConvert;
 pub use env::{Env, EnvBase};
 pub use env_val::{EnvVal, FromVal, IntoVal, TryFromVal, TryIntoVal};
 pub use unimplemented_env::UnimplementedEnv;
+pub use vmcaller_checked_env::{VmCaller, VmCallerCheckedEnv};
 
 // BitSet, Status and Symbol wrap RawVals.
 // TODO: maybe these should wrap EnvVals?
diff --git a/soroban-env-common/src/raw_val.rs b/soroban-env-common/src/raw_val.rs
index f8e071e1..aebbfe3f 100644
--- a/soroban-env-common/src/raw_val.rs
+++ b/soroban-env-common/src/raw_val.rs
@@ -248,17 +248,20 @@ declare_tryfrom!(Static);
 declare_tryfrom!(Object);
 
 #[cfg(feature = "vm")]
-impl wasmi::FromValue for RawVal {
-    fn from_value(val: wasmi::RuntimeValue) -> Option<Self> {
-        let maybe: Option<u64> = val.try_into();
-        maybe.map(RawVal::from_payload)
+impl wasmi::core::FromValue for RawVal {
+    fn from_value(val: wasmi::core::Value) -> Option<Self> {
+        if let wasmi::core::Value::I64(i) = val {
+            Some(RawVal::from_payload(i as u64))
+        } else {
+            None
+        }
     }
 }
 
 #[cfg(feature = "vm")]
-impl From<RawVal> for wasmi::RuntimeValue {
+impl From<RawVal> for wasmi::core::Value {
     fn from(v: RawVal) -> Self {
-        wasmi::RuntimeValue::I64(v.get_payload() as i64)
+        wasmi::core::Value::I64(v.get_payload() as i64)
     }
 }
 
diff --git a/soroban-env-common/src/val_wrapper.rs b/soroban-env-common/src/val_wrapper.rs
index 9db33789..3174f3c6 100644
--- a/soroban-env-common/src/val_wrapper.rs
+++ b/soroban-env-common/src/val_wrapper.rs
@@ -70,9 +70,9 @@ macro_rules! decl_tagged_val_wrapper_methods {
 
         // wasmi / VM argument support
         #[cfg(feature = "vm")]
-        impl wasmi::FromValue for $tagname {
-            fn from_value(val: wasmi::RuntimeValue) -> Option<Self> {
-                let maybe: Option<u64> = val.try_into();
+        impl wasmi::core::FromValue for $tagname {
+            fn from_value(val: wasmi::core::Value) -> Option<Self> {
+                let maybe: Option<u64> = <u64 as wasmi::core::FromValue>::from_value(val);
                 match maybe {
                     Some(u) => {
                         let raw = RawVal::from_payload(u);
@@ -89,9 +89,9 @@ macro_rules! decl_tagged_val_wrapper_methods {
             }
         }
         #[cfg(feature = "vm")]
-        impl From<$tagname> for wasmi::RuntimeValue {
+        impl From<$tagname> for wasmi::core::Value {
             fn from(v: $tagname) -> Self {
-                wasmi::RuntimeValue::I64(v.as_raw().get_payload() as i64)
+                wasmi::core::Value::I64(v.as_raw().get_payload() as i64)
             }
         }
 
diff --git a/soroban-env-common/src/vmcaller_checked_env.rs b/soroban-env-common/src/vmcaller_checked_env.rs
new file mode 100644
index 00000000..ed609cfd
--- /dev/null
+++ b/soroban-env-common/src/vmcaller_checked_env.rs
@@ -0,0 +1,228 @@
+#[cfg(feature = "vm")]
+use crate::xdr::ScHostContextErrorCode;
+
+use crate::call_macro_with_all_host_functions;
+use crate::{Object, RawVal, Symbol};
+use core::fmt::Debug;
+#[cfg(not(feature = "vm"))]
+use core::marker::PhantomData;
+
+/// The VmCallerCheckedEnv trait is similar to the CheckedEnv trait -- it
+/// provides all the same-named methods -- but they have a form that takes an
+/// initial [`VmCaller`] argument by `&mut` that may or may-not wrap a
+/// `wasmi::Caller` structure, depending on whether it was invoked from a wasmi
+/// host-function wrapper.
+///
+/// There is a blanket `impl<T:VmCallerCheckedEnv> CheckedEnv for T` so that any
+/// type (eg. `Host`) that implements `VmCallerCheckedEnv` automatically also
+/// implements `CheckedEnv`, just by calling the corresponding
+/// `VmCallerCheckedEnv` method with the [`VmCaller::none()`] argument. This
+/// allows code to import and use `CheckedEnv` directly (such as the native
+/// contract) to call host methods without having to write `VmCaller::none()`
+/// everywhere.
+
+#[cfg(feature = "vm")]
+pub struct VmCaller<'a, T>(pub Option<wasmi::Caller<'a, T>>);
+#[cfg(feature = "vm")]
+impl<'a, T> VmCaller<'a, T> {
+    pub fn none() -> Self {
+        VmCaller(None)
+    }
+    pub fn try_ref(&self) -> Result<&wasmi::Caller<'a, T>, ScHostContextErrorCode> {
+        match &self.0 {
+            Some(caller) => Ok(caller),
+            None => Err(ScHostContextErrorCode::NoContractRunning),
+        }
+    }
+    pub fn try_mut(&mut self) -> Result<&mut wasmi::Caller<'a, T>, ScHostContextErrorCode> {
+        match &mut self.0 {
+            Some(caller) => Ok(caller),
+            None => Err(ScHostContextErrorCode::NoContractRunning),
+        }
+    }
+}
+
+#[cfg(not(feature = "vm"))]
+pub struct VmCaller<'a, T> {
+    _nothing: PhantomData<&'a T>,
+}
+#[cfg(not(feature = "vm"))]
+impl<'a, T> VmCaller<'a, T> {
+    pub fn none() -> Self {
+        VmCaller {
+            _nothing: PhantomData,
+        }
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+/// X-macro use: defining trait VmCallerCheckedEnv
+///////////////////////////////////////////////////////////////////////////////
+
+// This is a helper macro used only by generate_vmcaller_checked_env_trait
+// below. It consumes a token-tree of the form:
+//
+//  {fn $fn_id:ident $args:tt -> $ret:ty}
+//
+// and produces the the corresponding method declaration to be used in the Env
+// trait.
+macro_rules! host_function_helper {
+    {
+        $(#[$attr:meta])*
+        fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty
+    }
+    =>
+    {
+        $(#[$attr])*
+        fn $fn_id(&self, vmcaller: &mut VmCaller<Self::VmUserState>, $($arg:$type),*) -> Result<$ret, Self::Error>;
+    };
+}
+
+// This is a callback macro that pattern-matches the token-tree passed by the
+// x-macro (call_macro_with_all_host_functions) and produces a suite of method
+// declarations, which it places in the body of the declaration of the
+// VmCallerCheckedEnv trait.
+macro_rules! generate_vmcaller_checked_env_trait {
+    {
+        $(
+            // This outer pattern matches a single 'mod' block of the token-tree
+            // passed from the x-macro to this macro. It is embedded in a `$()*`
+            // pattern-repetition matcher so that it will match all provided
+            // 'mod' blocks provided.
+            $(#[$mod_attr:meta])*
+            mod $mod_id:ident $mod_str:literal
+            {
+                $(
+                    // This inner pattern matches a single function description
+                    // inside a 'mod' block in the token-tree passed from the
+                    // x-macro to this macro. It is embedded in a `$()*`
+                    // pattern-repetition matcher so that it will match all such
+                    // descriptions.
+                    $(#[$fn_attr:meta])*
+                    { $fn_str:literal, fn $fn_id:ident $args:tt -> $ret:ty }
+                )*
+            }
+        )*
+    }
+
+    => // The part of the macro above this line is a matcher; below is its expansion.
+
+    {
+        // This macro expands to a single item: the VmCallerCheckedEnv trait
+
+        /// This trait is a variant of the [Env](crate::Env) trait used to
+        /// define the interface implemented by Host. The wasmi VM dispatch
+        /// functions call methods on `VmCallerCheckedEnv`, passing a
+        /// [`VmCaller`] that wraps the wasmi Caller context, and then convert
+        /// any `Result::Err(...)` return value into a VM trap, halting VM
+        /// execution.
+        ///
+        /// There is also a blanket `impl<T:VmCallerCheckedEnv> CheckedEnv for
+        /// T` that implements the `CheckedEnv` for any `VmCallerCheckedEnv` by
+        /// passing [`VmCaller::none()`] for the first argument, allowing user
+        /// code such as the native contract to avoid writing `VmCaller::none()`
+        /// everywhere.
+        pub trait VmCallerCheckedEnv
+        {
+            type VmUserState;
+            type Error: Debug;
+            $(
+                $(
+                    // This invokes the host_function_helper! macro above
+                    // passing only the relevant parts of the declaration
+                    // matched by the inner pattern above. It is embedded in two
+                    // nested `$()*` pattern-repetition expanders that
+                    // correspond to the pattern-repetition matchers in the
+                    // match section, but we ignore the structure of the 'mod'
+                    // block repetition-level from the outer pattern in the
+                    // expansion, flattening all functions from all 'mod' blocks
+                    // into the VmCallerCheckedEnv trait.
+                    host_function_helper!{$(#[$fn_attr])* fn $fn_id $args -> $ret}
+                )*
+            )*
+        }
+    };
+}
+
+// Here we invoke the x-macro passing generate_env_trait as its callback macro.
+call_macro_with_all_host_functions! { generate_vmcaller_checked_env_trait }
+
+///////////////////////////////////////////////////////////////////////////////
+/// X-macro use: impl<E> CheckedEnv for VmCallerCheckedEnv<E>
+///////////////////////////////////////////////////////////////////////////////
+
+// This is a helper macro used only by
+// generate_impl_checked_env_for_vmcaller_checked_env below. It consumes a
+// token-tree of the form:
+//
+//  {fn $fn_id:ident $args:tt -> $ret:ty}
+//
+// and produces the the corresponding method declaration to be used in the Env
+// trait.
+macro_rules! vmcaller_none_function_helper {
+    {fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
+    =>
+    {
+        fn $fn_id(&self, $($arg:$type),*) -> Result<$ret, Self::Error> {
+            <Self as VmCallerCheckedEnv>::$fn_id(self, &mut VmCaller::none(), $($arg),*)
+        }
+    };
+}
+
+// This is a callback macro that pattern-matches the token-tree passed by the
+// x-macro (call_macro_with_all_host_functions) and produces a suite of method
+// declarations, which it places in the body of the blanket impl of Env for
+// T:CheckedEnv
+macro_rules! impl_checked_env_for_vmcaller_checked_env {
+    {
+        $(
+            // This outer pattern matches a single 'mod' block of the token-tree
+            // passed from the x-macro to this macro. It is embedded in a `$()*`
+            // pattern-repetition matcher so that it will match all provided
+            // 'mod' blocks provided.
+            $(#[$mod_attr:meta])*
+            mod $mod_id:ident $mod_str:literal
+            {
+                $(
+                    // This inner pattern matches a single function description
+                    // inside a 'mod' block in the token-tree passed from the
+                    // x-macro to this macro. It is embedded in a `$()*`
+                    // pattern-repetition matcher so that it will match all such
+                    // descriptions.
+                    $(#[$fn_attr:meta])*
+                    { $fn_str:literal, fn $fn_id:ident $args:tt -> $ret:ty }
+                )*
+            }
+        )*
+    }
+
+    => // The part of the macro above this line is a matcher; below is its expansion.
+
+    {
+        // This macro expands to a single item: a blanket impl that makes all
+        // `VmCallerCheckedEnv` types automatically `CheckedEnv` types, just
+        // passing [`VmCaller::none()`] as their first argument.
+        impl<T:VmCallerCheckedEnv> $crate::CheckedEnv for T
+        {
+            type Error = <Self as VmCallerCheckedEnv>::Error;
+            $(
+                $(
+                    // This invokes the vmcaller_none_function_helper! macro above
+                    // passing only the relevant parts of the declaration
+                    // matched by the inner pattern above. It is embedded in two
+                    // nested `$()*` pattern-repetition expanders that
+                    // correspond to the pattern-repetition matchers in the
+                    // match section, but we ignore the structure of the 'mod'
+                    // block repetition-level from the outer pattern in the
+                    // expansion, flattening all functions from all 'mod' blocks
+                    // into the impl.
+                    vmcaller_none_function_helper!{fn $fn_id $args -> $ret}
+                )*
+            )*
+        }
+    };
+}
+
+// Here we invoke the x-macro passing
+// generate_checked_env_for_vmcaller_checked_env as its callback macro.
+call_macro_with_all_host_functions! { impl_checked_env_for_vmcaller_checked_env }
diff --git a/soroban-env-host/Cargo.toml b/soroban-env-host/Cargo.toml
index 5e998b2d..40bf66de 100644
--- a/soroban-env-host/Cargo.toml
+++ b/soroban-env-host/Cargo.toml
@@ -13,7 +13,7 @@ rust-version = "1.63"
 [dependencies]
 soroban-native-sdk-macros = { version = "0.0.5" }
 soroban-env-common = { version = "0.0.5", features = [ "std" ] }
-wasmi = { package = "soroban-wasmi", version = "0.11.0", optional = true }
+wasmi = { package = "soroban-wasmi", version = "0.16.0", optional = true }
 static_assertions = "1.1.0"
 im-rc = "15.0.0"
 num-bigint = "0.4"
diff --git a/soroban-env-host/src/events.rs b/soroban-env-host/src/events.rs
index 63e02f04..364bb466 100644
--- a/soroban-env-host/src/events.rs
+++ b/soroban-env-host/src/events.rs
@@ -189,24 +189,25 @@ impl From<wasmi::Error> for DebugError {
         // of Strings) that we're already eliding at this level, that we might
         // want to report for diagnostic purposes if we ever get dynamic strings
         // in the diagnostic buffer.
+        use wasmi::core::TrapCode::*;
         use wasmi::Error::*;
-        use wasmi::TrapCode::*;
         let code = match err {
-            Validation(_) => ScVmErrorCode::Validation,
-            Instantiation(_) => ScVmErrorCode::Instantiation,
-            Function(_) => ScVmErrorCode::Function,
+            // TODO: re-reconcile these cases with ScVmErrorCode cases
+            Module(_) => ScVmErrorCode::Validation,
+            Linker(_) | Instantiation(_) => ScVmErrorCode::Instantiation,
+            Func(_) => ScVmErrorCode::Function,
             Table(_) => ScVmErrorCode::Table,
             Memory(_) => ScVmErrorCode::Memory,
             Global(_) => ScVmErrorCode::Global,
-            Value(_) => ScVmErrorCode::Value,
-            Trap(wasmi::Trap::Host(err)) => {
+            Trap(trap) if trap.is_host() => {
+                let err = trap.into_host().expect("trapped HostError");
                 let status: Status = match err.downcast_ref::<HostError>() {
                     Some(he) => he.status,
                     None => ScUnknownErrorCode::General.into(),
                 };
                 return DebugError::new(status).msg("VM trapped with from host error");
             }
-            Trap(wasmi::Trap::Code(c)) => match c {
+            Trap(trap) => match trap.as_code().expect("trap code") {
                 Unreachable => ScVmErrorCode::TrapUnreachable,
                 MemoryAccessOutOfBounds => ScVmErrorCode::TrapMemoryAccessOutOfBounds,
                 TableAccessOutOfBounds => ScVmErrorCode::TrapTableAccessOutOfBounds,
@@ -219,13 +220,7 @@ impl From<wasmi::Error> for DebugError {
                 MemLimitExceeded => ScVmErrorCode::TrapMemLimitExceeded,
                 CpuLimitExceeded => ScVmErrorCode::TrapCpuLimitExceeded,
             },
-            Host(err) => {
-                let status: Status = match err.downcast_ref::<HostError>() {
-                    Some(he) => he.status,
-                    None => ScUnknownErrorCode::General.into(),
-                };
-                return DebugError::new(status).msg("VM returned host error");
-            }
+            _ => ScVmErrorCode::Unknown,
         };
         Self::new(code).msg(code.name())
     }
diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs
index f7f57d4a..650aeace 100644
--- a/soroban-env-host/src/host.rs
+++ b/soroban-env-host/src/host.rs
@@ -4,9 +4,12 @@
 use core::cell::RefCell;
 use core::cmp::Ordering;
 use core::fmt::Debug;
+
 use im_rc::{OrdMap, Vector};
 use num_bigint::Sign;
-use soroban_env_common::{EnvVal, TryConvert, TryFromVal, TryIntoVal, OK, UNKNOWN_ERROR};
+use soroban_env_common::{
+    EnvVal, TryConvert, TryFromVal, TryIntoVal, VmCaller, VmCallerCheckedEnv, OK, UNKNOWN_ERROR,
+};
 
 use soroban_env_common::xdr::{
     AccountId, ContractEvent, ContractEventBody, ContractEventType, ContractEventV0,
@@ -27,7 +30,6 @@ use crate::xdr::{
 use std::rc::Rc;
 
 use crate::host_object::{HostMap, HostObj, HostObject, HostObjectType, HostVal, HostVec};
-use crate::CheckedEnv;
 #[cfg(feature = "vm")]
 use crate::SymbolStr;
 #[cfg(feature = "vm")]
@@ -644,7 +646,10 @@ impl Host {
         contract: ScContractCode,
         id_preimage: Vec<u8>,
     ) -> Result<Object, HostError> {
-        let id_obj = self.compute_hash_sha256(self.add_host_object(id_preimage)?.into())?;
+        let id_obj = self.compute_hash_sha256(
+            &mut VmCaller::none(),
+            self.add_host_object(id_preimage)?.into(),
+        )?;
         self.create_contract_with_id(contract, id_obj)?;
         Ok(id_obj)
     }
@@ -735,8 +740,14 @@ impl Host {
                         let key = self.to_host_obj(k_obj)?.to_object();
                         let signature = self.to_host_obj(sig_obj)?.to_object();
                         //TODO: should create_contract_from_ed25519 return a RawVal instead of Object to avoid this conversion?
-                        self.create_contract_from_ed25519(contract, salt, key, signature)
-                            .map(|obj| <RawVal>::from(obj))
+                        self.create_contract_from_ed25519(
+                            &mut VmCaller::none(),
+                            contract,
+                            salt,
+                            key,
+                            signature,
+                        )
+                        .map(|obj| <RawVal>::from(obj))
                     })
                 } else {
                     Err(self.err_status_msg(
@@ -922,17 +933,18 @@ impl EnvBase for Host {
     }
 }
 
-impl CheckedEnv for Host {
+impl VmCallerCheckedEnv for Host {
+    type VmUserState = Host;
     type Error = HostError;
 
     // Notes on metering: covered by the components
-    fn log_value(&self, v: RawVal) -> Result<RawVal, HostError> {
+    fn log_value(&self, _vmcaller: &mut VmCaller<Host>, v: RawVal) -> Result<RawVal, HostError> {
         self.record_debug_event(DebugEvent::new().msg("log").arg(v))?;
         Ok(RawVal::from_void())
     }
 
     // Notes on metering: covered by the components
-    fn get_invoking_contract(&self) -> Result<Object, HostError> {
+    fn get_invoking_contract(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, HostError> {
         let frames = self.0.context.borrow();
         // the previous frame must exist and must be a contract
         let hash: Hash = if frames.len() >= 2 {
@@ -954,7 +966,12 @@ impl CheckedEnv for Host {
 
     // FIXME: the `cmp` method is not metered. Need a "metered" version (similar to metered_clone)
     // and use that.
-    fn obj_cmp(&self, a: RawVal, b: RawVal) -> Result<i64, HostError> {
+    fn obj_cmp(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        a: RawVal,
+        b: RawVal,
+    ) -> Result<i64, HostError> {
         let res = unsafe {
             self.unchecked_visit_val_obj(a, |ao| {
                 self.unchecked_visit_val_obj(b, |bo| Ok(ao.cmp(&bo)))
@@ -967,7 +984,12 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn contract_event(&self, topics: Object, data: RawVal) -> Result<RawVal, HostError> {
+    fn contract_event(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        topics: Object,
+        data: RawVal,
+    ) -> Result<RawVal, HostError> {
         let topics = self.event_topics_from_host_obj(topics)?;
         let data = self.from_host_val(data)?;
         self.record_contract_event(ContractEventType::Contract, topics, data)?;
@@ -975,38 +997,44 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by the components.
-    fn get_current_contract(&self) -> Result<Object, HostError> {
+    fn get_current_contract(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, HostError> {
         let hash: Hash = self.get_current_contract_id()?;
         Ok(self.add_host_object(<Vec<u8>>::from(hash.0))?.into())
     }
 
     // Notes on metering: covered by `add_host_object`.
-    fn obj_from_u64(&self, u: u64) -> Result<Object, HostError> {
+    fn obj_from_u64(&self, _vmcaller: &mut VmCaller<Host>, u: u64) -> Result<Object, HostError> {
         Ok(self.add_host_object(u)?.into())
     }
 
     // Notes on metering: covered by `visit_obj`.
-    fn obj_to_u64(&self, obj: Object) -> Result<u64, HostError> {
+    fn obj_to_u64(&self, _vmcaller: &mut VmCaller<Host>, obj: Object) -> Result<u64, HostError> {
         self.visit_obj(obj, |u: &u64| Ok(*u))
     }
 
     // Notes on metering: covered by `add_host_object`.
-    fn obj_from_i64(&self, i: i64) -> Result<Object, HostError> {
+    fn obj_from_i64(&self, _vmcaller: &mut VmCaller<Host>, i: i64) -> Result<Object, HostError> {
         Ok(self.add_host_object(i)?.into())
     }
 
     // Notes on metering: covered by `visit_obj`.
-    fn obj_to_i64(&self, obj: Object) -> Result<i64, HostError> {
+    fn obj_to_i64(&self, _vmcaller: &mut VmCaller<Host>, obj: Object) -> Result<i64, HostError> {
         self.visit_obj(obj, |i: &i64| Ok(*i))
     }
 
-    fn map_new(&self) -> Result<Object, HostError> {
+    fn map_new(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, HostError> {
         Ok(self
             .add_host_object(HostMap::new(self.0.budget.clone())?)?
             .into())
     }
 
-    fn map_put(&self, m: Object, k: RawVal, v: RawVal) -> Result<Object, HostError> {
+    fn map_put(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        m: Object,
+        k: RawVal,
+        v: RawVal,
+    ) -> Result<Object, HostError> {
         let k = self.associate_raw_val(k);
         let v = self.associate_raw_val(v);
         let mnew = self.visit_obj(m, move |hm: &HostMap| {
@@ -1017,7 +1045,12 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(mnew)?.into())
     }
 
-    fn map_get(&self, m: Object, k: RawVal) -> Result<RawVal, HostError> {
+    fn map_get(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        m: Object,
+        k: RawVal,
+    ) -> Result<RawVal, HostError> {
         let k = self.associate_raw_val(k);
         self.visit_obj(m, move |hm: &HostMap| {
             hm.get(&k)?
@@ -1026,7 +1059,12 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn map_del(&self, m: Object, k: RawVal) -> Result<Object, HostError> {
+    fn map_del(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        m: Object,
+        k: RawVal,
+    ) -> Result<Object, HostError> {
         let k = self.associate_raw_val(k);
         let mnew = self.visit_obj(m, |hm: &HostMap| {
             let mut mnew = hm.metered_clone(&self.0.budget)?;
@@ -1035,17 +1073,27 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(mnew)?.into())
     }
 
-    fn map_len(&self, m: Object) -> Result<RawVal, HostError> {
+    fn map_len(&self, _vmcaller: &mut VmCaller<Host>, m: Object) -> Result<RawVal, HostError> {
         let len = self.visit_obj(m, |hm: &HostMap| Ok(hm.len()))?;
         self.usize_to_rawval_u32(len)
     }
 
-    fn map_has(&self, m: Object, k: RawVal) -> Result<RawVal, HostError> {
+    fn map_has(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        m: Object,
+        k: RawVal,
+    ) -> Result<RawVal, HostError> {
         let k = self.associate_raw_val(k);
         self.visit_obj(m, move |hm: &HostMap| Ok(hm.contains_key(&k)?.into()))
     }
 
-    fn map_prev_key(&self, m: Object, k: RawVal) -> Result<RawVal, HostError> {
+    fn map_prev_key(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        m: Object,
+        k: RawVal,
+    ) -> Result<RawVal, HostError> {
         let k = self.associate_raw_val(k);
         self.visit_obj(m, |hm: &HostMap| {
             // OrdMap's `get_prev`/`get_next` return the previous/next key if the input key is not found.
@@ -1076,7 +1124,12 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn map_next_key(&self, m: Object, k: RawVal) -> Result<RawVal, HostError> {
+    fn map_next_key(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        m: Object,
+        k: RawVal,
+    ) -> Result<RawVal, HostError> {
         let k = self.associate_raw_val(k);
         self.visit_obj(m, |hm: &HostMap| {
             if let Some((pk, _)) = hm.get_next(&k)? {
@@ -1101,7 +1154,7 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn map_min_key(&self, m: Object) -> Result<RawVal, HostError> {
+    fn map_min_key(&self, _vmcaller: &mut VmCaller<Host>, m: Object) -> Result<RawVal, HostError> {
         self.visit_obj(m, |hm: &HostMap| {
             match hm.get_min()? {
                 Some((pk, pv)) => Ok(pk.to_raw()),
@@ -1110,7 +1163,7 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn map_max_key(&self, m: Object) -> Result<RawVal, HostError> {
+    fn map_max_key(&self, _vmcaller: &mut VmCaller<Host>, m: Object) -> Result<RawVal, HostError> {
         self.visit_obj(m, |hm: &HostMap| {
             match hm.get_max()? {
                 Some((pk, pv)) => Ok(pk.to_raw()),
@@ -1119,7 +1172,7 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn map_keys(&self, m: Object) -> Result<Object, HostError> {
+    fn map_keys(&self, _vmcaller: &mut VmCaller<Host>, m: Object) -> Result<Object, HostError> {
         let vec = self.visit_obj(m, |hm: &HostMap| {
             let mut vec = HostVec::new(self.0.budget.clone())?;
             for k in hm.keys()?.cloned() {
@@ -1130,7 +1183,7 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vec)?.into())
     }
 
-    fn map_values(&self, m: Object) -> Result<Object, HostError> {
+    fn map_values(&self, _vmcaller: &mut VmCaller<Host>, m: Object) -> Result<Object, HostError> {
         let vec = self.visit_obj(m, |hm: &HostMap| {
             let mut vec = HostVec::new(self.0.budget.clone())?;
             for k in hm.values()?.cloned() {
@@ -1141,7 +1194,7 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vec)?.into())
     }
 
-    fn vec_new(&self, c: RawVal) -> Result<Object, HostError> {
+    fn vec_new(&self, _vmcaller: &mut VmCaller<Host>, c: RawVal) -> Result<Object, HostError> {
         let capacity: usize = if c.is_void() {
             0
         } else {
@@ -1153,7 +1206,13 @@ impl CheckedEnv for Host {
             .into())
     }
 
-    fn vec_put(&self, v: Object, i: RawVal, x: RawVal) -> Result<Object, HostError> {
+    fn vec_put(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        i: RawVal,
+        x: RawVal,
+    ) -> Result<Object, HostError> {
         let i = self.u32_from_rawval_input("i", i)?;
         let x = self.associate_raw_val(x);
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
@@ -1165,12 +1224,22 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_get(&self, v: Object, i: RawVal) -> Result<RawVal, HostError> {
+    fn vec_get(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        i: RawVal,
+    ) -> Result<RawVal, HostError> {
         let i: usize = self.usize_from_rawval_u32_input("i", i)?;
         self.visit_obj(v, move |hv: &HostVec| hv.get(i).map(|hval| hval.to_raw()))
     }
 
-    fn vec_del(&self, v: Object, i: RawVal) -> Result<Object, HostError> {
+    fn vec_del(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        i: RawVal,
+    ) -> Result<Object, HostError> {
         let i = self.u32_from_rawval_input("i", i)?;
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             self.validate_index_lt_bound(i, hv.len())?;
@@ -1181,12 +1250,17 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_len(&self, v: Object) -> Result<RawVal, HostError> {
+    fn vec_len(&self, _vmcaller: &mut VmCaller<Host>, v: Object) -> Result<RawVal, HostError> {
         let len = self.visit_obj(v, |hv: &HostVec| Ok(hv.len()))?;
         self.usize_to_rawval_u32(len)
     }
 
-    fn vec_push_front(&self, v: Object, x: RawVal) -> Result<Object, HostError> {
+    fn vec_push_front(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        x: RawVal,
+    ) -> Result<Object, HostError> {
         let x = self.associate_raw_val(x);
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             let mut vnew = hv.metered_clone(&self.0.budget)?;
@@ -1196,7 +1270,11 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_pop_front(&self, v: Object) -> Result<Object, HostError> {
+    fn vec_pop_front(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+    ) -> Result<Object, HostError> {
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             let mut vnew = hv.metered_clone(&self.0.budget)?;
             vnew.pop_front().map(|_| vnew)
@@ -1204,7 +1282,12 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_push_back(&self, v: Object, x: RawVal) -> Result<Object, HostError> {
+    fn vec_push_back(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        x: RawVal,
+    ) -> Result<Object, HostError> {
         let x = self.associate_raw_val(x);
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             let mut vnew = hv.metered_clone(&self.0.budget)?;
@@ -1214,7 +1297,7 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_pop_back(&self, v: Object) -> Result<Object, HostError> {
+    fn vec_pop_back(&self, _vmcaller: &mut VmCaller<Host>, v: Object) -> Result<Object, HostError> {
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             let mut vnew = hv.metered_clone(&self.0.budget)?;
             vnew.pop_back().map(|_| vnew)
@@ -1222,15 +1305,21 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_front(&self, v: Object) -> Result<RawVal, HostError> {
+    fn vec_front(&self, _vmcaller: &mut VmCaller<Host>, v: Object) -> Result<RawVal, HostError> {
         self.visit_obj(v, |hv: &HostVec| hv.front().map(|hval| hval.to_raw()))
     }
 
-    fn vec_back(&self, v: Object) -> Result<RawVal, HostError> {
+    fn vec_back(&self, _vmcaller: &mut VmCaller<Host>, v: Object) -> Result<RawVal, HostError> {
         self.visit_obj(v, |hv: &HostVec| hv.back().map(|hval| hval.to_raw()))
     }
 
-    fn vec_insert(&self, v: Object, i: RawVal, x: RawVal) -> Result<Object, HostError> {
+    fn vec_insert(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        i: RawVal,
+        x: RawVal,
+    ) -> Result<Object, HostError> {
         let i = self.u32_from_rawval_input("i", i)?;
         let x = self.associate_raw_val(x);
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
@@ -1242,7 +1331,12 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_append(&self, v1: Object, v2: Object) -> Result<Object, HostError> {
+    fn vec_append(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v1: Object,
+        v2: Object,
+    ) -> Result<Object, HostError> {
         let mut vnew = self.visit_obj(v1, |hv: &HostVec| Ok(hv.metered_clone(&self.0.budget)?))?;
         let v2 = self.visit_obj(v2, |hv: &HostVec| Ok(hv.metered_clone(&self.0.budget)?))?;
         if v2.len() > u32::MAX as usize - vnew.len() {
@@ -1252,7 +1346,13 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_slice(&self, v: Object, start: RawVal, end: RawVal) -> Result<Object, HostError> {
+    fn vec_slice(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        start: RawVal,
+        end: RawVal,
+    ) -> Result<Object, HostError> {
         let start = self.u32_from_rawval_input("start", start)?;
         let end = self.u32_from_rawval_input("end", end)?;
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
@@ -1262,7 +1362,12 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn vec_first_index_of(&self, v: Object, x: RawVal) -> Result<RawVal, Self::Error> {
+    fn vec_first_index_of(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        x: RawVal,
+    ) -> Result<RawVal, Self::Error> {
         let x = self.associate_raw_val(x);
         self.visit_obj(v, |hv: &HostVec| {
             Ok(match hv.first_index_of(&x)? {
@@ -1272,7 +1377,12 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn vec_last_index_of(&self, v: Object, x: RawVal) -> Result<RawVal, Self::Error> {
+    fn vec_last_index_of(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        x: RawVal,
+    ) -> Result<RawVal, Self::Error> {
         let x = self.associate_raw_val(x);
         self.visit_obj(v, |hv: &HostVec| {
             Ok(match hv.last_index_of(&x)? {
@@ -1282,7 +1392,12 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn vec_binary_search(&self, v: Object, x: RawVal) -> Result<u64, Self::Error> {
+    fn vec_binary_search(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        x: RawVal,
+    ) -> Result<u64, Self::Error> {
         let x = self.associate_raw_val(x);
         self.visit_obj(v, |hv: &HostVec| {
             let res = hv.binary_search(&x)?;
@@ -1291,7 +1406,12 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by components
-    fn put_contract_data(&self, k: RawVal, v: RawVal) -> Result<RawVal, HostError> {
+    fn put_contract_data(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        k: RawVal,
+        v: RawVal,
+    ) -> Result<RawVal, HostError> {
         let key = self.contract_data_key_from_rawval(k)?;
         let data = LedgerEntryData::ContractData(ContractDataEntry {
             contract_id: self.get_current_contract_id()?,
@@ -1308,14 +1428,22 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by components
-    fn has_contract_data(&self, k: RawVal) -> Result<RawVal, HostError> {
+    fn has_contract_data(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        k: RawVal,
+    ) -> Result<RawVal, HostError> {
         let key = self.storage_key_from_rawval(k)?;
         let res = self.0.storage.borrow_mut().has(&key)?;
         Ok(RawVal::from_bool(res))
     }
 
     // Notes on metering: covered by components
-    fn get_contract_data(&self, k: RawVal) -> Result<RawVal, HostError> {
+    fn get_contract_data(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        k: RawVal,
+    ) -> Result<RawVal, HostError> {
         let key = self.storage_key_from_rawval(k)?;
         match self.0.storage.borrow_mut().get(&key)?.data {
             LedgerEntryData::ContractData(ContractDataEntry {
@@ -1331,7 +1459,11 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by components
-    fn del_contract_data(&self, k: RawVal) -> Result<RawVal, HostError> {
+    fn del_contract_data(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        k: RawVal,
+    ) -> Result<RawVal, HostError> {
         let key = self.contract_data_key_from_rawval(k)?;
         self.0.storage.borrow_mut().del(&key)?;
         Ok(().into())
@@ -1340,6 +1472,7 @@ impl CheckedEnv for Host {
     // Notes on metering: covered by the components.
     fn create_contract_from_ed25519(
         &self,
+        vmcaller: &mut VmCaller<Host>,
         v: Object,
         salt: Object,
         key: Object,
@@ -1359,9 +1492,9 @@ impl CheckedEnv for Host {
             self.charge_budget(CostType::BytesConcat, params.len() as u64)?;
             Ok(params)
         })?;
-        let hash = self.compute_hash_sha256(self.add_host_object(params)?.into())?;
+        let hash = self.compute_hash_sha256(vmcaller, self.add_host_object(params)?.into())?;
 
-        self.verify_sig_ed25519(hash, key, sig)?;
+        self.verify_sig_ed25519(vmcaller, hash, key, sig)?;
 
         let wasm = self.visit_obj(v, |b: &Vec<u8>| {
             Ok(ScContractCode::Wasm(
@@ -1374,7 +1507,12 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by the components.
-    fn create_contract_from_contract(&self, v: Object, salt: Object) -> Result<Object, HostError> {
+    fn create_contract_from_contract(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: Object,
+        salt: Object,
+    ) -> Result<Object, HostError> {
         let contract_id = self.get_current_contract_id()?;
         let salt = self.uint256_from_obj_input("salt", salt)?;
 
@@ -1390,6 +1528,7 @@ impl CheckedEnv for Host {
 
     fn create_token_from_ed25519(
         &self,
+        vmcaller: &mut VmCaller<Host>,
         salt: Object,
         key: Object,
         sig: Object,
@@ -1404,15 +1543,19 @@ impl CheckedEnv for Host {
         };
         // Another charge-after-work. Easier to get the num bytes this way.
         self.charge_budget(CostType::BytesConcat, params.len() as u64)?;
-        let hash = self.compute_hash_sha256(self.add_host_object(params)?.into())?;
+        let hash = self.compute_hash_sha256(vmcaller, self.add_host_object(params)?.into())?;
 
-        self.verify_sig_ed25519(hash, key, sig)?;
+        self.verify_sig_ed25519(vmcaller, hash, key, sig)?;
 
         let buf = self.id_preimage_from_ed25519(key_val, salt_val)?;
         self.create_contract_with_id_preimage(ScContractCode::Token, buf)
     }
 
-    fn create_token_from_contract(&self, salt: Object) -> Result<Object, HostError> {
+    fn create_token_from_contract(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        salt: Object,
+    ) -> Result<Object, HostError> {
         let contract_id = self.get_current_contract_id()?;
         let salt = self.uint256_from_obj_input("salt", salt)?;
         let buf = self.id_preimage_from_contract(contract_id, salt)?;
@@ -1420,7 +1563,13 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: here covers the args unpacking. The actual VM work is changed at lower layers.
-    fn call(&self, contract: Object, func: Symbol, args: Object) -> Result<RawVal, HostError> {
+    fn call(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        contract: Object,
+        func: Symbol,
+        args: Object,
+    ) -> Result<RawVal, HostError> {
         let args: Vec<RawVal> = self.visit_obj(args, |hv: &HostVec| {
             self.charge_budget(CostType::CallArgsUnpack, hv.len() as u64)?;
             Ok(hv.iter().map(|a| a.to_raw()).collect())
@@ -1429,8 +1578,14 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by the components.
-    fn try_call(&self, contract: Object, func: Symbol, args: Object) -> Result<RawVal, HostError> {
-        match self.call(contract, func, args) {
+    fn try_call(
+        &self,
+        vmcaller: &mut VmCaller<Host>,
+        contract: Object,
+        func: Symbol,
+        args: Object,
+    ) -> Result<RawVal, HostError> {
+        match self.call(vmcaller, contract, func, args) {
             Ok(rv) => Ok(rv),
             Err(e) => {
                 let evt = DebugEvent::new()
@@ -1442,14 +1597,14 @@ impl CheckedEnv for Host {
         }
     }
 
-    fn bigint_from_u64(&self, x: u64) -> Result<Object, HostError> {
+    fn bigint_from_u64(&self, _vmcaller: &mut VmCaller<Host>, x: u64) -> Result<Object, HostError> {
         Ok(self
             .add_host_object(MeteredBigInt::from_u64(self.0.budget.clone(), x)?)?
             .into())
     }
 
     // Notes on metering: visiting object is covered. Conversion from BigInt to u64 is free.
-    fn bigint_to_u64(&self, x: Object) -> Result<u64, HostError> {
+    fn bigint_to_u64(&self, _vmcaller: &mut VmCaller<Host>, x: Object) -> Result<u64, HostError> {
         self.visit_obj(x, |bi: &MeteredBigInt| {
             bi.to_u64()
                 .ok_or_else(|| self.err_conversion_into_rawval::<u64>(x.into()))
@@ -1457,14 +1612,14 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: new object adding is covered. Conversion from i64 to BigInt is free.
-    fn bigint_from_i64(&self, x: i64) -> Result<Object, HostError> {
+    fn bigint_from_i64(&self, _vmcaller: &mut VmCaller<Host>, x: i64) -> Result<Object, HostError> {
         Ok(self
             .add_host_object(MeteredBigInt::from_i64(self.0.budget.clone(), x)?)?
             .into())
     }
 
     // Notes on metering: visiting object is covered. Conversion from BigInt to i64 is free.
-    fn bigint_to_i64(&self, x: Object) -> Result<i64, HostError> {
+    fn bigint_to_i64(&self, _vmcaller: &mut VmCaller<Host>, x: Object) -> Result<i64, HostError> {
         self.visit_obj(x, |bi: &MeteredBigInt| {
             bi.to_i64()
                 .ok_or_else(|| self.err_conversion_into_rawval::<i64>(x.into()))
@@ -1473,7 +1628,12 @@ impl CheckedEnv for Host {
 
     // Notes on metering: fully covered.
     // Notes on calibration: use equal length objects to get the result upper bound.
-    fn bigint_add(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_add(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.add(b))
         })?;
@@ -1481,7 +1641,12 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering and calibration: see `bigint_add`
-    fn bigint_sub(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_sub(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.sub(b))
         })?;
@@ -1495,7 +1660,12 @@ impl CheckedEnv for Host {
     // - (32, 256]
     // - [256, )
     // As they use different algorithms that have different performance and involves different complexity of intermediate objects.
-    fn bigint_mul(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_mul(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.mul(b))
         })?;
@@ -1504,7 +1674,12 @@ impl CheckedEnv for Host {
 
     // Notes on metering and model calibration:
     // Use uneven length numbers for the upper bound measurement.
-    fn bigint_div(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_div(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| {
                 if b.is_zero() {
@@ -1516,7 +1691,12 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_rem(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_rem(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| {
                 if b.is_zero() {
@@ -1530,28 +1710,48 @@ impl CheckedEnv for Host {
 
     // Notes on metering and model calibration:
     // Use equal length numbers for the upper bound measurement.
-    fn bigint_and(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_and(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.bitand(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_or(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_or(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.bitor(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_xor(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_xor(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.bitxor(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_shl(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_shl(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.shl(b))
         })?;
@@ -1559,7 +1759,12 @@ impl CheckedEnv for Host {
     }
 
     // Notes on calibration: choose small y for the upper bound.
-    fn bigint_shr(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_shr(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.shr(b))
         })?;
@@ -1567,30 +1772,44 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by `visit_obj`. `is_zero` call is free.
-    fn bigint_is_zero(&self, x: Object) -> Result<RawVal, HostError> {
+    fn bigint_is_zero(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+    ) -> Result<RawVal, HostError> {
         self.visit_obj(x, |a: &MeteredBigInt| Ok(a.is_zero().into()))
     }
 
     // Notes on metering: covered by `visit_obj`. `neg` call is free.
-    fn bigint_neg(&self, x: Object) -> Result<Object, HostError> {
+    fn bigint_neg(&self, _vmcaller: &mut VmCaller<Host>, x: Object) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| Ok(a.neg()))?;
         Ok(self.add_host_object(res)?.into())
     }
 
     // Notes on metering: covered by `visit_obj`. `not` call is free.
-    fn bigint_not(&self, x: Object) -> Result<Object, HostError> {
+    fn bigint_not(&self, _vmcaller: &mut VmCaller<Host>, x: Object) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| Ok(a.not()))?;
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_gcd(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_gcd(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.gcd(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_lcm(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_lcm(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |b: &MeteredBigInt| a.lcm(b))
         })?;
@@ -1598,14 +1817,25 @@ impl CheckedEnv for Host {
     }
 
     // Note on calibration: pick y with all 1-bits to get the upper bound.
-    fn bigint_pow(&self, x: Object, y: Object) -> Result<Object, HostError> {
+    fn bigint_pow(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        y: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| {
             self.visit_obj(y, |e: &MeteredBigInt| a.pow(e))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_pow_mod(&self, p: Object, q: Object, m: Object) -> Result<Object, HostError> {
+    fn bigint_pow_mod(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        p: Object,
+        q: Object,
+        m: Object,
+    ) -> Result<Object, HostError> {
         let res = self.visit_obj(p, |a: &MeteredBigInt| {
             self.visit_obj(q, |exponent: &MeteredBigInt| {
                 self.visit_obj(m, |modulus: &MeteredBigInt| a.modpow(exponent, modulus))
@@ -1614,27 +1844,41 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_sqrt(&self, x: Object) -> Result<Object, HostError> {
+    fn bigint_sqrt(&self, _vmcaller: &mut VmCaller<Host>, x: Object) -> Result<Object, HostError> {
         let res = self.visit_obj(x, |a: &MeteredBigInt| a.sqrt())?;
         Ok(self.add_host_object(res)?.into())
     }
 
-    fn bigint_bits(&self, x: Object) -> Result<u64, HostError> {
+    fn bigint_bits(&self, _vmcaller: &mut VmCaller<Host>, x: Object) -> Result<u64, HostError> {
         self.visit_obj(x, |a: &MeteredBigInt| Ok(a.bits()))
     }
 
-    fn bigint_to_bytes_be(&self, x: Object) -> Result<Object, Self::Error> {
+    fn bigint_to_bytes_be(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+    ) -> Result<Object, Self::Error> {
         let sign_bytes = self.visit_obj(x, |a: &MeteredBigInt| a.to_bytes_be())?;
         Ok(self.add_host_object(sign_bytes.1)?.into())
     }
 
-    fn bigint_to_radix_be(&self, x: Object, radix: RawVal) -> Result<Object, Self::Error> {
+    fn bigint_to_radix_be(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        radix: RawVal,
+    ) -> Result<Object, Self::Error> {
         let r: u32 = self.u32_from_rawval_input("radix", radix)?;
         let sign_bytes = self.visit_obj(x, |a: &MeteredBigInt| a.to_radix_be(r))?;
         Ok(self.add_host_object(sign_bytes.1)?.into())
     }
 
-    fn bigint_from_bytes_be(&self, sign: RawVal, bytes: Object) -> Result<Object, Self::Error> {
+    fn bigint_from_bytes_be(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        sign: RawVal,
+        bytes: Object,
+    ) -> Result<Object, Self::Error> {
         let s = self.bigint_sign_from_rawval(sign)?;
         let res = self.visit_obj(bytes, |b: &Vec<u8>| {
             MeteredBigInt::from_bytes_be(self.0.budget.clone(), s, b)
@@ -1644,6 +1888,7 @@ impl CheckedEnv for Host {
 
     fn bigint_from_radix_be(
         &self,
+        _vmcaller: &mut VmCaller<Host>,
         sign: RawVal,
         buf: Object,
         radix: RawVal,
@@ -1664,7 +1909,11 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by components
-    fn serialize_to_bytes(&self, v: RawVal) -> Result<Object, HostError> {
+    fn serialize_to_bytes(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        v: RawVal,
+    ) -> Result<Object, HostError> {
         let scv = self.from_host_val(v)?;
         let mut buf = Vec::<u8>::new();
         scv.write_xdr(&mut buf)
@@ -1680,7 +1929,11 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by components
-    fn deserialize_from_bytes(&self, b: Object) -> Result<RawVal, HostError> {
+    fn deserialize_from_bytes(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        b: Object,
+    ) -> Result<RawVal, HostError> {
         let scv = self.visit_obj(b, |hv: &Vec<u8>| {
             self.charge_budget(CostType::ValDeser, hv.len() as u64)?;
             ScVal::read_xdr(&mut hv.as_slice())
@@ -1691,6 +1944,7 @@ impl CheckedEnv for Host {
 
     fn bytes_copy_to_linear_memory(
         &self,
+        vmcaller: &mut VmCaller<Host>,
         b: Object,
         b_pos: RawVal,
         lm_pos: RawVal,
@@ -1704,10 +1958,12 @@ impl CheckedEnv for Host {
             let b_pos = u32::try_from(b_pos)?;
             self.visit_obj(b, move |hv: &Vec<u8>| {
                 let range = self.valid_range_from_start_span_bound(b_pos, len, hv.len())?;
-                vm.with_memory_access(self, |mem| {
-                    self.charge_budget(CostType::VmMemCpy, hv.len() as u64)?;
-                    self.map_err(mem.set(pos, &hv.as_slice()[range]))
-                })?;
+                let mem = vm.get_memory(self)?;
+                self.charge_budget(CostType::VmMemCpy, hv.len() as u64)?;
+                self.map_err(
+                    mem.write(vmcaller.try_mut()?, pos as usize, &hv.as_slice()[range])
+                        .map_err(|me| wasmi::Error::Memory(me)),
+                )?;
                 Ok(().into())
             })
         }
@@ -1715,6 +1971,7 @@ impl CheckedEnv for Host {
 
     fn bytes_copy_from_linear_memory(
         &self,
+        vmcaller: &mut VmCaller<Host>,
         b: Object,
         b_pos: RawVal,
         lm_pos: RawVal,
@@ -1736,18 +1993,23 @@ impl CheckedEnv for Host {
             if end_idx > vnew.len() {
                 vnew.resize(end_idx, 0);
             }
-            vm.with_memory_access(self, |mem| {
-                self.charge_budget(CostType::VmMemCpy, len as u64)?;
-                Ok(self.map_err(
-                    mem.get_into(pos, &mut vnew.as_mut_slice()[b_pos as usize..end_idx]),
-                )?)
-            })?;
+            let mem = vm.get_memory(self)?;
+            self.charge_budget(CostType::VmMemCpy, len as u64)?;
+            self.map_err(
+                mem.read(
+                    vmcaller.try_mut()?,
+                    pos as usize,
+                    &mut vnew.as_mut_slice()[b_pos as usize..end_idx],
+                )
+                .map_err(|me| wasmi::Error::Memory(me)),
+            )?;
             Ok(self.add_host_object(vnew)?.into())
         }
     }
 
     fn bytes_new_from_linear_memory(
         &self,
+        vmcaller: &mut VmCaller<Host>,
         lm_pos: RawVal,
         len: RawVal,
     ) -> Result<Object, HostError> {
@@ -1757,21 +2019,29 @@ impl CheckedEnv for Host {
         {
             let VmSlice { vm, pos, len } = self.decode_vmslice(lm_pos, len)?;
             let mut vnew: Vec<u8> = vec![0; len as usize];
-            vm.with_memory_access(self, |mem| {
-                self.charge_budget(CostType::VmMemCpy, len as u64)?;
-                self.map_err(mem.get_into(pos, vnew.as_mut_slice()))
-            })?;
+            let mem = vm.get_memory(self)?;
+            self.charge_budget(CostType::VmMemCpy, len as u64)?;
+            self.map_err(
+                mem.read(vmcaller.try_ref()?, pos as usize, vnew.as_mut_slice())
+                    .map_err(|me| wasmi::Error::Memory(me)),
+            )?;
             Ok(self.add_host_object(vnew)?.into())
         }
     }
 
     // Notes on metering: covered by `add_host_object`
-    fn bytes_new(&self) -> Result<Object, HostError> {
+    fn bytes_new(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, HostError> {
         Ok(self.add_host_object(Vec::<u8>::new())?.into())
     }
 
     // Notes on metering: `get_mut` is free
-    fn bytes_put(&self, b: Object, i: RawVal, u: RawVal) -> Result<Object, HostError> {
+    fn bytes_put(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        b: Object,
+        i: RawVal,
+        u: RawVal,
+    ) -> Result<Object, HostError> {
         let i = self.usize_from_rawval_u32_input("i", i)?;
         let u = self.u8_from_rawval_input("u", u)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
@@ -1788,7 +2058,12 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: `get` is free
-    fn bytes_get(&self, b: Object, i: RawVal) -> Result<RawVal, HostError> {
+    fn bytes_get(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        b: Object,
+        i: RawVal,
+    ) -> Result<RawVal, HostError> {
         let i = self.usize_from_rawval_u32_input("i", i)?;
         self.visit_obj(b, |hv: &Vec<u8>| {
             hv.get(i)
@@ -1797,7 +2072,12 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn bytes_del(&self, b: Object, i: RawVal) -> Result<Object, HostError> {
+    fn bytes_del(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        b: Object,
+        i: RawVal,
+    ) -> Result<Object, HostError> {
         let i = self.u32_from_rawval_input("i", i)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
             self.validate_index_lt_bound(i, hv.len())?;
@@ -1810,13 +2090,18 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: `len` is free
-    fn bytes_len(&self, b: Object) -> Result<RawVal, HostError> {
+    fn bytes_len(&self, _vmcaller: &mut VmCaller<Host>, b: Object) -> Result<RawVal, HostError> {
         let len = self.visit_obj(b, |hv: &Vec<u8>| Ok(hv.len()))?;
         self.usize_to_rawval_u32(len)
     }
 
     // Notes on metering: `push` is free
-    fn bytes_push(&self, b: Object, u: RawVal) -> Result<Object, HostError> {
+    fn bytes_push(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        b: Object,
+        u: RawVal,
+    ) -> Result<Object, HostError> {
         let u = self.u8_from_rawval_input("u", u)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
             let mut vnew = hv.metered_clone(&self.0.budget)?;
@@ -1829,7 +2114,7 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: `pop` is free
-    fn bytes_pop(&self, b: Object) -> Result<Object, HostError> {
+    fn bytes_pop(&self, _vmcaller: &mut VmCaller<Host>, b: Object) -> Result<Object, HostError> {
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
             let mut vnew = hv.metered_clone(&self.0.budget)?;
             // Passing `len()` since worse case can cause reallocation.
@@ -1842,7 +2127,7 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: `first` is free
-    fn bytes_front(&self, b: Object) -> Result<RawVal, HostError> {
+    fn bytes_front(&self, _vmcaller: &mut VmCaller<Host>, b: Object) -> Result<RawVal, HostError> {
         self.visit_obj(b, |hv: &Vec<u8>| {
             hv.first()
                 .map(|u| Into::<RawVal>::into(Into::<u32>::into(*u)))
@@ -1851,7 +2136,7 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: `last` is free
-    fn bytes_back(&self, b: Object) -> Result<RawVal, HostError> {
+    fn bytes_back(&self, _vmcaller: &mut VmCaller<Host>, b: Object) -> Result<RawVal, HostError> {
         self.visit_obj(b, |hv: &Vec<u8>| {
             hv.last()
                 .map(|u| Into::<RawVal>::into(Into::<u32>::into(*u)))
@@ -1859,7 +2144,13 @@ impl CheckedEnv for Host {
         })
     }
 
-    fn bytes_insert(&self, b: Object, i: RawVal, u: RawVal) -> Result<Object, HostError> {
+    fn bytes_insert(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        b: Object,
+        i: RawVal,
+        u: RawVal,
+    ) -> Result<Object, HostError> {
         let i = self.u32_from_rawval_input("i", i)?;
         let u = self.u8_from_rawval_input("u", u)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
@@ -1872,7 +2163,12 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn bytes_append(&self, b1: Object, b2: Object) -> Result<Object, HostError> {
+    fn bytes_append(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        b1: Object,
+        b2: Object,
+    ) -> Result<Object, HostError> {
         let mut vnew = self.visit_obj(b1, |hv: &Vec<u8>| Ok(hv.metered_clone(&self.0.budget)?))?;
         let mut b2 = self.visit_obj(b2, |hv: &Vec<u8>| Ok(hv.metered_clone(&self.0.budget)?))?;
         if b2.len() > u32::MAX as usize - vnew.len() {
@@ -1883,7 +2179,13 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn bytes_slice(&self, b: Object, start: RawVal, end: RawVal) -> Result<Object, HostError> {
+    fn bytes_slice(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        b: Object,
+        start: RawVal,
+        end: RawVal,
+    ) -> Result<Object, HostError> {
         let start = self.u32_from_rawval_input("start", start)?;
         let end = self.u32_from_rawval_input("end", end)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
@@ -1894,30 +2196,56 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
-    fn hash_from_bytes(&self, x: Object) -> Result<Object, HostError> {
+    fn hash_from_bytes(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+    ) -> Result<Object, HostError> {
         todo!()
     }
 
-    fn hash_to_bytes(&self, x: Object) -> Result<Object, HostError> {
+    fn hash_to_bytes(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+    ) -> Result<Object, HostError> {
         todo!()
     }
 
-    fn public_key_from_bytes(&self, x: Object) -> Result<Object, HostError> {
+    fn public_key_from_bytes(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+    ) -> Result<Object, HostError> {
         todo!()
     }
 
-    fn public_key_to_bytes(&self, x: Object) -> Result<Object, HostError> {
+    fn public_key_to_bytes(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+    ) -> Result<Object, HostError> {
         todo!()
     }
 
     // Notes on metering: covered by components.
-    fn compute_hash_sha256(&self, x: Object) -> Result<Object, HostError> {
+    fn compute_hash_sha256(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+    ) -> Result<Object, HostError> {
         let hash = self.sha256_hash_from_bytes_input(x)?;
         Ok(self.add_host_object(hash)?.into())
     }
 
     // Notes on metering: covered by components.
-    fn verify_sig_ed25519(&self, x: Object, k: Object, s: Object) -> Result<RawVal, HostError> {
+    fn verify_sig_ed25519(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        x: Object,
+        k: Object,
+        s: Object,
+    ) -> Result<RawVal, HostError> {
         use ed25519_dalek::Verifier;
         let public_key = self.ed25519_pub_key_from_obj_input(k)?;
         let sig = self.signature_from_obj_input("sig", s)?;
@@ -1931,33 +2259,54 @@ impl CheckedEnv for Host {
     }
 
     // Notes on metering: covered by components.
-    fn account_get_low_threshold(&self, a: Object) -> Result<RawVal, Self::Error> {
+    fn account_get_low_threshold(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        a: Object,
+    ) -> Result<RawVal, Self::Error> {
         let threshold = self.load_account(a)?.thresholds.0[ThresholdIndexes::Low as usize];
         let threshold = Into::<u32>::into(threshold);
         Ok(threshold.into())
     }
 
     // Notes on metering: covered by components.
-    fn account_get_medium_threshold(&self, a: Object) -> Result<RawVal, Self::Error> {
+    fn account_get_medium_threshold(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        a: Object,
+    ) -> Result<RawVal, Self::Error> {
         let threshold = self.load_account(a)?.thresholds.0[ThresholdIndexes::Med as usize];
         let threshold = Into::<u32>::into(threshold);
         Ok(threshold.into())
     }
 
     // Notes on metering: covered by components.
-    fn account_get_high_threshold(&self, a: Object) -> Result<RawVal, Self::Error> {
+    fn account_get_high_threshold(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        a: Object,
+    ) -> Result<RawVal, Self::Error> {
         let threshold = self.load_account(a)?.thresholds.0[ThresholdIndexes::High as usize];
         let threshold = Into::<u32>::into(threshold);
         Ok(threshold.into())
     }
 
     // Notes on metering: covered by components.
-    fn account_exists(&self, a: Object) -> Result<RawVal, Self::Error> {
+    fn account_exists(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        a: Object,
+    ) -> Result<RawVal, Self::Error> {
         Ok(self.has_account(a)?.into())
     }
 
     // Notes on metering: some covered. The for loop and comparisons are free (for now).
-    fn account_get_signer_weight(&self, a: Object, s: Object) -> Result<RawVal, Self::Error> {
+    fn account_get_signer_weight(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+        a: Object,
+        s: Object,
+    ) -> Result<RawVal, Self::Error> {
         use xdr::{Signer, SignerKey};
 
         let target_signer = self.to_u256(s)?;
@@ -1988,25 +2337,28 @@ impl CheckedEnv for Host {
         }
     }
 
-    fn get_ledger_version(&self) -> Result<RawVal, Self::Error> {
+    fn get_ledger_version(&self, _vmcaller: &mut VmCaller<Host>) -> Result<RawVal, Self::Error> {
         self.with_ledger_info(|li| Ok(li.protocol_version.into()))
     }
 
-    fn get_ledger_sequence(&self) -> Result<RawVal, Self::Error> {
+    fn get_ledger_sequence(&self, _vmcaller: &mut VmCaller<Host>) -> Result<RawVal, Self::Error> {
         self.with_ledger_info(|li| Ok(li.sequence_number.into()))
     }
 
-    fn get_ledger_timestamp(&self) -> Result<Object, Self::Error> {
+    fn get_ledger_timestamp(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, Self::Error> {
         self.with_ledger_info(|li| Ok(self.add_host_object(li.timestamp)?.into()))
     }
 
-    fn get_ledger_network_passphrase(&self) -> Result<Object, Self::Error> {
+    fn get_ledger_network_passphrase(
+        &self,
+        _vmcaller: &mut VmCaller<Host>,
+    ) -> Result<Object, Self::Error> {
         Ok(self
             .with_ledger_info(|li| self.add_host_object(li.network_passphrase.clone()))?
             .into())
     }
 
-    fn get_current_call_stack(&self) -> Result<Object, HostError> {
+    fn get_current_call_stack(&self, _vmcaller: &mut VmCaller<Host>) -> Result<Object, HostError> {
         let frames = self.0.context.borrow();
 
         let get_host_val_tuple =
diff --git a/soroban-env-host/src/vm.rs b/soroban-env-host/src/vm.rs
index d0423e34..5b1d828b 100644
--- a/soroban-env-host/src/vm.rs
+++ b/soroban-env-host/src/vm.rs
@@ -12,124 +12,42 @@ mod dispatch;
 mod func_info;
 
 use crate::{budget::CostType, host::Frame, HostError};
-use std::{io::Cursor, ops::RangeInclusive, rc::Rc};
+use std::{cell::RefCell, io::Cursor, ops::RangeInclusive, rc::Rc};
 
 use super::{
     xdr::{Hash, ScVal, ScVec},
     Host, RawVal, Symbol,
 };
 use func_info::HOST_FUNCTIONS;
-use parity_wasm::elements::{self, Internal, Type};
 use soroban_env_common::{
     meta,
     xdr::{ReadXdr, ScEnvMetaEntry, ScHostFnErrorCode, ScVmErrorCode},
-};
-use wasmi::{
-    Externals, FuncInstance, ImportResolver, Module, ModuleInstance, ModuleRef, RuntimeArgs,
-    RuntimeValue, ValueType,
+    ConversionError,
 };
 
-impl wasmi::HostError for HostError {}
+use wasmi::{core::Value, Memory};
+use wasmi::{Engine, Instance, Linker, Module, StepMeter, Store};
 
-impl Externals for Host {
-    fn invoke_index(
-        &mut self,
-        index: usize,
-        args: RuntimeArgs,
-    ) -> Result<Option<wasmi::RuntimeValue>, wasmi::Trap> {
-        if index > HOST_FUNCTIONS.len() {
-            return Err(wasmi::TrapCode::TableAccessOutOfBounds.into());
-        }
-        let hf = &HOST_FUNCTIONS[index];
-        if hf.arity != args.len() {
-            return Err(wasmi::TrapCode::UnexpectedSignature.into());
-        }
-
-        let rv = (hf.dispatch)(self, args)?;
-        Ok(Some(rv))
-    }
+impl wasmi::core::HostError for HostError {}
 
+impl StepMeter for Host {
     fn max_insn_step(&self) -> u64 {
         256
     }
 
-    fn charge_cpu(&mut self, insns: u64) -> Result<(), wasmi::TrapCode> {
+    fn charge_cpu(&self, insns: u64) -> Result<(), wasmi::core::TrapCode> {
         // TODO reconcile TrapCode with HostError better.
         self.charge_budget(CostType::WasmInsnExec, insns)
-            .map_err(|_| wasmi::TrapCode::CpuLimitExceeded)
+            .map_err(|_| wasmi::core::TrapCode::CpuLimitExceeded)
     }
 
-    fn charge_mem(&mut self, bytes: u64) -> Result<(), wasmi::TrapCode> {
+    fn charge_mem(&self, bytes: u64) -> Result<(), wasmi::core::TrapCode> {
         self.charge_budget(CostType::WasmMemAlloc, bytes)
-            .map_err(|_| wasmi::TrapCode::MemLimitExceeded)
-    }
-}
-
-impl ImportResolver for Host {
-    fn resolve_func(
-        &self,
-        module_name: &str,
-        field_name: &str,
-        signature: &wasmi::Signature,
-    ) -> Result<wasmi::FuncRef, wasmi::Error> {
-        for (i, hf) in HOST_FUNCTIONS.iter().enumerate() {
-            if module_name == hf.mod_str && field_name == hf.fn_str {
-                if signature.params().len() != hf.arity
-                    || !signature.params().iter().all(|p| *p == ValueType::I64)
-                    || signature.return_type() != Some(ValueType::I64)
-                {
-                    return Err(wasmi::Error::Function(format!(
-                        "Bad imported function signature on {}.{}",
-                        module_name, field_name
-                    )));
-                }
-                return Ok(FuncInstance::alloc_host(signature.clone(), i));
-            }
-        }
-        Err(wasmi::Error::Function(format!(
-            "No such function: {}.{}",
-            module_name, field_name
-        )))
-    }
-
-    fn resolve_global(
-        &self,
-        module_name: &str,
-        field_name: &str,
-        _descriptor: &wasmi::GlobalDescriptor,
-    ) -> Result<wasmi::GlobalRef, wasmi::Error> {
-        Err(wasmi::Error::Global(format!(
-            "No such global: {}.{}",
-            module_name, field_name
-        )))
-    }
-
-    fn resolve_memory(
-        &self,
-        module_name: &str,
-        field_name: &str,
-        _descriptor: &wasmi::MemoryDescriptor,
-    ) -> Result<wasmi::MemoryRef, wasmi::Error> {
-        Err(wasmi::Error::Memory(format!(
-            "No such memory: {}.{}",
-            module_name, field_name
-        )))
-    }
-
-    fn resolve_table(
-        &self,
-        module_name: &str,
-        field_name: &str,
-        _descriptor: &wasmi::TableDescriptor,
-    ) -> Result<wasmi::TableRef, wasmi::Error> {
-        Err(wasmi::Error::Table(format!(
-            "No such table: {}.{}",
-            module_name, field_name
-        )))
+            .map_err(|_| wasmi::core::TrapCode::MemLimitExceeded)
     }
 }
 
-/// A [Vm] is a thin wrapper around an instance of [wasmi::ModuleRef]. Multiple
+/// A [Vm] is a thin wrapper around an instance of [wasmi::Module]. Multiple
 /// [Vm]s may be held in a single [Host], and each contains a single WASM module
 /// instantiation.
 ///
@@ -143,8 +61,12 @@ impl ImportResolver for Host {
 pub struct Vm {
     #[allow(dead_code)]
     pub(crate) contract_id: Hash,
-    elements_module: elements::Module,
-    instance: ModuleRef, // this is a cloneable Rc<ModuleInstance>
+    // TODO: consider moving store and possibly module to Host so they can be
+    // recycled across calls. Or possibly beyond, to be recycled across txs.
+    module: Module,
+    store: RefCell<Store<Host>>,
+    instance: Instance,
+    memory: Option<Memory>,
 }
 
 /// Minimal description of a single function defined in a WASM module.
@@ -156,7 +78,7 @@ pub struct VmFunction {
 }
 
 impl Vm {
-    fn check_meta_section(host: &Host, m: &elements::Module) -> Result<(), HostError> {
+    fn check_meta_section(host: &Host, m: &Module) -> Result<(), HostError> {
         // At present the supported interface-version range is always just a single
         // point, and it is hard-wired into the host as the current
         // `soroban_env_common` value [`meta::INTERFACE_VERSION`]. In the future when
@@ -215,9 +137,11 @@ impl Vm {
     ///   - Checks that the module contains an [meta::INTERFACE_VERSION] that
     ///     matches the host.
     ///   - Checks that the module has no floating point code or `start`
-    ///     function.
+    ///     function, or post-MVP wasm extensions.
     ///   - Instantiates the module, leaving it ready to accept function
     ///     invocations.
+    ///   - Looks up and caches its linear memory export named `memory`
+    ///     if it exists.
     ///
     /// This method is called automatically as part of [Host::invoke_function]
     /// and does not usually need to be called from outside the crate.
@@ -227,43 +151,67 @@ impl Vm {
         module_wasm_code: &[u8],
     ) -> Result<Rc<Self>, HostError> {
         host.charge_budget(CostType::VmInstantiation, module_wasm_code.len() as u64)?;
-        let elements_module: elements::Module =
-            host.map_err(elements::deserialize_buffer(module_wasm_code))?;
 
-        Self::check_meta_section(host, &elements_module)?;
+        let mut config = wasmi::Config::default();
+
+        // Turn off all optional wasm features.
+        config.wasm_multi_value(false);
+        config.wasm_mutable_global(false);
+        config.wasm_saturating_float_to_int(false);
+        config.wasm_sign_extension(false);
 
-        let module: Module =
-            host.map_err(Module::from_parity_wasm_module(elements_module.clone()))?;
-        host.map_err(module.deny_floating_point())?;
+        // This should always be true, and it enforces wasmi's notion of "deterministic only"
+        // execution, which excludes all floating point ops. Double check to be sure.
+        assert!(config.wasm_features().deterministic_only);
 
-        let not_started_instance = host.map_err(ModuleInstance::new(&module, host))?;
-        if not_started_instance.has_start() {
-            return Err(host.err_status_msg(
-                ScVmErrorCode::Instantiation,
-                "module contains disallowed start function",
-            ));
+        let engine = Engine::new(&config);
+        let module = host.map_err(Module::new(&engine, module_wasm_code))?;
+
+        Self::check_meta_section(host, &module)?;
+
+        let mut store = Store::new(&engine, host.clone());
+        let mut linker = <Linker<Host>>::new();
+
+        for hf in HOST_FUNCTIONS {
+            let func = (hf.wrap)(&mut store);
+            linker.define(hf.mod_str, hf.fn_str, func).map_err(|_| {
+                host.err_status_msg(ScVmErrorCode::Instantiation, "error defining host function")
+            })?;
         }
-        let instance = not_started_instance.assert_no_start();
+
+        let not_started_instance = host.map_err(linker.instantiate(&mut store, &module))?;
+
+        let instance = not_started_instance
+            .ensure_no_start(&mut store)
+            .map_err(|_| {
+                host.err_status_msg(
+                    ScVmErrorCode::Instantiation,
+                    "module contains disallowed start function",
+                )
+            })?;
+
+        let memory = if let Some(ext) = instance.get_export(&mut store, "memory") {
+            ext.into_memory()
+        } else {
+            None
+        };
+
+        let store = RefCell::new(store);
         Ok(Rc::new(Self {
             contract_id,
-            elements_module,
+            module,
+            store,
             instance,
+            memory,
         }))
     }
 
-    pub(crate) fn with_memory_access<F, U>(&self, host: &Host, f: F) -> Result<U, HostError>
-    where
-        F: FnOnce(&wasmi::MemoryRef) -> Result<U, HostError>,
-    {
-        match self.instance.export_by_name("memory") {
-            Some(ev) => match ev.as_memory() {
-                Some(mem) => f(mem),
-                None => Err(host.err_status_msg(
-                    ScVmErrorCode::Memory,
-                    "export name `memory` is not of memory type",
-                )),
-            },
-            None => Err(host.err_status_msg(ScVmErrorCode::Memory, "`memory` export not found")),
+    pub(crate) fn get_memory(&self, host: &Host) -> Result<Memory, HostError> {
+        match self.memory {
+            Some(mem) => Ok(mem),
+            None => {
+                Err(host.err_status_msg(ScVmErrorCode::Memory, "no linear memory named `memory`"))
+            }
         }
     }
 
@@ -277,32 +225,34 @@ impl Vm {
         host.with_frame(
             Frame::ContractVM(self.clone(), Symbol::from_str(func)),
             || {
-                let wasm_args: Vec<_> = args
+                let wasm_args: Vec<Value> = args
                     .iter()
-                    .map(|i| RuntimeValue::I64(i.get_payload() as i64))
+                    .map(|i| Value::I64(i.get_payload() as i64))
                     .collect();
+                let mut wasm_ret: [Value; 1] = [Value::I64(0)];
 
-                // Wasmi wants a mut& to an E:Externals value, which is reasonable but
-                // irrelevant (and awkward) in our case since our Host (which implements
-                // Externals) is a Rc<RefCell<...>> with interior mutability. So rather
-                // than propagate the irrelevant-and-awkward mut& requirement to our
-                // callers, we just clone Host (which is, again, just a refcounted
-                // pointer) into a mutable local variable here, and pass a &mut to that
-                // local.
-                let mut host = host.clone();
-
-                let wasm_res = self
-                    .instance
-                    .invoke_export(func, wasm_args.as_slice(), &mut host);
-                let wasm_ret = host.map_err(wasm_res)?;
-                if let Some(RuntimeValue::I64(ret)) = wasm_ret {
-                    Ok(RawVal::from_payload(ret as u64))
-                } else {
-                    Err(host.err_status_msg(
-                        ScVmErrorCode::TrapUnexpectedSignature,
-                        "contract function did not return an i64",
-                    ))
-                }
+                let ext = match self.instance.get_export(&*self.store.borrow(), func) {
+                    None => {
+                        return Err(
+                            host.err_status_msg(ScVmErrorCode::Unknown, "invoking unknown export")
+                        )
+                    }
+                    Some(e) => e,
+                };
+                let func = match ext.into_func() {
+                    None => {
+                        return Err(
+                            host.err_status_msg(ScVmErrorCode::Unknown, "export is not a function")
+                        )
+                    }
+                    Some(e) => e,
+                };
+                host.map_err(func.call(
+                    &mut *self.store.borrow_mut(),
+                    wasm_args.as_slice(),
+                    &mut wasm_ret,
+                ))?;
+                Ok(wasm_ret[0].try_into().ok_or(ConversionError)?)
             },
         )
     }
@@ -333,54 +283,23 @@ impl Vm {
 
     /// Returns a list of functions in the WASM module loaded into the [Vm].
     pub fn functions(&self) -> Vec<VmFunction> {
-        if let Some(export_section) = self.elements_module.export_section() {
-            let fn_import_count = self
-                .elements_module
-                .import_count(elements::ImportCountType::Function);
-            // A function in the export section points to a function in the
-            // function section, which references a type in the type section.
-            // The type contains the list of parameters.
-            export_section
-                .entries()
-                .iter()
-                .filter_map(|entry| match entry.internal() {
-                    Internal::Function(idx) => {
-                        let fn_type = self.elements_module.function_section().and_then(|fs| {
-                            // Function index space includes imported
-                            // functions. Imported functions are always
-                            // indexed prior to other functions and so
-                            // the index points into the space of the
-                            // imported functions and the module's
-                            // functions. To get the index of the
-                            // function in the function section, the
-                            // index is reduced by the import count.
-                            let fs_idx = (*idx as usize) - fn_import_count;
-                            fs.entries().get(fs_idx).and_then(|f| {
-                                self.elements_module.type_section().and_then(|ts| {
-                                    ts.types().get(f.type_ref() as usize).and_then(|t| match t {
-                                        Type::Function(ft) => Some(ft),
-                                    })
-                                })
-                            })
-                        });
-                        Some(VmFunction {
-                            name: entry.field().to_string(),
-                            param_count: fn_type.map_or(0, |fn_type| fn_type.params().len()),
-                            result_count: fn_type.map_or(0, |fn_type| fn_type.results().len()),
-                        })
-                    }
-                    _ => None,
+        let mut res = Vec::new();
+        for e in self.module.exports() {
+            if let wasmi::ExportItemKind::Func(f) = e.kind() {
+                res.push(VmFunction {
+                    name: e.name().to_string(),
+                    param_count: f.params().len(),
+                    result_count: f.results().len(),
                 })
-                .collect()
-        } else {
-            vec![]
+            }
         }
+        res
     }
 
-    fn module_custom_section(m: &elements::Module, name: impl AsRef<str>) -> Option<&[u8]> {
-        m.custom_sections().find_map(|s| {
-            if s.name() == name.as_ref() {
-                Some(s.payload())
+    fn module_custom_section(m: &Module, name: impl AsRef<str>) -> Option<&[u8]> {
+        m.custom_sections().iter().find_map(|s| {
+            if &*s.name == name.as_ref() {
+                Some(&*s.data)
             } else {
                 None
             }
@@ -390,6 +309,6 @@ impl Vm {
     /// Returns the raw bytes content of a named custom section from the WASM
     /// module loaded into the [Vm], or `None` if no such custom section exists.
     pub fn custom_section(&self, name: impl AsRef<str>) -> Option<&[u8]> {
-        Self::module_custom_section(&self.elements_module, name)
+        Self::module_custom_section(&self.module, name)
     }
 }
diff --git a/soroban-env-host/src/vm/dispatch.rs b/soroban-env-host/src/vm/dispatch.rs
index 5498b584..58332bb3 100644
--- a/soroban-env-host/src/vm/dispatch.rs
+++ b/soroban-env-host/src/vm/dispatch.rs
@@ -1,76 +1,11 @@
-use crate::{budget::CostType, CheckedEnv, Host, Object, RawVal, Symbol};
+use crate::{budget::CostType, Host, Object, RawVal, Symbol, VmCaller, VmCallerCheckedEnv};
 use soroban_env_common::call_macro_with_all_host_functions;
-use wasmi::{RuntimeArgs, RuntimeValue};
+use wasmi::core::{FromValue, Trap, TrapCode::UnexpectedSignature, Value};
 
 ///////////////////////////////////////////////////////////////////////////////
 /// X-macro use: dispatch functions
 ///////////////////////////////////////////////////////////////////////////////
 
-// This is a helper macro used only by generate_dispatch_functions below. It
-// consumes a token-tree of the form:
-//
-//  {$host:expr, $vmargs:expr, fn $fn_id:ident $args:tt }
-//
-// in each of 6 valid forms (with 0, 1, ... 6 possible arguments) and produces a
-// function-call expression that extracts and converts the correct number of
-// arguments from the $vmargs argument (a wasmi::RuntimeArgs structure) and
-// forwards them to the corresponding $host.$fn_id function (a method on Host).
-macro_rules! dispatch_function_helper {
-
-    {$host:expr, $vmargs:expr, fn $fn_id:ident()} =>
-    {$host.$fn_id()};
-
-    {$host:expr, $vmargs:expr, fn $fn_id:ident($a0:ident:$t0:ty)} =>
-    {$host.$fn_id($vmargs.nth_checked::<$t0>(0)?)};
-
-    {$host:expr, $vmargs:expr, fn $fn_id:ident($a0:ident:$t0:ty,
-                                                 $a1:ident:$t1:ty)} =>
-    {$host.$fn_id($vmargs.nth_checked::<$t0>(0)?,
-                  $vmargs.nth_checked::<$t1>(1)?)};
-
-    {$host:expr, $vmargs:expr, fn $fn_id:ident($a0:ident:$t0:ty,
-                                                 $a1:ident:$t1:ty,
-                                                 $a2:ident:$t2:ty)} =>
-    {$host.$fn_id($vmargs.nth_checked::<$t0>(0)?,
-                  $vmargs.nth_checked::<$t1>(1)?,
-                  $vmargs.nth_checked::<$t2>(2)?)};
-
-
-    {$host:expr, $vmargs:expr, fn $fn_id:ident($a0:ident:$t0:ty,
-                                                 $a1:ident:$t1:ty,
-                                                 $a2:ident:$t2:ty,
-                                                 $a3:ident:$t3:ty)} =>
-    {$host.$fn_id($vmargs.nth_checked::<$t0>(0)?,
-                  $vmargs.nth_checked::<$t1>(1)?,
-                  $vmargs.nth_checked::<$t2>(2)?,
-                  $vmargs.nth_checked::<$t3>(3)?)};
-
-    {$host:expr, $vmargs:expr, fn $fn_id:ident($a0:ident:$t0:ty,
-                                                 $a1:ident:$t1:ty,
-                                                 $a2:ident:$t2:ty,
-                                                 $a3:ident:$t3:ty,
-                                                 $a4:ident:$t4:ty)} =>
-    {$host.$fn_id($vmargs.nth_checked::<$t0>(0)?,
-                  $vmargs.nth_checked::<$t1>(1)?,
-                  $vmargs.nth_checked::<$t2>(2)?,
-                  $vmargs.nth_checked::<$t3>(3)?,
-                  $vmargs.nth_checked::<$t4>(4)?)};
-
-
-    {$host:expr, $vmargs:expr, fn $fn_id:ident($a0:ident:$t0:ty,
-                                                 $a1:ident:$t1:ty,
-                                                 $a2:ident:$t2:ty,
-                                                 $a3:ident:$t3:ty,
-                                                 $a4:ident:$t4:ty,
-                                                 $a5:ident:$t5:ty)} =>
-    {$host.$fn_id($vmargs.nth_checked::<$t0>(0)?,
-                  $vmargs.nth_checked::<$t1>(1)?,
-                  $vmargs.nth_checked::<$t2>(2)?,
-                  $vmargs.nth_checked::<$t3>(3)?,
-                  $vmargs.nth_checked::<$t4>(4)?,
-                  $vmargs.nth_checked::<$t5>(5)?)};
-}
-
 // This is a callback macro that pattern-matches the token-tree passed by the
 // x-macro (call_macro_with_all_host_functions) and produces a suite of
 // dispatch-function definitions.
@@ -91,7 +26,7 @@ macro_rules! generate_dispatch_functions {
                     // pattern-repetition matcher so that it will match all such
                     // descriptions.
                     $(#[$fn_attr:meta])*
-                    { $fn_str:literal, fn $fn_id:ident $args:tt -> $ret:ty }
+                    { $fn_str:literal, fn $fn_id:ident ($($arg:ident:$type:ty),*) -> $ret:ty }
                 )*
             }
         )*
@@ -105,28 +40,47 @@ macro_rules! generate_dispatch_functions {
         // to forward calls to the host.
         $(
             $(
-                // This defines a function containing a body that includes the
-                // expansion of a macro call to the dispatch_function_helper!
-                // macro above, called with some expressions from the enclosing
-                // function (host and vmargs) as well as the relevant parts of
-                // the token-tree function declaration matched by the inner
-                // pattern above. It is embedded in two nested `$()*`
-                // pattern-repetition expanders that correspond to the
-                // pattern-repetition matchers in the match section, but we
-                // ignore the structure of the 'mod' block repetition-level from
-                // the outer pattern in the expansion, flattening all functions
-                // from all 'mod' blocks into a set of functions.
+                // This defines a "dispatch function" that does several things:
+                //
+                //  1. charges the budget for the call, failing if over budget.
+                //  2. attempts to convert incoming wasmi i64 args to RawVals or
+                //     RawVal-wrappers expected by host functions, failing if
+                //     any conversions fail.
+                //  3. calls the host function
+                //  4. checks the result is Ok, or traps the VM on Err
+                //  5. converts the result back to an i64 for wasmi
+                //
+                // It is embedded in two nested `$()*` pattern-repetition
+                // expanders that correspond to the pattern-repetition matchers
+                // in the match section, but we ignore the structure of the
+                // 'mod' block repetition-level from the outer pattern in the
+                // expansion, flattening all functions from all 'mod' blocks
+                // into a set of functions.
                 $(#[$fn_attr])*
-                pub(crate) fn $fn_id(host: &mut Host, _vmargs: RuntimeArgs) ->
-                    Result<RuntimeValue, wasmi::Trap>
+                pub(crate) fn $fn_id(caller: wasmi::Caller<Host>, $($arg:i64),*) ->
+                    Result<(i64,), Trap>
                 {
                     // Notes on metering: a flat charge per host function invocation.
                     // This does not account for the actual work being done in those functions,
                     // which are accounted for individually at the operation level.
-                    // This is analogous to a flat toll charge for getting on the highway,
-                    // whereas the actual work are the tickets to the attractions.
-                    host.charge_budget(CostType::HostFunction, _vmargs.len() as u64)?;
-                    Ok(dispatch_function_helper!{host, _vmargs, fn $fn_id $args }?.into())
+                    let host = caller.host_data().clone();
+                    host.charge_budget(CostType::HostFunction, 1)?;
+                    let mut vmcaller = VmCaller(Some(caller));
+                    // The odd / seemingly-redundant use of `wasmi::Value` here
+                    // as intermediates -- rather than just passing RawVals --
+                    // has to do with the fact that some host functions are
+                    // typed as receiving or returning plain _non-Rawval_ i64 or
+                    // u64 values. So the call here has to be able to massage
+                    // both types into and out of i64, and `wasmi::Value`
+                    // happens to be a natural switching point for that: we have
+                    // conversions to and from both RawVal and i64 / u64 for
+                    // wasmi::Value.
+                    let res: Value = host.$fn_id(&mut vmcaller, $(<$type as FromValue>::from_value(Value::I64($arg)).ok_or(UnexpectedSignature)?),*)?.into();
+                    if let Value::I64(v) = res {
+                        Ok((v,))
+                    } else {
+                        Err(UnexpectedSignature.into())
+                    }
                 }
             )*
         )*
diff --git a/soroban-env-host/src/vm/func_info.rs b/soroban-env-host/src/vm/func_info.rs
index 73b3a464..f9bdc033 100644
--- a/soroban-env-host/src/vm/func_info.rs
+++ b/soroban-env-host/src/vm/func_info.rs
@@ -1,7 +1,7 @@
 use super::dispatch;
 use crate::Host;
 use soroban_env_common::call_macro_with_all_host_functions;
-use wasmi::{RuntimeArgs, RuntimeValue};
+use wasmi::{Func, Store};
 
 pub(crate) struct HostFuncInfo {
     /// String name of the WASM module this host function is importable from.
@@ -11,33 +11,16 @@ pub(crate) struct HostFuncInfo {
     /// as.
     pub(crate) fn_str: &'static str,
 
-    /// Number of u64 arguments the host function takes.
-    pub(crate) arity: usize,
-
-    /// Dispatch function for this host function, that takes a Host and some
-    /// RuntimeArgs, unpacks and converts the appropriate set of arguments from
-    /// RuntimeArgs, and calls the corresponding method on Host.
-    pub(crate) dispatch: fn(&mut Host, RuntimeArgs) -> Result<RuntimeValue, wasmi::Trap>,
+    /// Function that takes a wasmi::Store and _wraps_ a dispatch function
+    /// for this host function, with the specific type of the dispatch function,
+    /// into a Func in the Store.
+    pub(crate) wrap: fn(&mut Store<Host>) -> Func,
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 /// X-macro use: static HOST_FUNCTIONS array of HostFuncInfo
 ///////////////////////////////////////////////////////////////////////////////
 
-// This is a helper macro that matches simple ident:ty argument list token-trees
-// and returns a literal token that is the arity (number of arguments) in the
-// list. It is used to convert the supplied token-tree pattern to an arity number
-// stored in the HostFuncInfo.
-macro_rules! arity_helper {
-    { () } => { 0 };
-    { ($a0:ident:$t0:ty) } => { 1 };
-    { ($a0:ident:$t0:ty, $a1:ident:$t1:ty) } => { 2 };
-    { ($a0:ident:$t0:ty, $a1:ident:$t1:ty, $a2:ident:$t2:ty) } => { 3 };
-    { ($a0:ident:$t0:ty, $a1:ident:$t1:ty, $a2:ident:$t2:ty, $a3:ident:$t3:ty) } => { 4 };
-    { ($a0:ident:$t0:ty, $a1:ident:$t1:ty, $a2:ident:$t2:ty, $a3:ident:$t3:ty, $a4:ident:$t4:ty) } => { 5 };
-    { ($a0:ident:$t0:ty, $a1:ident:$t1:ty, $a2:ident:$t2:ty, $a3:ident:$t3:ty, $a4:ident:$t4:ty, $a5:ident:$t5:ty) } => { 6 };
-}
-
 // This is a callback macro that pattern-matches the token-tree passed by the
 // x-macro (call_macro_with_all_host_functions) and produces a suite of
 // dispatch-function definitions.
@@ -94,8 +77,7 @@ macro_rules! generate_host_function_infos {
                     HostFuncInfo {
                         mod_str: $mod_str,
                         fn_str: $fn_id,
-                        arity: arity_helper!{$args},
-                        dispatch: dispatch::$func_id,
+                        wrap: |store| Func::wrap(store, dispatch::$func_id),
                     },
                 )*
             )*


Test patch:
diff --git a/soroban-env-host/src/test/budget_metering.rs b/soroban-env-host/src/test/budget_metering.rs
index 06a72ce0..3640ace5 100644
--- a/soroban-env-host/src/test/budget_metering.rs
+++ b/soroban-env-host/src/test/budget_metering.rs
@@ -45,17 +45,17 @@ fn vm_hostfn_invocation() -> Result<(), HostError> {
         .enable_model(CostType::HostFunction);
 
     let obj = host.test_bin_obj(&dummy_id)?;
-    // `vec_err` is a test contract function which calls `vec_new` (1 param)
-    // and `vec_put` (3 params) so total 4 inputs to the budget from `CostType::HostFunction`.
+    // `vec_err` is a test contract function which calls `vec_new` (1 call)
+    // and `vec_put` (1 call) so total input of 2 to the budget from `CostType::HostFunction`.
     let sym = Symbol::from_str("vec_err");
     let args = host.test_vec_obj::<u32>(&[1])?;
 
     // try_call
     host.try_call(obj.to_object(), sym.into(), args.clone().into())?;
     host.get_budget(|budget| {
-        assert_eq!(budget.get_input(CostType::HostFunction), 4);
-        assert_eq!(budget.get_cpu_insns_count(), 40);
-        assert_eq!(budget.get_mem_bytes_count(), 4);
+        assert_eq!(budget.get_input(CostType::HostFunction), 2);
+        assert_eq!(budget.get_cpu_insns_count(), 20);
+        assert_eq!(budget.get_mem_bytes_count(), 2);
     });
 
     Ok(())
diff --git a/soroban-env-host/src/test/bytes.rs b/soroban-env-host/src/test/bytes.rs
index 190805e5..19d125bf 100644
--- a/soroban-env-host/src/test/bytes.rs
+++ b/soroban-env-host/src/test/bytes.rs
@@ -166,7 +166,7 @@ fn bytes_xdr_roundtrip() -> Result<(), HostError> {
 
 #[cfg(feature = "vm")]
 #[test]
-fn invoke_memcpy() -> Result<(), HostError> {
+fn linear_memory_operations() -> Result<(), HostError> {
     use crate::xdr::ScContractCode;
     use crate::{budget::Budget, host::metered_map::MeteredOrdMap};
 
diff --git a/soroban-test-wasms/wasm-workspace/Cargo.toml b/soroban-test-wasms/wasm-workspace/Cargo.toml
index 83427154..9c480a0b 100644
--- a/soroban-test-wasms/wasm-workspace/Cargo.toml
+++ b/soroban-test-wasms/wasm-workspace/Cargo.toml
@@ -38,7 +38,7 @@ stellar-xdr = { git = "https://github.com/stellar/rs-stellar-xdr", rev = "fee9a4
 
 # You should actually be able to set this to be any existing version of wasmi because we are
 # not building the SDK in host mode, so the "vm" feature is off.
-wasmi = { package = "soroban-wasmi", git = "https://github.com/stellar/wasmi", rev = "7b1f2355" }
+wasmi = { package = "soroban-wasmi", git = "https://github.com/stellar/wasmi", rev = "3bda8032" }
 
 # The following SDK patch lines must denote an SDK version that is _compatible_
 # with the current version of soroban-env-guest and soroban-env-common, as found