Repository: stellar/rs-soroban-env
Pull request #1078 (2023-09-20 14:55:33)

Add redundant "next" dependencies
### What

Some crates have not exhaustively listed all its "feature=next" dependencies. 
Even if some of those "next" are redundant, i.e., they may only underneath differ by `stellar-xdr`'s `curr/next`, which is already covered by `soroban-env-common`. I think it's still good to list them out explicitly, since it's possible those dependencies may have more divergence between their curr and next in the future.

### Why

[TODO: Why this change is being made. Include any context required to understand the why.]

### Known limitations

[TODO or N/A]



Patch:
diff --git a/.github/workflows/check-bump-interface-version.yml b/.github/workflows/check-bump-interface-version.yml
index e2cb0adb..303edae8 100644
--- a/.github/workflows/check-bump-interface-version.yml
+++ b/.github/workflows/check-bump-interface-version.yml
@@ -22,4 +22,4 @@ jobs:
     - name: "check soroban-test-wasms/wasm-workspace/opt/*.wasm were modified"
       uses: brettcannon/check-for-changed-files@36aaa76474fc979db4e19e44c05e8e978759ac01
       with:
-        file-pattern: "soroban-test-wasms/wasm-workspace/opt/*.wasm"
+        file-pattern: "soroban-test-wasms/wasm-workspace/opt/curr/*.wasm"
diff --git a/.gitignore b/.gitignore
index 60a6eb3c..54d550f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 /target
 .vscode
 /vendor
+lcov.info
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index d3cfbb76..6a38d8e0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1337,7 +1337,7 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
 
 [[package]]
 name = "soroban-bench-utils"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "perf-event",
  "soroban-env-common",
@@ -1346,7 +1346,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-env-common"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "arbitrary",
  "crate-git-revision",
@@ -1363,7 +1363,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-env-guest"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "soroban-env-common",
  "static_assertions",
@@ -1371,7 +1371,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-env-host"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "backtrace",
  "bytes-lit",
@@ -1408,7 +1408,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-env-macros"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "itertools",
  "proc-macro2",
@@ -1421,7 +1421,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-native-sdk-macros"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "itertools",
  "proc-macro2",
@@ -1431,7 +1431,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-synth-wasm"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "expect-test",
  "soroban-env-common",
@@ -1442,7 +1442,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-test-wasms"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 
 [[package]]
 name = "soroban-wasmi"
@@ -1490,7 +1490,7 @@ dependencies = [
 [[package]]
 name = "stellar-xdr"
 version = "20.0.0-rc1"
-source = "git+https://github.com/stellar/rs-stellar-xdr?rev=d5ce0c9e7aa83461773a6e81662067f35d39e4c1#d5ce0c9e7aa83461773a6e81662067f35d39e4c1"
+source = "git+https://github.com/stellar/rs-stellar-xdr?rev=c9d24eb4fae1f9968f6620e06750c1fce7a5d6c3#c9d24eb4fae1f9968f6620e06750c1fce7a5d6c3"
 dependencies = [
  "arbitrary",
  "base64 0.13.1",
@@ -1575,7 +1575,7 @@ dependencies = [
 
 [[package]]
 name = "test_no_std"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "soroban-env-common",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index b7d3da9c..649ef78f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,19 +16,19 @@ members = [
 exclude = ["soroban-test-wasms/wasm-workspace"]
 
 [workspace.package]
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 
 [workspace.dependencies]
-soroban-env-common = { version = "20.0.0-rc1", path = "soroban-env-common", default-features = false }
-soroban-env-guest = { version = "20.0.0-rc1", path = "soroban-env-guest" }
-soroban-env-host = { version = "20.0.0-rc1", path = "soroban-env-host" }
-soroban-env-macros = { version = "20.0.0-rc1", path = "soroban-env-macros" }
-soroban-native-sdk-macros = { version = "20.0.0-rc1", path = "soroban-native-sdk-macros" }
+soroban-env-common = { version = "20.0.0-rc2", path = "soroban-env-common", default-features = false }
+soroban-env-guest = { version = "20.0.0-rc2", path = "soroban-env-guest" }
+soroban-env-host = { version = "20.0.0-rc2", path = "soroban-env-host" }
+soroban-env-macros = { version = "20.0.0-rc2", path = "soroban-env-macros" }
+soroban-native-sdk-macros = { version = "20.0.0-rc2", path = "soroban-native-sdk-macros" }
 
 [workspace.dependencies.stellar-xdr]
 version = "20.0.0-rc1"
 git = "https://github.com/stellar/rs-stellar-xdr"
-rev = "d5ce0c9e7aa83461773a6e81662067f35d39e4c1"
+rev = "c9d24eb4fae1f9968f6620e06750c1fce7a5d6c3"
 default-features = false
 
 [workspace.dependencies.wasmi]
diff --git a/README.md b/README.md
index 8c1de417..0e6ba789 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 Rust contract-environment interface and (optional) host implementation for Soroban.
 
-The `soroban-env-common` crate contains elements of the shared environment-interface between smart contract guest and host: the `Env` trait that defines the set of available environment functions as well as the `Val` type that can pass back and forth through the WASM calling convention. Additionally small wrappers around subtypes of `Val` are included: `Object`, `Symbol`, `Status`, etc.
+The `soroban-env-common` crate contains elements of the shared environment-interface between smart contract guest and host: the `Env` trait that defines the set of available environment functions as well as the `Val` type that can pass back and forth through the WASM calling convention. Additionally small wrappers around subtypes of `Val` are included: `Object`, `Symbol`, `Error`, etc.
 
 The `soroban-env-guest` crate contains the guest-side _stub implementation_ of the environment interface called `Guest` dependent on extern fns provided by the host implementation. This can be used in a WASM runtime that provides the extern fns.
 
diff --git a/soroban-env-common/Cargo.toml b/soroban-env-common/Cargo.toml
index 05465257..6a49ffe3 100644
--- a/soroban-env-common/Cargo.toml
+++ b/soroban-env-common/Cargo.toml
@@ -33,7 +33,7 @@ std = ["stellar-xdr/std", "stellar-xdr/base64"]
 serde = ["dep:serde", "stellar-xdr/serde"]
 wasmi = ["dep:wasmi"]
 testutils = ["dep:arbitrary", "stellar-xdr/arbitrary"]
-next = ["stellar-xdr/next"]
+next = ["stellar-xdr/next", "soroban-env-macros/next"]
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/soroban-env-common/env.json b/soroban-env-common/env.json
index 31936347..c70d7696 100644
--- a/soroban-env-common/env.json
+++ b/soroban-env-common/env.json
@@ -30,13 +30,6 @@
                 },
                 {
                     "export": "0",
-                    "name": "get_invoking_contract",
-                    "args": [],
-                    "return": "AddressObject",
-                    "docs": "Get the address object of the contract which invoked the running contract. Traps if the running contract was not invoked by a contract."
-                },
-                {
-                    "export": "1",
                     "name": "obj_cmp",
                     "args": [
                         {
@@ -52,7 +45,7 @@
                     "docs": "Compare two objects, or at least one object to a non-object, structurally. Returns -1 if a<b, 1 if a>b, or 0 if a==b."
                 },
                 {
-                    "export": "2",
+                    "export": "1",
                     "name": "contract_event",
                     "args": [
                         {
@@ -68,35 +61,28 @@
                     "docs": "Records a contract event. `topics` is expected to be a `SCVec` with length <= 4 that cannot contain `Vec`, `Map`, or `Bytes` with length > 32."
                 },
                 {
-                    "export": "3",
+                    "export": "2",
                     "name": "get_ledger_version",
                     "args": [],
                     "return": "U32Val",
                     "docs": "Return the protocol version of the current ledger as a u32."
                 },
                 {
-                    "export": "4",
+                    "export": "3",
                     "name": "get_ledger_sequence",
                     "args": [],
                     "return": "U32Val",
                     "docs": "Return the sequence number of the current ledger as a u32."
                 },
                 {
-                    "export": "5",
+                    "export": "4",
                     "name": "get_ledger_timestamp",
                     "args": [],
                     "return": "U64Val",
                     "docs": "Return the timestamp number of the current ledger as a u64."
                 },
                 {
-                    "export": "6",
-                    "name": "get_current_call_stack",
-                    "args": [],
-                    "return": "VecObject",
-                    "docs": "Returns the full call stack from the first contract call to the current one as a vector of vectors, where the inside vector contains the contract id as Hash, and a function as a Symbol."
-                },
-                {
-                    "export": "7",
+                    "export": "5",
                     "name": "fail_with_error",
                     "args": [
                         {
@@ -108,21 +94,21 @@
                     "docs": "Causes the currently executing contract to fail immediately with a provided error code, which must be of error-type `ScErrorType::Contract`. Does not actually return."
                 },
                 {
-                    "export": "8",
+                    "export": "6",
                     "name": "get_ledger_network_id",
                     "args": [],
                     "return": "BytesObject",
                     "docs": "Return the network id (sha256 hash of network passphrase) of the current ledger as `Bytes`. The value is always 32 bytes in length."
                 },
                 {
-                    "export": "9",
+                    "export": "7",
                     "name": "get_current_contract_address",
                     "args": [],
                     "return": "AddressObject",
                     "docs": "Get the Address object for the current contract."
                 },
                 {
-                    "export": "a",
+                    "export": "8",
                     "name": "get_max_expiration_ledger",
                     "args": [],
                     "return": "U32Val",
@@ -1178,7 +1164,7 @@
                         }
                     ],
                     "return": "u64",
-                    "docs": "Binary search a sorted vector for a given element. If it exists, the high-32 bits of the return value is 0x0001 and the low-32 bits contain the u32 index of the element. If it does not exist, the high-32 bits of the return value is 0x0000 and the low-32 bits contain the u32 index at which the element would need to be inserted into the vector to maintain sorted order."
+                    "docs": "Binary search a sorted vector for a given element. If it exists, the high 32 bits of the return value is 0x0000_0001 and the low 32 bits contain the u32 index of the element. If it does not exist, the high 32 bits of the return value is 0x0000_0000 and the low-32 bits contain the u32 index at which the element would need to be inserted into the vector to maintain sorted order."
                 },
                 {
                     "export": "g",
@@ -1474,7 +1460,7 @@
                         }
                     ],
                     "return": "Val",
-                    "docs": "Calls a function in another contract with arguments contained in vector `args`, returning either the result of the called function or an ScError if the called function failed."
+                    "docs": "Calls a function in another contract with arguments contained in vector `args`, returning either the result of the called function or an `Error` if the called function failed. The returned error is either a custom `ContractError` that the called contract returns explicitly, or an error with type `Context` and code `InvalidAction` in case of any other error in the called contract (such as a host function failure that caused a trap). `try_call` might trap in a few scenarios where the error can't be meaningfully recovered from, such as running out of budget."
                 }
             ]
         },
diff --git a/soroban-env-common/src/arbitrary.rs b/soroban-env-common/src/arbitrary.rs
index 1f15f4bc..f20a24b7 100644
--- a/soroban-env-common/src/arbitrary.rs
+++ b/soroban-env-common/src/arbitrary.rs
@@ -1,7 +1,5 @@
 //! Implementations of [`Arbitrary`] for contract types.
 
-#![cfg(feature = "testutils")]
-
 extern crate alloc;
 
 use crate::xdr::ScError;
diff --git a/soroban-env-common/src/compare.rs b/soroban-env-common/src/compare.rs
index 542506c8..0334ac5b 100644
--- a/soroban-env-common/src/compare.rs
+++ b/soroban-env-common/src/compare.rs
@@ -175,8 +175,6 @@ impl<E: Env> Compare<Val> for E {
 
                 Tag::SymbolSmall => delegate_compare_to_wrapper!(SymbolSmall, a, b, self),
 
-                Tag::LedgerKeyContractInstance => Ok(Ordering::Equal),
-
                 Tag::SmallCodeUpperBound => Ok(Ordering::Equal),
                 Tag::ObjectCodeLowerBound => Ok(Ordering::Equal),
 
@@ -196,11 +194,10 @@ impl<E: Env> Compare<Val> for E {
                 | Tag::SymbolObject
                 | Tag::VecObject
                 | Tag::MapObject
-                | Tag::AddressObject => Err(Error::from_type_and_code(
+                | Tag::AddressObject => Err(self.error_from_error_val(Error::from_type_and_code(
                     ScErrorType::Context,
                     ScErrorCode::InternalError,
-                )
-                .into()),
+                ))),
 
                 Tag::ObjectCodeUpperBound => Ok(Ordering::Equal),
                 Tag::Bad => Ok(Ordering::Equal),
diff --git a/soroban-env-common/src/convert.rs b/soroban-env-common/src/convert.rs
index 8dff9702..ab75c82d 100644
--- a/soroban-env-common/src/convert.rs
+++ b/soroban-env-common/src/convert.rs
@@ -20,7 +20,7 @@ use crate::{
 /// General trait representing a the ability of some object to perform a
 /// (possibly unsuccessful) conversion between two other types.
 pub trait Convert<F, T> {
-    type Error: Debug + Into<crate::Error> + From<crate::Error>;
+    type Error: Debug + Into<crate::Error>;
     fn convert(&self, f: F) -> Result<T, Self::Error>;
 }
 
@@ -29,7 +29,7 @@ pub trait Convert<F, T> {
 /// conversions via `.try_into_val(e)` or specifying convertability with a bound
 /// like `TryIntoVal<E,Other>`.
 pub trait TryIntoVal<E: Env, V> {
-    type Error: Debug + Into<crate::Error> + From<crate::Error>;
+    type Error: Debug + Into<crate::Error>;
     fn try_into_val(&self, env: &E) -> Result<V, Self::Error>;
 }
 
@@ -40,7 +40,7 @@ pub trait TryIntoVal<E: Env, V> {
 /// delegate to the environment to look up and extract the content of those
 /// handles.
 pub trait TryFromVal<E: Env, V: ?Sized>: Sized {
-    type Error: Debug + Into<crate::Error> + From<crate::Error>;
+    type Error: Debug + Into<crate::Error>;
     fn try_from_val(env: &E, v: &V) -> Result<Self, Self::Error>;
 }
 
@@ -376,8 +376,10 @@ where
 
     fn try_from_val(env: &E, val: &Val) -> Result<Self, ConversionError> {
         if let Ok(object) = Object::try_from(val) {
-            // FIXME: it's not really great to be dropping the error from the other
-            // TryFromVal here, we should really switch to taking errors from E.
+            // Bug https://github.com/stellar/rs-soroban-env/issues/1076: it's
+            // not really great to be dropping the error from the other
+            // TryFromVal here, we should really switch to taking errors from E
+            // or returning Error or something.
             let scvo: ScValObject = object.try_into_val(env).map_err(|_| ConversionError)?;
             return Ok(scvo.into());
         }
@@ -387,8 +389,8 @@ where
             Tag::True => Ok(ScVal::Bool(true)),
             Tag::Void => Ok(ScVal::Void),
             Tag::Error => {
-                let status: Error = unsafe { <Error as ValConvert>::unchecked_from_val(val) };
-                Ok(status.try_into()?)
+                let error: Error = unsafe { <Error as ValConvert>::unchecked_from_val(val) };
+                Ok(error.try_into()?)
             }
             Tag::U32Val => Ok(ScVal::U32(val.get_major())),
             Tag::I32Val => Ok(ScVal::I32(val.get_major() as i32)),
@@ -432,8 +434,8 @@ where
                 )))
             }
 
-            Tag::LedgerKeyContractInstance => Ok(ScVal::LedgerKeyContractInstance),
-
+            // The object types should all be handled above, and the other tag
+            // cases should never occur.
             Tag::U64Object
             | Tag::I64Object
             | Tag::TimepointObject
@@ -447,8 +449,8 @@ where
             | Tag::SymbolObject
             | Tag::VecObject
             | Tag::MapObject
-            | Tag::AddressObject => unreachable!(),
-            Tag::SmallCodeUpperBound
+            | Tag::AddressObject
+            | Tag::SmallCodeUpperBound
             | Tag::ObjectCodeLowerBound
             | Tag::ObjectCodeUpperBound
             | Tag::Bad => Err(ConversionError),
@@ -456,6 +458,15 @@ where
     }
 }
 
+#[cfg(feature = "std")]
+fn require_or_conversion_error(b: bool) -> Result<(), ConversionError> {
+    if !b {
+        Err(ConversionError)
+    } else {
+        Ok(())
+    }
+}
+
 #[cfg(feature = "std")]
 impl<E: Env> TryFromVal<E, ScVal> for Val
 where
@@ -468,6 +479,7 @@ where
             return Ok(obj.into());
         }
 
+        // Remaining cases should only be "small" types.
         Ok(match val {
             ScVal::Bool(b) => Val::from_bool(*b).into(),
             ScVal::Void => Val::from_void().into(),
@@ -475,37 +487,37 @@ where
             ScVal::U32(u) => (*u).into(),
             ScVal::I32(i) => (*i).into(),
             ScVal::U64(u) => {
-                assert!(num::is_small_u64(*u));
+                require_or_conversion_error(num::is_small_u64(*u))?;
                 unsafe { Val::from_body_and_tag(*u, Tag::U64Small) }
             }
             ScVal::I64(i) => {
-                assert!(num::is_small_i64(*i));
+                require_or_conversion_error(num::is_small_i64(*i))?;
                 unsafe { Val::from_body_and_tag(*i as u64, Tag::I64Small) }
             }
             ScVal::Timepoint(TimePoint(u)) => {
-                assert!(num::is_small_u64(*u));
+                require_or_conversion_error(num::is_small_u64(*u))?;
                 unsafe { Val::from_body_and_tag(*u, Tag::TimepointSmall) }
             }
             ScVal::Duration(Duration(u)) => {
-                assert!(num::is_small_u64(*u));
+                require_or_conversion_error(num::is_small_u64(*u))?;
                 unsafe { Val::from_body_and_tag(*u, Tag::DurationSmall) }
             }
             ScVal::U128(u) => {
                 let u: u128 = u.into();
-                assert!(num::is_small_u128(u));
+                require_or_conversion_error(num::is_small_u128(u))?;
                 unsafe { Val::from_body_and_tag(u as u64, Tag::U128Small) }
             }
             ScVal::I128(i) => {
                 let i: i128 = i.into();
-                assert!(num::is_small_i128(i));
+                require_or_conversion_error(num::is_small_i128(i))?;
                 unsafe { Val::from_body_and_tag((i as i64) as u64, Tag::I128Small) }
             }
             ScVal::U256(u) => {
-                assert!(num::is_small_u256_parts(u));
+                require_or_conversion_error(num::is_small_u256_parts(u))?;
                 unsafe { Val::from_body_and_tag(u.lo_lo, Tag::U256Small) }
             }
             ScVal::I256(i) => {
-                assert!(num::is_small_i256_parts(i));
+                require_or_conversion_error(num::is_small_i256_parts(i))?;
                 unsafe { Val::from_body_and_tag(i.lo_lo, Tag::I256Small) }
             }
             ScVal::Symbol(bytes) => {
@@ -515,17 +527,17 @@ where
                 };
                 SymbolSmall::try_from_str(ss)?.into()
             }
-            ScVal::LedgerKeyContractInstance => unsafe {
-                Val::from_body_and_tag(0, Tag::LedgerKeyContractInstance)
-            },
 
+            // These should all have been classified as ScValObjRef above, or are
+            // reserved ScVal types that are never passed as vals at all.
             ScVal::Bytes(_)
             | ScVal::String(_)
             | ScVal::Vec(_)
             | ScVal::Map(_)
             | ScVal::Address(_)
             | ScVal::LedgerKeyNonce(_)
-            | ScVal::ContractInstance(_) => unreachable!(),
+            | ScVal::LedgerKeyContractInstance
+            | ScVal::ContractInstance(_) => return Err(ConversionError),
         })
     }
 }
diff --git a/soroban-env-common/src/env.rs b/soroban-env-common/src/env.rs
index a5acdaed..c77dfa81 100644
--- a/soroban-env-common/src/env.rs
+++ b/soroban-env-common/src/env.rs
@@ -6,7 +6,6 @@ use super::{
     I64Object, MapObject, StorageType, StringObject, SymbolObject, TimepointObject, U128Object,
     U256Object, U256Val, U32Val, U64Object, U64Val, Val, VecObject, Void,
 };
-use core::any;
 
 /// Base trait extended by the [Env](crate::Env) trait, providing various special-case
 /// functions that do _not_ simply call across cross the guest/host interface.
@@ -32,11 +31,20 @@ pub trait EnvBase: Sized + Clone {
     /// environment-interface level, and then either directly handle or escalate
     /// the contained `Error` code to the user as a `Error` or `Result<>` of
     /// some other type, depending on the API.
-    type Error: core::fmt::Debug + Into<crate::Error> + From<crate::Error>;
+    type Error: core::fmt::Debug + Into<crate::Error>;
+
+    /// Convert a [`crate::Error`] into [`EnvBase::Error`]. This is similar to adding
+    /// `+ From<crate::Error>` to the associated type bound for `EnvBase::Error`
+    /// but it allows us to restrict that conversion in downstream crates, which
+    /// is desirable to keep "conversions that panic" (as the guest definition
+    /// of `EnvBase::Error` does) out of the common crate and avoid accidentally
+    /// triggering them in the host. It also gives the `Env` an opportunity to
+    /// log or enrich the error with context (both of which happen in `Host`).
+    fn error_from_error_val(&self, e: crate::Error) -> Self::Error;
 
     /// Reject an error from the environment, turning it into a panic but on
-    /// terms that the environment controls (eg. transforming or logging it).
-    /// This should only ever be called by client-side / SDK local-testing code,
+    /// terms that the environment controls (eg. enriching or logging it). This
+    /// should only ever be called by client-side / SDK local-testing code,
     /// never in the `Host`.
     #[cfg(feature = "testutils")]
     fn escalate_error_to_panic(&self, e: Self::Error) -> !;
@@ -53,14 +61,8 @@ pub trait EnvBase: Sized + Clone {
         x
     }
 
-    /// Used for recovering the concrete type of the Host.
-    fn as_mut_any(&mut self) -> &mut dyn any::Any;
-
-    /// Used to check two environments are the same, trapping if not.
-    fn check_same_env(&self, other: &Self);
-
-    /// Used to clone an environment deeply, not just a handle to it.
-    fn deep_clone(&self) -> Self;
+    /// Used to check two environments are the same, returning Error if not.
+    fn check_same_env(&self, other: &Self) -> Result<(), Self::Error>;
 
     // Helpers for methods that wish to pass Rust lifetime-qualified _slices_
     // into the environment. These are _not_ done via Env trait methods to avoid
diff --git a/soroban-env-common/src/error.rs b/soroban-env-common/src/error.rs
index de709488..d3d382cc 100644
--- a/soroban-env-common/src/error.rs
+++ b/soroban-env-common/src/error.rs
@@ -1,8 +1,8 @@
 use crate::xdr::{ScError, ScErrorCode, ScErrorType, ScVal};
 use crate::{
     impl_wrapper_as_and_to_rawval, impl_wrapper_tag_based_constructors,
-    impl_wrapper_tag_based_rawvalconvertible, impl_wrapper_wasmi_conversions, Compare,
-    ConversionError, Env, SymbolError, Val,
+    impl_wrapper_tag_based_valconvert, impl_wrapper_wasmi_conversions, Compare, ConversionError,
+    Env, SymbolError, Val,
 };
 use core::{
     cmp::Ordering,
@@ -12,15 +12,15 @@ use core::{
 };
 
 /// Wrapper for a [Val] that is tagged with [Tag::Error], interpreting the
-/// [Val]'s body as a pair of a 28-bit status-type code and a 32-bit status
-/// code. The status-type codes correspond to the enumerated cases of
-/// [ScErrorType], and the status codes correspond to the code values stored in
+/// [Val]'s body as a pair of a 28-bit error-type code and a 32-bit error
+/// code. The error-type codes correspond to the enumerated cases of
+/// [ScErrorType], and the error codes correspond to the code values stored in
 /// each variant of the [ScError] union.
 #[repr(transparent)]
 #[derive(Copy, Clone)]
 pub struct Error(Val);
 
-impl_wrapper_tag_based_rawvalconvertible!(Error);
+impl_wrapper_tag_based_valconvert!(Error);
 impl_wrapper_tag_based_constructors!(Error);
 impl_wrapper_as_and_to_rawval!(Error);
 impl_wrapper_wasmi_conversions!(Error);
@@ -199,6 +199,20 @@ impl From<wasmi::core::TrapCode> for Error {
     }
 }
 
+#[cfg(feature = "wasmi")]
+impl From<wasmi::errors::FuncError> for Error {
+    fn from(err: wasmi::errors::FuncError) -> Self {
+        let ec = match err {
+            wasmi::errors::FuncError::ExportedFuncNotFound => ScErrorCode::MissingValue,
+            wasmi::errors::FuncError::MismatchingParameterType
+            | wasmi::errors::FuncError::MismatchingResultType => ScErrorCode::UnexpectedType,
+            wasmi::errors::FuncError::MismatchingParameterLen
+            | wasmi::errors::FuncError::MismatchingResultLen => ScErrorCode::UnexpectedSize,
+        };
+        return Error::from_type_and_code(ScErrorType::WasmVm, ec);
+    }
+}
+
 #[cfg(feature = "wasmi")]
 impl From<wasmi::Error> for Error {
     fn from(e: wasmi::Error) -> Self {
@@ -235,10 +249,22 @@ impl From<wasmi::Error> for Error {
                     return code.into();
                 }
             }
+            wasmi::Error::Func(e) => {
+                return e.into();
+            }
+            wasmi::Error::Global(e) => {
+                if matches!(e, wasmi::errors::GlobalError::TypeMismatch { .. }) {
+                    return Error::from_type_and_code(
+                        ScErrorType::WasmVm,
+                        ScErrorCode::UnexpectedType,
+                    );
+                }
+            }
+
             _ => (),
         }
 
-        Error::from_type_and_code(ScErrorType::WasmVm, ScErrorCode::InternalError)
+        Error::from_type_and_code(ScErrorType::WasmVm, ScErrorCode::InvalidAction)
     }
 }
 
@@ -289,14 +315,8 @@ impl Error {
 }
 
 impl From<core::convert::Infallible> for crate::Error {
-    fn from(_: core::convert::Infallible) -> Self {
-        unreachable!()
-    }
-}
-
-impl From<crate::Error> for core::convert::Infallible {
-    fn from(_value: crate::Error) -> Self {
-        unreachable!()
+    fn from(x: core::convert::Infallible) -> Self {
+        match x {}
     }
 }
 
@@ -305,7 +325,7 @@ mod tests {
     use super::*;
 
     #[test]
-    fn status_ord_same_as_scstatus() {
+    fn error_ord_same_as_scerror() {
         // The impl `Ord for Error` must agree with `Ord for ScError`,
         // re https://github.com/stellar/rs-soroban-env/issues/743.
         //
diff --git a/soroban-env-common/src/lib.rs b/soroban-env-common/src/lib.rs
index 2e2d0c31..bf3d47bd 100644
--- a/soroban-env-common/src/lib.rs
+++ b/soroban-env-common/src/lib.rs
@@ -4,10 +4,10 @@
 //!   - The [Val] type, a 64-bit value type that is a union between several
 //!     different types (numbers, booleans, symbols, object references), encoded
 //!     via careful bit-packing.
-//!   - Wrapper types ([Object], [Symbol], [Error]) that
-//!     contain [Val] in a specific, known union state. These are also 64-bit
-//!     values, but offer methods specific to the union state (eg. [Symbol] will
-//!     interconvert with Rust strings).
+//!   - Wrapper types ([Object], [Symbol], [Error]) that contain [Val] in a
+//!     specific, known union state. These are also 64-bit values, but offer
+//!     methods specific to the union state (eg. [Symbol] will interconvert with
+//!     Rust string types).
 //!   - The [Env] trait, which describes the _interface_ between guest and host
 //!     code. In other words, `Env` describes a set of _host functions_ that
 //!     must be implemented in a contract host, and can be called from a guest
@@ -36,6 +36,7 @@ pub const VERSION: Version = Version {
 
 mod wrapper_macros;
 
+#[cfg(feature = "testutils")]
 mod arbitrary;
 mod bytes;
 mod compare;
@@ -49,11 +50,10 @@ mod storage_type;
 mod string;
 mod symbol;
 mod tuple;
-mod unimplemented_env;
 mod val;
 mod vmcaller_env;
 
-// We have some types that we don't re-export everything
+// We have some modules that we don't re-export everything
 // from because only specific users are likely to use them.
 pub mod meta;
 pub mod num;
@@ -78,16 +78,16 @@ pub use stellar_xdr::curr as xdr;
 pub use stellar_xdr::next as xdr;
 
 // Val is the 64-bit transparent type.
+pub use val::{ConversionError, Tag, Val};
+
 #[cfg(feature = "wasmi")]
 pub use val::WasmiMarshal;
 pub use val::{AddressObject, MapObject, VecObject};
 pub use val::{Bool, Void};
-pub use val::{ConversionError, Tag, Val};
 
 pub use compare::Compare;
 pub use convert::{Convert, TryFromVal, TryIntoVal};
 pub use env::{call_macro_with_all_host_functions, Env, EnvBase};
-pub use unimplemented_env::UnimplementedEnv;
 pub use vmcaller_env::{VmCaller, VmCallerEnv};
 
 pub use bytes::BytesObject;
@@ -95,14 +95,3 @@ pub use error::Error;
 pub use object::{Object, ScValObjRef, ScValObject};
 pub use string::StringObject;
 pub use symbol::{Symbol, SymbolError, SymbolObject, SymbolSmall, SymbolSmallIter, SymbolStr};
-
-#[inline(always)]
-// Awkward: this is a free function rather than a trait call because
-// you can't have const trait calls. It calls panic! rather than
-// rt::trap because trap can't be const because one of its versions
-// is the wasm unreachable intruction. Ideally this would be a function
-// that did panic! in a const context and rt::trap in a non-const
-// context but it's not clear how to actually do that.
-pub const fn require(b: bool) {
-    assert!(b,);
-}
diff --git a/soroban-env-common/src/meta.rs b/soroban-env-common/src/meta.rs
index 118c698b..5a5274e5 100644
--- a/soroban-env-common/src/meta.rs
+++ b/soroban-env-common/src/meta.rs
@@ -14,10 +14,10 @@
 // within.
 //
 // Protocol numbers will be checked for ordered compatibility (a host will only
-// run protocols it's at least as new as) whereas pre-release numbers will be
-// checked for _exact_ identity. Any pre-release number is considered
-// incompatible with every other pre-release number, requires recompiling
-// contracts.
+// run contracts built for same-or-older protocol versions than its own) whereas
+// pre-release numbers will be checked for _exact_ identity. Any pre-release
+// number is considered incompatible with every other pre-release number,
+// requires recompiling contracts.
 //
 // Any change to the logical interface of a released version of soroban (with a
 // nonzero major version):
@@ -58,7 +58,7 @@ soroban_env_macros::generate_env_meta_consts!(
 #[cfg(not(feature = "next"))]
 soroban_env_macros::generate_env_meta_consts!(
     ledger_protocol_version: 20,
-    pre_release_version: 58,
+    pre_release_version: 62,
 );
 
 pub const fn get_ledger_protocol_version(interface_version: u64) -> u32 {
diff --git a/soroban-env-common/src/num.rs b/soroban-env-common/src/num.rs
index 48e0307c..ffd9326d 100644
--- a/soroban-env-common/src/num.rs
+++ b/soroban-env-common/src/num.rs
@@ -22,16 +22,6 @@ impl Val {
     pub const fn from_i32(i: i32) -> I32Val {
         unsafe { I32Val(Val::from_major_minor_and_tag(i as u32, 0, Tag::I32Val)) }
     }
-
-    #[inline(always)]
-    pub const fn is_i32_zero(self) -> bool {
-        self.shallow_eq(&Self::I32_ZERO.0)
-    }
-
-    #[inline(always)]
-    pub const fn is_u32_zero(self) -> bool {
-        self.shallow_eq(&Self::U32_ZERO.0)
-    }
 }
 
 impl<E: Env> Compare<U32Val> for E {
diff --git a/soroban-env-common/src/object.rs b/soroban-env-common/src/object.rs
index 4a4af6eb..18a4dbdc 100644
--- a/soroban-env-common/src/object.rs
+++ b/soroban-env-common/src/object.rs
@@ -6,8 +6,8 @@ use crate::{
 use core::{cmp::Ordering, fmt::Debug};
 
 /// Wrapper for a [Val] that is tagged with one of the object types,
-/// interpreting the [Val]'s body as containing a 32-bit object-code handle
-/// to a host object of the object-type.
+/// interpreting the [Val]'s body as containing a 32-bit handle to a host
+/// object.
 #[repr(transparent)]
 #[derive(Copy, Clone)]
 pub struct Object(pub(crate) Val);
@@ -127,22 +127,23 @@ impl<'a> ScValObjRef<'a> {
     /// that needs to be stored as a host-side object, or else `None`.
     pub fn classify(value: &'a ScVal) -> Option<Self> {
         match value {
-            // Always-small values are never ScValObject
+            // Always-small values are never ScValObject, nor are
+            // ScVals that don't actually project into Vals at all.
             ScVal::Bool(_)
             | ScVal::Void
             | ScVal::Error(_)
             | ScVal::U32(_)
             | ScVal::I32(_)
-            | ScVal::LedgerKeyContractInstance => None,
+            | ScVal::LedgerKeyContractInstance
+            | ScVal::LedgerKeyNonce(_)
+            | ScVal::ContractInstance(_) => None,
 
             // Always-large values are always ScValObject
             ScVal::Bytes(_)
             | ScVal::String(_)
             | ScVal::Vec(_)
             | ScVal::Map(_)
-            | ScVal::Address(_)
-            | ScVal::LedgerKeyNonce(_)
-            | ScVal::ContractInstance(_) => Some(ScValObjRef(value)),
+            | ScVal::Address(_) => Some(ScValObjRef(value)),
 
             // Other values are small or large depending on
             // their actual scalar value.
diff --git a/soroban-env-common/src/symbol.rs b/soroban-env-common/src/symbol.rs
index cb2d9e3a..9710b164 100644
--- a/soroban-env-common/src/symbol.rs
+++ b/soroban-env-common/src/symbol.rs
@@ -1,30 +1,45 @@
-//! Ths `Symbol` type is designed for encoding short, unambiguous, single-word
+//! The [`Symbol`] type is designed for encoding short, unambiguous, single-word
 //! identifiers such as the names of contract functions or assets in the
 //! network. `Symbol`s only admit characters from the 63-character repertoire
 //! `[a-zA-Z0-9_]` -- latin-script alphabetic letters, digits, and underscores.
 //!
-//! There are two reasons for this type to be different from the general
+//! There are three reasons for this type to be different from the general
 //! `String` type:
 //!
-//!   1. We provide a space-optimized "small" form (`SymbolSmall`) that uses
+//!   1. We provide a space-optimized "small" form ([`SymbolSmall`]) that uses
 //!      small 6-bit codes (since the character repertoire is only 63 characters
-//!      plus null) and bit-packs them into the body of a `Val` such that they
+//!      plus null) and bit-packs them into the body of a [`Val`] such that they
 //!      can be used to represent short identifiers (up to 9 characters long)
 //!      without allocating a host object at all, essentially as "machine
 //!      integers with a textual interpretation". This is an optimization, since
-//!      we expect contracts to use `Symbol`s heavily.
+//!      we expect contracts to use `Symbol`s heavily. When a `Symbol` is larger
+//!      than this, it overflows to a [`SymbolObject`] transparently, as with
+//!      the size-optiized small number types.
 //!
-//!   2. We expect (though do not require) `String` to usually be interpreted as
-//!      Unicode codepoints encoded in UTF-8. Unicode characters unfortunately
-//!      admit a wide variety of "confusables" or "homoglyphs": characters that
-//!      have different codes, but look the same when rendered in many common
-//!      fonts. In many contexts these represent a significant security risk to
-//!      end users -- for example by confusing an asset named `USD` (encoded as
-//!      the UTF-8 hex byte sequence `[55 53 44]`) with a similar-looking but
-//!      different asset named `ՍЅᎠ` (encoded as `[d5 8d d0 85 e1 8e a0]`) --
-//!      and so we provide `Symbol` as an alternative to `String` for use in
-//!      contexts where users wish to minimize such risks, by restricting the
-//!      possible characters that can occur.
+//!   2. Unlike [`StringObject`](crate::StringObject)s, there is a reasonably
+//!      small maximum size for [`SymbolObject`]s, given by
+//!      [`SCSYMBOL_LIMIT`](crate::xdr::SCSYMBOL_LIMIT) (currently 32 bytes).
+//!      Having such a modest maximum size allows working with `Symbol`s
+//!      entirely in guest WASM code without a heap allocator, using only
+//!      fixed-size buffers on the WASM shadow stack. The [`SymbolStr`] type is
+//!      a convenience wrapper around such a buffer, that can be directly
+//!      created from a [`Symbol`], copying its bytes from the host if the
+//!      `Symbol` is a `SymbolObject` or unpacking its 6-bit codes if the
+//!      `Symbol` is a `SymbolSmall`. [`SymbolStr`] can also yield a standard
+//!      Rust `&str` allowing its use with many Rust core library functions.
+//!
+//!   3. We expect (though do not require) [`StringObject`](crate::StringObject)
+//!      to usually be interpreted as Unicode codepoints encoded in UTF-8.
+//!      Unicode characters unfortunately admit a wide variety of "confusables"
+//!      or "homoglyphs": characters that have different codes, but look the
+//!      same when rendered in many common fonts. In many contexts these
+//!      represent a significant security risk to end users -- for example by
+//!      confusing an asset named `USD` (encoded as the UTF-8 hex byte sequence
+//!      `[55 53 44]`) with a similar-looking but different asset named `ՍЅᎠ`
+//!      (encoded as `[d5 8d d0 85 e1 8e a0]`) -- and so we provide `Symbol` as
+//!      an alternative to `String` for use in contexts where users wish to
+//!      minimize such risks, by restricting the possible characters that can
+//!      occur.
 //!
 //! `SymbolSmall` values are packed into a 56 bits (the "body" part of a 64-bit
 //! `Val` word) with zero padding in the high-order bits rather than low-order
@@ -34,8 +49,8 @@
 //! literals are encoded as variable-length little-endian values, using ULEB128.
 
 use crate::{
-    declare_tag_based_small_and_object_wrappers, require, val::ValConvert, Compare,
-    ConversionError, Env, Tag, TryFromVal, Val,
+    declare_tag_based_small_and_object_wrappers, val::ValConvert, Compare, ConversionError, Env,
+    Tag, TryFromVal, Val,
 };
 use core::{cmp::Ordering, fmt::Debug, hash::Hash, str};
 
@@ -247,9 +262,10 @@ impl SymbolSmall {
     }
 
     pub fn to_str(&self) -> SymbolStr {
+        sa::const_assert!(SCSYMBOL_LIMIT as usize >= MAX_SMALL_CHARS);
         let mut chars = [b'\x00'; SCSYMBOL_LIMIT as usize];
-        for (i, ch) in self.into_iter().enumerate() {
-            chars[i] = ch as u8;
+        for (src, dst) in self.into_iter().zip(chars.iter_mut()) {
+            *dst = src as u8
         }
         SymbolStr(chars)
     }
@@ -397,11 +413,14 @@ impl Iterator for SymbolSmallIter {
     }
 }
 
+#[cfg(feature = "testutils")]
 impl FromIterator<char> for SymbolSmall {
     fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
         let mut accum: u64 = 0;
         for (n, i) in iter.into_iter().enumerate() {
-            require(n < MAX_SMALL_CHARS);
+            if n >= MAX_SMALL_CHARS {
+                panic!("too many chars for SymbolSmall");
+            }
             accum <<= CODE_BITS;
             let v = match i {
                 '_' => 1,
diff --git a/soroban-env-common/src/tuple.rs b/soroban-env-common/src/tuple.rs
index 5f9f9339..29c513aa 100644
--- a/soroban-env-common/src/tuple.rs
+++ b/soroban-env-common/src/tuple.rs
@@ -81,10 +81,10 @@ impl_for_tuple! { 12_u32 12_usize T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T
 impl_for_tuple! { 13_u32 13_usize T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9 T10 10 T11 11 T12 12 }
 
 // Unit types are not tuples, but when people write out what they think is an
-// empty tuple, it is a unit type. The following conversions have unit types
+// empty tuple, it is a unit type. The following conversions on unit types
 // behave like tuples in some conversions that are safe to do so, like
-// conversions to and from arrays. Note that unit typles convert to
-// Val::VOID, see raw_val.rs for those conversions.
+// conversions to and from arrays. Note that unit types directly convert to
+// Val::VOID, see val.rs for those conversions.
 
 impl<E: Env> TryFromVal<E, [Val; 0]> for () {
     type Error = crate::Error;
diff --git a/soroban-env-common/src/unimplemented_env.rs b/soroban-env-common/src/unimplemented_env.rs
deleted file mode 100644
index a601a73b..00000000
--- a/soroban-env-common/src/unimplemented_env.rs
+++ /dev/null
@@ -1,182 +0,0 @@
-use super::{call_macro_with_all_host_functions, Env, EnvBase, Symbol};
-use super::{
-    AddressObject, Bool, BytesObject, DurationObject, Error, I128Object, I256Object, I256Val,
-    I64Object, MapObject, StorageType, StringObject, SymbolObject, TimepointObject, U128Object,
-    U256Object, U256Val, U32Val, U64Object, U64Val, Val, VecObject, Void,
-};
-use core::{any, convert::Infallible};
-
-/// A dummy implementation of the [Env] trait that fails with `unimplemented!()` in
-/// all functions. Useful for certain testing scenarios.
-#[derive(Clone, Default)]
-pub struct UnimplementedEnv;
-
-impl EnvBase for UnimplementedEnv {
-    type Error = Infallible;
-
-    #[cfg(feature = "testutils")]
-    fn escalate_error_to_panic(&self, _e: Self::Error) -> ! {
-        unimplemented!()
-    }
-
-    fn as_mut_any(&mut self) -> &mut dyn any::Any {
-        self
-    }
-
-    fn check_same_env(&self, _other: &Self) {}
-
-    fn deep_clone(&self) -> Self {
-        Self
-    }
-
-    fn bytes_copy_from_slice(
-        &self,
-        _b: BytesObject,
-        _b_pos: U32Val,
-        _slice: &[u8],
-    ) -> Result<BytesObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn bytes_copy_to_slice(
-        &self,
-        _b: BytesObject,
-        _b_pos: U32Val,
-        _slice: &mut [u8],
-    ) -> Result<(), Self::Error> {
-        unimplemented!()
-    }
-
-    fn string_copy_to_slice(
-        &self,
-        _b: StringObject,
-        _b_pos: U32Val,
-        _slice: &mut [u8],
-    ) -> Result<(), Self::Error> {
-        unimplemented!()
-    }
-
-    fn symbol_copy_to_slice(
-        &self,
-        _b: SymbolObject,
-        _b_pos: U32Val,
-        _slice: &mut [u8],
-    ) -> Result<(), Self::Error> {
-        unimplemented!()
-    }
-
-    fn bytes_new_from_slice(&self, _slice: &[u8]) -> Result<BytesObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn string_new_from_slice(&self, _s: &str) -> Result<crate::StringObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn symbol_new_from_slice(&self, _s: &str) -> Result<crate::SymbolObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn map_new_from_slices(&self, _keys: &[&str], _vals: &[Val]) -> Result<MapObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn map_unpack_to_slice(
-        &self,
-        _map: MapObject,
-        _keys: &[&str],
-        _vals: &mut [Val],
-    ) -> Result<Void, Self::Error> {
-        unimplemented!()
-    }
-
-    fn vec_new_from_slice(&self, _vals: &[Val]) -> Result<VecObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn vec_unpack_to_slice(&self, _vec: VecObject, _vals: &mut [Val]) -> Result<Void, Self::Error> {
-        unimplemented!()
-    }
-
-    fn symbol_index_in_strs(&self, _key: Symbol, _strs: &[&str]) -> Result<U32Val, Self::Error> {
-        unimplemented!()
-    }
-
-    fn log_from_slice(&self, _msg: &str, _vals: &[Val]) -> Result<Void, Self::Error> {
-        unimplemented!()
-    }
-}
-
-// This is a helper macro used only by generate_env_unimplemented 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 {
-    {fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
-    =>
-    {
-        fn $fn_id(&self, $(_:$type),*) -> Result<$ret, Self::Error> {
-            unimplemented!()
-        }
-    };
-}
-
-// 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
-// UnimplementedEnv trait.
-macro_rules! generate_env_unimplemented {
-    {
-        $(
-            // 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 UnimplementedEnv struct used
-        // to define a type that implements the Env trait with all functions
-        // unimplemented, intended for use in tests that need an Env that has no
-        // implementation.
-        impl Env for UnimplementedEnv
-        {
-            $(
-                $(
-                    // 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 Env trait.
-                    host_function_helper!{fn $fn_id $args -> $ret}
-                )*
-            )*
-        }
-    };
-}
-
-// Invoke the x-macro passing generate_env_unimplemented as its callback macro.
-call_macro_with_all_host_functions! { generate_env_unimplemented }
diff --git a/soroban-env-common/src/val.rs b/soroban-env-common/src/val.rs
index e81bf5df..1001aecf 100644
--- a/soroban-env-common/src/val.rs
+++ b/soroban-env-common/src/val.rs
@@ -4,7 +4,7 @@
 use crate::xdr::{ScError, ScValType};
 use crate::{
     declare_tag_based_object_wrapper, declare_tag_based_wrapper, impl_rawval_wrapper_base,
-    impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible, Compare, I32Val, SymbolSmall,
+    impl_tryfroms_and_tryfromvals_delegating_to_valconvert, Compare, I32Val, SymbolSmall,
     SymbolStr, U32Val,
 };
 
@@ -48,7 +48,7 @@ sa::const_assert!(MAJOR_BITS + MINOR_BITS == BODY_BITS);
 #[cfg_attr(test, derive(num_enum::TryFromPrimitive))]
 pub enum Tag {
     /// Tag for a [Val] that encodes [bool] `false`. The bool type is refined to
-    /// two single-value subtypes in order for each tag number to coincides with
+    /// two single-value subtypes in order for each tag number to coincide with
     /// the WASM encoding of a boolean.
     False = 0,
 
@@ -84,29 +84,25 @@ pub enum Tag {
     /// Tag for a [Val] that contains a [u128] small enough to fit in 56 bits.
     U128Small = 10,
 
-    /// Tag for a [Val] that contains a [i128] small enough to fit in 56 bits.
+    /// Tag for a [Val] that contains an [i128] small enough to fit in 56 bits.
     I128Small = 11,
 
-    /// Tag for a [Val] that contains a [u256] small enough to fit in 56 bits.
+    /// Tag for a [Val] that contains a [u256](ethnum::u256) small enough to fit in 56 bits.
     U256Small = 12,
 
-    /// Tag for a [Val] that contains a [i256] small enough to fit in 56 bits.
+    /// Tag for a [Val] that contains an [i256](ethnum::i256) small enough to fit in 56 bits.
     I256Small = 13,
 
     /// Tag for a [Val] that contains up to 9 character symbols.
     SymbolSmall = 14,
 
-    /// Tag for a [Val] that corresponds to
-    /// [crate::xdr::ScVal::LedgerKeyContractInstance]
-    LedgerKeyContractInstance = 15,
-
     /// Code delimiting the upper boundary of "small" types.
-    SmallCodeUpperBound = 16,
+    SmallCodeUpperBound = 15,
 
     /// Tag reserved to indicate boundary between tags for "small" types with
     /// their payload packed into the remaining 56 bits of the [Val] and
     /// "object" types that are stored as host objects and referenced by
-    /// [Object] handle.
+    /// [Object](crate::Object) handle.
     ObjectCodeLowerBound = 63,
 
     /// Tag for a [Val] that refers to a host-side [u64] number.
@@ -129,23 +125,34 @@ pub enum Tag {
     /// Tag for a [Val] that refers to a host-side [i128] number.
     I128Object = 69,
 
-    /// Tag for a [Val] that refers to a host-side [u256] number.
+    /// Tag for a [Val] that refers to a host-side [u256](ethnum::u256) number.
     U256Object = 70,
 
-    /// Tag for a [Val] that refers to a host-side [i256] number.
+    /// Tag for a [Val] that refers to a host-side [i256](ethnum::i256) number.
     I256Object = 71,
 
+    /// Tag for a [Val] that refers to a host-side byte array.
     BytesObject = 72,
+
+    /// Tag for a [Val] that refers to a host-side string.
     StringObject = 73,
+
+    /// Tag for a [Val] that refers to a host-side symbol (see [`Symbol`](crate::Symbol)).
     SymbolObject = 74,
 
+    /// Tag for a [Val] that refers to a host-side vector of [Val]s.
     VecObject = 75,
+
+    /// Tag for a [Val] that refers to a host-side map from [Val]s to [Val]s.
     MapObject = 76,
 
+    /// Tag for a [Val] that refers to a host-side contract address.
     AddressObject = 77,
 
+    /// Code delimiting the upper boundary of "object" types.
     ObjectCodeUpperBound = 78,
 
+    /// Code reserved to indicate mis-tagged [`Val`]s.
     Bad = 0x7f,
 }
 
@@ -215,7 +222,6 @@ impl Tag {
             Tag::U256Small => Some(ScValType::U256),
             Tag::I256Small => Some(ScValType::I256),
             Tag::SymbolSmall => Some(ScValType::Symbol),
-            Tag::LedgerKeyContractInstance => Some(ScValType::LedgerKeyContractInstance),
             Tag::SmallCodeUpperBound => None,
             Tag::ObjectCodeLowerBound => None,
             Tag::U64Object => Some(ScValType::U64),
@@ -330,7 +336,7 @@ declare_tag_based_object_wrapper!(AddressObject);
 // conversion to a more-structured error code at a higher level.
 
 /// Error type indicating a failure to convert some type to another; details of
-/// the failed conversion will typically be written to the debug log.
+/// the failed conversion may be written to the debug log, when possible.
 ///
 /// This is intentionally minimal and uninformative to minimize impact of its
 /// use on wasm codesize. It converts to `Error(ScErrorType::Value,
@@ -361,8 +367,7 @@ impl From<crate::Error> for ConversionError {
 
 /// Trait abstracting over types that can be converted into [Val], similar to
 /// [TryFrom] but with a different signature that enables generating slightly
-/// more efficient conversion code. An implementation of `TryFrom<Val>` is also
-/// provided for any type that implements `ValConvert`.
+/// more efficient conversion code.
 pub(crate) trait ValConvert: Into<Val> + TryFrom<Val> {
     /// Returns `true` if `v` is in a union state compatible with `Self`.
     fn is_val_type(v: Val) -> bool;
@@ -391,11 +396,11 @@ pub(crate) trait ValConvert: Into<Val> + TryFrom<Val> {
     }
 }
 
-impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(());
-impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(bool);
-impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(u32);
-impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(i32);
-impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(Error);
+impl_tryfroms_and_tryfromvals_delegating_to_valconvert!(());
+impl_tryfroms_and_tryfromvals_delegating_to_valconvert!(bool);
+impl_tryfroms_and_tryfromvals_delegating_to_valconvert!(u32);
+impl_tryfroms_and_tryfromvals_delegating_to_valconvert!(i32);
+impl_tryfroms_and_tryfromvals_delegating_to_valconvert!(Error);
 
 #[cfg(feature = "wasmi")]
 pub trait WasmiMarshal: Sized {
@@ -722,7 +727,6 @@ impl Debug for Val {
                 let s: &str = ss.as_ref();
                 write!(f, "Symbol({s})")
             }
-            Tag::LedgerKeyContractInstance => write!(f, "LedgerKeyContractCode"),
 
             Tag::U64Object => fmt_obj("U64", self, f),
             Tag::I64Object => fmt_obj("I64", self, f),
diff --git a/soroban-env-common/src/vmcaller_env.rs b/soroban-env-common/src/vmcaller_env.rs
index dd4105ae..e9e5ee29 100644
--- a/soroban-env-common/src/vmcaller_env.rs
+++ b/soroban-env-common/src/vmcaller_env.rs
@@ -36,8 +36,8 @@ impl<'a, T> VmCaller<'a, T> {
         match &self.0 {
             Some(caller) => Ok(caller),
             None => Err(Error::from_type_and_code(
-                ScErrorType::WasmVm,
-                ScErrorCode::MissingValue,
+                ScErrorType::Context,
+                ScErrorCode::InternalError,
             )),
         }
     }
@@ -45,8 +45,8 @@ impl<'a, T> VmCaller<'a, T> {
         match &mut self.0 {
             Some(caller) => Ok(caller),
             None => Err(Error::from_type_and_code(
-                ScErrorType::WasmVm,
-                ScErrorCode::MissingValue,
+                ScErrorType::Context,
+                ScErrorCode::InternalError,
             )),
         }
     }
@@ -122,15 +122,15 @@ macro_rules! generate_vmcaller_checked_env_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 `VmCallerEnv`, passing a
-        /// [`VmCaller`] that wraps the wasmi Caller context, and then convert
-        /// any `Result::Err(...)` return value into a VM trap, halting VM
-        /// execution.
+        /// functions (in soroban_env_host::dispatch) call methods on
+        /// `VmCallerEnv`, 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:VmCallerEnv> Env for
-        /// T` that implements the `Env` for any `VmCallerEnv` by
-        /// passing [`VmCaller::none()`] for the first argument, allowing user
-        /// code such as the native contract to avoid writing `VmCaller::none()`
+        /// There is also a blanket `impl<T:VmCallerEnv> Env for T` that
+        /// implements the `Env` for any `VmCallerEnv` by passing
+        /// [`VmCaller::none()`] for the first argument, allowing user code such
+        /// as the native contract to avoid writing `VmCaller::none()`
         /// everywhere.
         pub trait VmCallerEnv: EnvBase
         {
@@ -172,6 +172,10 @@ macro_rules! vmcaller_none_function_helper {
     {fn $fn_id:ident($($arg:ident:$type:ty),*) -> $ret:ty}
     =>
     {
+        // We call `augment_err_result` here to give the Env a chance to attach
+        // context (eg. a backtrace) to any error that was generated by code
+        // that didn't have an Env on hand when creating the error. This will at
+        // least localize the error to a given Env call.
         fn $fn_id(&self, $($arg:$type),*) -> Result<$ret, Self::Error> {
             self.augment_err_result(<Self as VmCallerEnv>::$fn_id(self, &mut VmCaller::none(), $($arg),*))
         }
diff --git a/soroban-env-common/src/wrapper_macros.rs b/soroban-env-common/src/wrapper_macros.rs
index be521826..3df173e3 100644
--- a/soroban-env-common/src/wrapper_macros.rs
+++ b/soroban-env-common/src/wrapper_macros.rs
@@ -1,6 +1,6 @@
 #[doc(hidden)]
 #[macro_export]
-macro_rules! impl_wrapper_tag_based_rawvalconvertible {
+macro_rules! impl_wrapper_tag_based_valconvert {
     ($tagname:ident) => {
         // A ValConvert impl for types where the wrapper _has the same
         // name_ as a Tag::case and being-that-wrapper is identical to
@@ -41,7 +41,7 @@ macro_rules! impl_wrapper_tag_based_constructors {
 
 #[doc(hidden)]
 #[macro_export]
-macro_rules! impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible {
+macro_rules! impl_tryfroms_and_tryfromvals_delegating_to_valconvert {
     ($T:ty) => {
         impl TryFrom<$crate::Val> for $T {
             type Error = $crate::ConversionError;
@@ -177,7 +177,7 @@ macro_rules! impl_rawval_wrapper_base {
         }
         $crate::impl_wrapper_as_and_to_rawval!($T);
         $crate::impl_wrapper_wasmi_conversions!($T);
-        $crate::impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!($T);
+        $crate::impl_tryfroms_and_tryfromvals_delegating_to_valconvert!($T);
     };
 }
 
@@ -216,7 +216,7 @@ macro_rules! declare_tag_based_wrapper {
         pub struct $T($crate::Val);
 
         $crate::impl_rawval_wrapper_base!($T);
-        $crate::impl_wrapper_tag_based_rawvalconvertible!($T);
+        $crate::impl_wrapper_tag_based_valconvert!($T);
         $crate::impl_wrapper_tag_based_constructors!($T);
     };
 }
diff --git a/soroban-env-guest/src/guest.rs b/soroban-env-guest/src/guest.rs
index b50dbc9d..b9b7226a 100644
--- a/soroban-env-guest/src/guest.rs
+++ b/soroban-env-guest/src/guest.rs
@@ -1,6 +1,3 @@
-#![allow(dead_code)]
-#![allow(unused_variables)]
-
 use core::convert::Infallible;
 
 use soroban_env_common::call_macro_with_all_host_functions;
@@ -11,9 +8,16 @@ use super::{
     U256Object, U256Val, U32Val, U64Object, U64Val, Val, VecObject, Void,
 };
 use super::{Env, EnvBase, Symbol};
-#[cfg(target_family = "wasm")]
 use static_assertions as sa;
 
+// Just the smallest possible version of a runtime assertion-or-panic.
+#[inline(always)]
+fn require(b: bool) {
+    if !b {
+        core::arch::wasm32::unreachable()
+    }
+}
+
 /// The [Guest] is the implementation of the [Env] interface seen and used by
 /// contracts built into WASM for execution within a WASM VM. It is a 0-sized
 /// "stub" type implementation of the [Env] interface that forwards each [Env]
@@ -22,133 +26,20 @@ use static_assertions as sa;
 #[derive(Copy, Clone, Default)]
 pub struct Guest;
 
-// The Guest struct is only meaningful when compiling for the WASM target. All
-// these fns should not be called at all because the SDK's choice of Env should be
-// Host for a non-WASM build.
-#[cfg(not(target_family = "wasm"))]
 impl EnvBase for Guest {
     type Error = Infallible;
 
-    #[cfg(feature = "testutils")]
-    fn escalate_error_to_panic(&self, e: Self::Error) -> ! {
-        unimplemented!()
-    }
-
-    fn as_mut_any(&mut self) -> &mut dyn core::any::Any {
-        unimplemented!()
-    }
-
-    fn check_same_env(&self, other: &Self) {
-        unimplemented!()
-    }
-
-    fn deep_clone(&self) -> Self {
-        unimplemented!()
-    }
-
-    fn bytes_copy_from_slice(
-        &self,
-        b: BytesObject,
-        b_pos: U32Val,
-        slice: &[u8],
-    ) -> Result<BytesObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn bytes_copy_to_slice(
-        &self,
-        b: BytesObject,
-        b_pos: U32Val,
-        slice: &mut [u8],
-    ) -> Result<(), Self::Error> {
-        unimplemented!()
-    }
-
-    fn string_copy_to_slice(
-        &self,
-        b: soroban_env_common::StringObject,
-        b_pos: U32Val,
-        slice: &mut [u8],
-    ) -> Result<(), Self::Error> {
-        unimplemented!()
-    }
-
-    fn symbol_copy_to_slice(
-        &self,
-        b: soroban_env_common::SymbolObject,
-        b_pos: U32Val,
-        slice: &mut [u8],
-    ) -> Result<(), Self::Error> {
-        unimplemented!()
-    }
-
-    fn bytes_new_from_slice(&self, slice: &[u8]) -> Result<BytesObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn string_new_from_slice(
-        &self,
-        s: &str,
-    ) -> Result<soroban_env_common::StringObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn symbol_new_from_slice(
-        &self,
-        s: &str,
-    ) -> Result<soroban_env_common::SymbolObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn map_new_from_slices(&self, _keys: &[&str], _vals: &[Val]) -> Result<MapObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn map_unpack_to_slice(
-        &self,
-        _map: MapObject,
-        _keys: &[&str],
-        _vals: &mut [Val],
-    ) -> Result<Void, Self::Error> {
-        unimplemented!()
-    }
-
-    fn vec_new_from_slice(&self, _vals: &[Val]) -> Result<VecObject, Self::Error> {
-        unimplemented!()
-    }
-
-    fn vec_unpack_to_slice(&self, _vec: VecObject, _vals: &mut [Val]) -> Result<Void, Self::Error> {
-        unimplemented!()
-    }
-
-    fn symbol_index_in_strs(&self, _key: Symbol, _strs: &[&str]) -> Result<U32Val, Self::Error> {
-        unimplemented!()
+    fn error_from_error_val(&self, _e: crate::Error) -> Self::Error {
+        core::arch::wasm32::unreachable()
     }
 
-    fn log_from_slice(&self, msg: &str, vals: &[Val]) -> Result<Void, Self::Error> {
-        unimplemented!()
-    }
-}
-
-#[cfg(target_family = "wasm")]
-impl EnvBase for Guest {
-    type Error = Infallible;
-
     #[cfg(feature = "testutils")]
-    fn escalate_error_to_panic(&self, e: Self::Error) -> ! {
+    fn escalate_error_to_panic(&self, _e: Self::Error) -> ! {
         core::arch::wasm32::unreachable()
     }
 
-    fn as_mut_any(&mut self) -> &mut dyn core::any::Any {
-        return self;
-    }
-
-    fn check_same_env(&self, other: &Self) {
-        ()
-    }
-
-    fn deep_clone(&self) -> Self {
-        Self
+    fn check_same_env(&self, _other: &Self) -> Result<(), Self::Error> {
+        Ok(())
     }
 
     fn bytes_copy_from_slice(
@@ -235,7 +126,7 @@ impl EnvBase for Guest {
     fn map_new_from_slices(&self, keys: &[&str], vals: &[Val]) -> Result<MapObject, Self::Error> {
         sa::assert_eq_size!(u32, *const u8);
         sa::assert_eq_size!(u32, usize);
-        soroban_env_common::require(keys.len() == vals.len());
+        require(keys.len() == vals.len());
         let keys_lm_pos: U32Val = Val::from_u32(keys.as_ptr() as u32);
         let vals_lm_pos: U32Val = Val::from_u32(vals.as_ptr() as u32);
         let len: U32Val = Val::from_u32(keys.len() as u32);
@@ -250,7 +141,7 @@ impl EnvBase for Guest {
     ) -> Result<Void, Self::Error> {
         sa::assert_eq_size!(u32, *const u8);
         sa::assert_eq_size!(u32, usize);
-        soroban_env_common::require(keys.len() == vals.len());
+        require(keys.len() == vals.len());
         let keys_lm_pos: U32Val = Val::from_u32(keys.as_ptr() as u32);
         let vals_lm_pos: U32Val = Val::from_u32(vals.as_ptr() as u32);
         let len: U32Val = Val::from_u32(keys.len() as u32);
diff --git a/soroban-env-guest/src/lib.rs b/soroban-env-guest/src/lib.rs
index e2838364..41f8a435 100644
--- a/soroban-env-guest/src/lib.rs
+++ b/soroban-env-guest/src/lib.rs
@@ -1,13 +1,16 @@
 #![no_std]
 
 //! This crate provides the [Guest] type, the "stub" implementation of the [Env]
-//! interface used for communicating between a contract guest and its host.
+//! interface used for communicating between a contract guest and its host. It
+//! is only defined for `cfg(target_family = "wasm")`.
 //!
 //! It also re-exports all of the content of the [soroban_env_common] crate for
 //! use by guest code. Most of the type and module definitions visible here are
 //! actually defined in the common crate.
 
+#[cfg(target_family = "wasm")]
 mod guest;
 
+#[cfg(target_family = "wasm")]
 pub use guest::Guest;
 pub use soroban_env_common::*;
diff --git a/soroban-env-host/Cargo.toml b/soroban-env-host/Cargo.toml
index 3e52eb01..d4a61178 100644
--- a/soroban-env-host/Cargo.toml
+++ b/soroban-env-host/Cargo.toml
@@ -54,7 +54,7 @@ pretty_assertions = "1.4.0"
 
 [features]
 testutils = ["soroban-env-common/testutils"]
-next = ["soroban-env-common/next", "soroban-test-wasms/next"]
+next = ["soroban-env-common/next", "soroban-test-wasms/next", "soroban-synth-wasm/next", "soroban-bench-utils/next"]
 tracy = ["dep:tracy-client"]
 
 [[bench]]
diff --git a/soroban-env-host/benches/common/cost_types/mod.rs b/soroban-env-host/benches/common/cost_types/mod.rs
index 32a7aed4..128bd36a 100644
--- a/soroban-env-host/benches/common/cost_types/mod.rs
+++ b/soroban-env-host/benches/common/cost_types/mod.rs
@@ -9,6 +9,7 @@ mod host_mem_cpy;
 mod invoke;
 mod map_ops;
 mod num_ops;
+mod prng;
 mod recover_ecdsa_secp256k1_key;
 mod val_deser;
 mod val_ser;
@@ -29,6 +30,7 @@ pub(crate) use host_mem_cpy::*;
 pub(crate) use invoke::*;
 pub(crate) use map_ops::*;
 pub(crate) use num_ops::*;
+pub(crate) use prng::*;
 pub(crate) use recover_ecdsa_secp256k1_key::*;
 pub(crate) use val_deser::*;
 pub(crate) use val_ser::*;
diff --git a/soroban-env-host/benches/common/cost_types/prng.rs b/soroban-env-host/benches/common/cost_types/prng.rs
new file mode 100644
index 00000000..4b1e3a67
--- /dev/null
+++ b/soroban-env-host/benches/common/cost_types/prng.rs
@@ -0,0 +1,18 @@
+use crate::common::HostCostMeasurement;
+use rand::{rngs::StdRng, SeedableRng};
+use rand_chacha::ChaCha20Rng;
+use soroban_env_host::{cost_runner::ChaCha20DrawBytesRun, Host};
+
+pub(crate) struct ChaCha20DrawBytesMeasure;
+
+impl HostCostMeasurement for ChaCha20DrawBytesMeasure {
+    type Runner = ChaCha20DrawBytesRun;
+
+    fn new_random_case(_host: &Host, _rng: &mut StdRng, input: u64) -> (ChaCha20Rng, Vec<u8>) {
+        let size = 1 + input * Self::STEP_SIZE;
+        let seed = [0u8; 32];
+        let rng = ChaCha20Rng::from_seed(seed);
+        let dest = vec![0u8; size as usize];
+        (rng, dest)
+    }
+}
diff --git a/soroban-env-host/benches/common/mod.rs b/soroban-env-host/benches/common/mod.rs
index e8c852d5..bd320dc8 100644
--- a/soroban-env-host/benches/common/mod.rs
+++ b/soroban-env-host/benches/common/mod.rs
@@ -87,6 +87,7 @@ pub(crate) fn for_each_host_cost_measurement<B: Benchmark>(
     call_bench::<B, Int256DivMeasure>(&mut params)?;
     call_bench::<B, Int256PowMeasure>(&mut params)?;
     call_bench::<B, Int256ShiftMeasure>(&mut params)?;
+    call_bench::<B, ChaCha20DrawBytesMeasure>(&mut params)?;
 
     if get_explicit_bench_names().is_none() {
         for cost in ContractCostType::variants() {
diff --git a/soroban-env-host/benches/common/modelfit.rs b/soroban-env-host/benches/common/modelfit.rs
index afe20863..1f8b18c9 100644
--- a/soroban-env-host/benches/common/modelfit.rs
+++ b/soroban-env-host/benches/common/modelfit.rs
@@ -1,4 +1,3 @@
-use log::info;
 use std::collections::HashSet;
 use std::str::FromStr;
 
diff --git a/soroban-env-host/benches/common/util.rs b/soroban-env-host/benches/common/util.rs
index ef2501fa..9dce9d51 100644
--- a/soroban-env-host/benches/common/util.rs
+++ b/soroban-env-host/benches/common/util.rs
@@ -1,7 +1,12 @@
-use soroban_env_host::{budget::AsBudget, Host, Val, I256};
+use soroban_env_host::{budget::AsBudget, meta, Host, LedgerInfo, Val, I256};
 
 pub(crate) fn test_host() -> Host {
     let host = Host::default();
+    host.set_ledger_info(LedgerInfo {
+        protocol_version: meta::get_ledger_protocol_version(meta::INTERFACE_VERSION),
+        ..Default::default()
+    })
+    .unwrap();
     host.as_budget().reset_unlimited().unwrap();
     host.as_budget().reset_fuel_config().unwrap();
     host
diff --git a/soroban-env-host/src/budget.rs b/soroban-env-host/src/budget.rs
index 0bab914a..6c01d0c5 100644
--- a/soroban-env-host/src/budget.rs
+++ b/soroban-env-host/src/budget.rs
@@ -15,9 +15,10 @@ use crate::{
 
 use wasmi::{errors, FuelCosts, ResourceLimiter};
 
-// These should match the default network config settings in core
-pub const DEFAULT_CPU_INSN_LIMIT: u64 = 100_000_000;
-pub const DEFAULT_MEM_BYTES_LIMIT: u64 = 100 * 1024 * 1024; // 100MB
+// These are some sane values, however the embedder should typically customize
+// these to match the network config.
+const DEFAULT_CPU_INSN_LIMIT: u64 = 100_000_000;
+const DEFAULT_MEM_BYTES_LIMIT: u64 = 40 * 1024 * 1024; // 40MB
 
 /// The number of bits to scale the linear term by. The linear coefficient has
 /// been scaled by this factor during parameter fitting to retain more significant
@@ -429,6 +430,7 @@ impl BudgetImpl {
                 ContractCostType::Int256Div => (),
                 ContractCostType::Int256Pow => (),
                 ContractCostType::Int256Shift => (),
+                ContractCostType::ChaCha20DrawBytes => init_input(i), // number of random bytes to draw
             }
         }
     }
@@ -618,6 +620,10 @@ impl Default for BudgetImpl {
                     cpu.const_term = 412;
                     cpu.lin_term = ScaledU64(0);
                 }
+                ContractCostType::ChaCha20DrawBytes => {
+                    cpu.const_term = 4907;
+                    cpu.lin_term = ScaledU64(2461);
+                }
             }
 
             // define the memory cost model parameters
@@ -739,6 +745,10 @@ impl Default for BudgetImpl {
                     mem.const_term = 119;
                     mem.lin_term = ScaledU64(0);
                 }
+                ContractCostType::ChaCha20DrawBytes => {
+                    mem.const_term = 0;
+                    mem.lin_term = ScaledU64(0);
+                }
             }
 
             b.init_tracker();
@@ -1101,6 +1111,13 @@ impl Budget {
         )
     }
 
+    /// Resets the `FuelConfig` we pass into Wasmi before running calibration.
+    /// Wasmi instruction calibration requires running the same Wasmi insn
+    /// a fixed number of times, record their actual cpu and mem consumption, then
+    /// divide those numbers by the number of iterations, which is the fuel count.
+    /// Fuel count is kept tracked on the Wasmi side, based on the `FuelConfig`
+    /// of a specific fuel category. In order to get the correct, unscaled fuel
+    /// count, we have to preset all the `FuelConfig` entries to 1.
     #[cfg(any(test, feature = "testutils"))]
     pub fn reset_fuel_config(&self) -> Result<(), HostError> {
         self.0.try_borrow_mut_or_err()?.fuel_config.reset();
@@ -1112,7 +1129,7 @@ impl Budget {
     }
 
     // generate a wasmi fuel cost schedule based on our calibration
-    pub fn wasmi_fuel_costs(&self) -> Result<FuelCosts, HostError> {
+    pub(crate) fn wasmi_fuel_costs(&self) -> Result<FuelCosts, HostError> {
         let config = &self.0.try_borrow_or_err()?.fuel_config;
         let mut costs = FuelCosts::default();
         costs.base = config.base;
diff --git a/soroban-env-host/src/cost_runner/cost_types/compute_sha256_hash.rs b/soroban-env-host/src/cost_runner/cost_types/compute_sha256_hash.rs
index e9dc0c57..cd770e2c 100644
--- a/soroban-env-host/src/cost_runner/cost_types/compute_sha256_hash.rs
+++ b/soroban-env-host/src/cost_runner/cost_types/compute_sha256_hash.rs
@@ -1,6 +1,6 @@
 use std::hint::black_box;
 
-use crate::{cost_runner::CostRunner, xdr::ContractCostType};
+use crate::{cost_runner::CostRunner, host::crypto::sha256_hash_from_bytes, xdr::ContractCostType};
 
 pub struct ComputeSha256HashRun;
 
@@ -12,10 +12,7 @@ impl CostRunner for ComputeSha256HashRun {
     type RecycledType = (Option<Vec<u8>>, Vec<u8>);
 
     fn run_iter(host: &crate::Host, _iter: u64, sample: Self::SampleType) -> Self::RecycledType {
-        let hash = black_box(
-            host.sha256_hash_from_bytes(sample.as_slice())
-                .expect("sha256"),
-        );
+        let hash = black_box(sha256_hash_from_bytes(sample.as_slice(), host).expect("sha256"));
         (Some(hash), sample)
     }
 
diff --git a/soroban-env-host/src/cost_runner/cost_types/mod.rs b/soroban-env-host/src/cost_runner/cost_types/mod.rs
index 8c641699..620f8fa4 100644
--- a/soroban-env-host/src/cost_runner/cost_types/mod.rs
+++ b/soroban-env-host/src/cost_runner/cost_types/mod.rs
@@ -9,6 +9,7 @@ mod host_mem_cpy;
 mod invoke;
 mod map_ops;
 mod num_ops;
+mod prng;
 mod recover_ecdsa_secp256k1_key;
 mod val_deser;
 mod val_ser;
@@ -29,6 +30,7 @@ pub use host_mem_cpy::*;
 pub use invoke::*;
 pub use map_ops::*;
 pub use num_ops::*;
+pub use prng::*;
 pub use recover_ecdsa_secp256k1_key::*;
 pub use val_deser::*;
 pub use val_ser::*;
diff --git a/soroban-env-host/src/cost_runner/cost_types/prng.rs b/soroban-env-host/src/cost_runner/cost_types/prng.rs
new file mode 100644
index 00000000..4293f5e1
--- /dev/null
+++ b/soroban-env-host/src/cost_runner/cost_types/prng.rs
@@ -0,0 +1,36 @@
+use std::hint::black_box;
+
+use rand_chacha::ChaCha20Rng;
+
+use crate::{cost_runner::CostRunner, host::crypto::chacha20_fill_bytes, xdr::ContractCostType};
+
+pub struct ChaCha20DrawBytesRun;
+
+impl CostRunner for ChaCha20DrawBytesRun {
+    const COST_TYPE: ContractCostType = ContractCostType::ChaCha20DrawBytes;
+
+    type SampleType = (ChaCha20Rng, Vec<u8>);
+
+    type RecycledType = Self::SampleType;
+
+    fn run_iter(
+        host: &crate::Host,
+        _iter: u64,
+        mut sample: Self::SampleType,
+    ) -> Self::RecycledType {
+        black_box(
+            chacha20_fill_bytes(&mut sample.0, sample.1.as_mut_slice(), host)
+                .expect("chacha20 draw byte"),
+        );
+        black_box(sample)
+    }
+
+    fn run_baseline_iter(
+        host: &crate::Host,
+        _iter: u64,
+        sample: Self::SampleType,
+    ) -> Self::RecycledType {
+        black_box(host.charge_budget(Self::COST_TYPE, Some(0)).unwrap());
+        black_box(sample)
+    }
+}
diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs
index 80f6ab9d..f664caaf 100644
--- a/soroban-env-host/src/host.rs
+++ b/soroban-env-host/src/host.rs
@@ -53,7 +53,6 @@ use soroban_env_common::xdr::{
 use self::{
     frame::{Context, ContractReentryMode},
     metered_clone::MeteredAlloc,
-    metered_vector::MeteredVector,
     prng::Prng,
 };
 use self::{
@@ -117,6 +116,7 @@ pub(crate) struct HostImpl {
     // mainly because it's not really possible to achieve (the same budget is connected to many
     // metered sub-objects) but also because it's plausible that the person calling deep_clone
     // actually wants their clones to be metered by "the same" total budget
+    // FIXME: deep_clone is gone, maybe Budget should not be separately refcounted?
     pub(crate) budget: Budget,
     pub(crate) events: RefCell<InternalEventsBuffer>,
     authorization_manager: RefCell<AuthorizationManager>,
@@ -465,23 +465,20 @@ impl Host {
         use crate::native_contract::account_contract::ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME;
 
         let contract_id = self.hash_from_bytesobj_input("contract", contract)?;
-        let args = self.call_args_from_obj(args)?;
+        let args_vec = self.call_args_from_obj(args)?;
         let res = self.call_n_internal(
             &contract_id,
             ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME.try_into_val(self)?,
-            args.as_slice(),
+            args_vec.as_slice(),
             ContractReentryMode::Prohibited,
             true,
         );
         if let Err(e) = &res {
-            self.with_events_mut(|events| {
-                self.err_diagnostics(
-                    events,
-                    e.error,
-                    "check auth invocation for a custom account contract failed",
-                    &[],
-                )
-            })?;
+            self.error(
+                e.error,
+                "check auth invocation for a custom account contract failed",
+                &[contract.to_val(), args.to_val()],
+            );
         }
         res
     }
@@ -748,8 +745,7 @@ impl Host {
     }
 
     fn upload_contract_wasm(&self, wasm: Vec<u8>) -> Result<BytesObject, HostError> {
-        let hash_bytes: [u8; 32] = self
-            .sha256_hash_from_bytes(wasm.as_slice())?
+        let hash_bytes: [u8; 32] = crypto::sha256_hash_from_bytes(wasm.as_slice(), self)?
             .try_into()
             .map_err(|_| {
                 self.err(
@@ -944,6 +940,10 @@ impl Host {
 impl EnvBase for Host {
     type Error = HostError;
 
+    fn error_from_error_val(&self, e: soroban_env_common::Error) -> Self::Error {
+        self.error(e, "promoting Error to HostError", &[])
+    }
+
     // This function is somewhat subtle.
     //
     // It exists to allow the client of the (VmCaller)Env interface(s) to
@@ -957,9 +957,9 @@ impl EnvBase for Host {
     // of `Env::Error` as infallible by literally defining `Guest::Error` as the
     // `Infallible` type (which makes sense: we trap the user's VM on such
     // errors, don't resume it at all). But in a non-wasm, native build of a
-    // user contract, `Env=Host` and `Env::Error=HostError`, a real type you can
-    // observe. So the user might actually have a code path returning from such
-    // an error that is suddenly non-dead and receiving an
+    // user contract, `Env=Host` and `Env::Error=HostError`, an inhabited type
+    // you can observe. So the user might actually have a code path returning
+    // from such an error that is suddenly non-dead and receiving an
     // `Env::Error=HostError`, which (to maintain continuity with the VM case)
     // they then _want_ to treat as impossible-to-have-occurred just like
     // `Guest::Error`. They can panic, but that doesn't quite maintain the
@@ -968,9 +968,8 @@ impl EnvBase for Host {
     //
     // When such a "rejected error" occurs, we do panic, but only after checking
     // to see if we're in a `TestContract` invocation, and if so storing the
-    // error's Error value in that frame, such that `Host::call_n` above can
-    // recover the Error when it _catches_ the panic and converts it back to an
-    // error.
+    // error's Error value in that frame, such that `Host::call_n` can recover
+    // the Error when it _catches_ the panic and converts it back to an error.
     //
     // It might seem like we ought to `std::panic::panic_any(e)` here, making
     // the panic carry a `HostError` or `Error` and catching it by dynamic type
@@ -979,7 +978,7 @@ impl EnvBase for Host {
     // it constructs, so when/if the panic makes it to a top-level printout it
     // will display a relatively ugly message like "thread panicked at Box<dyn
     // Any>" to stderr, when it is much more useful to the user if we have it
-    // print the result of HostError::Debug, with its glorious status code,
+    // print the result of HostError::Debug, with its glorious Error,
     // site-of-origin backtrace and debug log.
     //
     // To get it to do that, we have to call `panic!()`, not `panic_any`.
@@ -1008,18 +1007,17 @@ impl EnvBase for Host {
         x
     }
 
-    fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
-        todo!()
-    }
-
-    fn check_same_env(&self, other: &Self) {
-        assert!(Rc::ptr_eq(&self.0, &other.0));
-    }
-
-    // This function is not being metered, it's not used anywhere so it's okay
-    // for now
-    fn deep_clone(&self) -> Self {
-        Host(Rc::new((*self.0).clone()))
+    fn check_same_env(&self, other: &Self) -> Result<(), Self::Error> {
+        if Rc::ptr_eq(&self.0, &other.0) {
+            Ok(())
+        } else {
+            Err(self.err(
+                ScErrorType::Context,
+                ScErrorCode::InternalError,
+                "check_same_env on different Hosts",
+                &[],
+            ))
+        }
     }
 
     fn bytes_copy_from_slice(
@@ -1069,6 +1067,7 @@ impl EnvBase for Host {
     }
 
     fn symbol_new_from_slice(&self, s: &str) -> Result<SymbolObject, HostError> {
+        self.charge_budget(ContractCostType::HostMemCmp, Some(s.len() as u64))?;
         for ch in s.chars() {
             SymbolSmall::validate_char(ch)?;
         }
@@ -1202,6 +1201,7 @@ impl VmCallerEnv for Host {
         vals_len: U32Val,
     ) -> Result<Void, HostError> {
         if self.is_debug()? {
+            // FIXME: change to a "debug budget" https://github.com/stellar/rs-soroban-env/issues/1061
             self.as_budget().with_free_budget(|| {
                 let VmSlice { vm, pos, len } = self.decode_vmslice(msg_pos, msg_len)?;
                 let mut msg: Vec<u8> = vec![0u8; len as usize];
@@ -1224,15 +1224,6 @@ impl VmCallerEnv for Host {
         Ok(Val::VOID)
     }
 
-    // Notes on metering: covered by the components
-    fn get_invoking_contract(
-        &self,
-        _vmcaller: &mut VmCaller<Host>,
-    ) -> Result<AddressObject, HostError> {
-        let invoking_contract_hash = self.get_invoking_contract_internal()?;
-        self.add_host_object(ScAddress::Contract(invoking_contract_hash))
-    }
-
     // Metered: covered by `visit`.
     fn obj_cmp(&self, _vmcaller: &mut VmCaller<Host>, a: Val, b: Val) -> Result<i64, HostError> {
         self.check_val_integrity(a)?;
@@ -1316,43 +1307,12 @@ impl VmCallerEnv for Host {
     }
 
     fn get_ledger_timestamp(&self, _vmcaller: &mut VmCaller<Host>) -> Result<U64Val, Self::Error> {
-        self.with_ledger_info(|li| Ok(self.add_host_object(li.timestamp)?.into()))
-    }
-
-    fn get_current_call_stack(
-        &self,
-        _vmcaller: &mut VmCaller<Host>,
-    ) -> Result<VecObject, HostError> {
-        let contexts = self.try_borrow_context()?;
-
-        let get_host_val_tuple = |id: &Hash, function: &Symbol| -> Result<[Val; 2], HostError> {
-            let addr_val = self
-                .add_host_object(ScAddress::Contract(id.metered_clone(self)?))?
-                .into();
-            let function_val = (*function).into();
-            Ok([addr_val, function_val])
-        };
-
-        let mut outer = Vec::with_capacity(contexts.len());
-        for context in contexts.iter() {
-            let vals = match &context.frame {
-                Frame::ContractVM { vm, fn_name, .. } => {
-                    get_host_val_tuple(&vm.contract_id, fn_name)?
-                }
-                Frame::HostFunction(_) => continue,
-                Frame::Token(id, function, ..) => get_host_val_tuple(id, function)?,
-                #[cfg(any(test, feature = "testutils"))]
-                Frame::TestContract(tc) => get_host_val_tuple(&tc.id, &tc.func)?,
-            };
-            let inner = MeteredVector::from_array(&vals, self.as_budget())?;
-            outer.push(self.add_host_object(inner)?.into());
-        }
-        self.add_host_object(HostVec::from_vec(outer)?)
+        self.with_ledger_info(|li| Ok(U64Val::try_from_val(self, &li.timestamp)?))
     }
 
     fn fail_with_error(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         error: Error,
     ) -> Result<Void, Self::Error> {
         if error.is_type(ScErrorType::Contract) {
@@ -1365,7 +1325,7 @@ impl VmCallerEnv for Host {
             Err(self.err(
                 ScErrorType::Context,
                 ScErrorCode::UnexpectedType,
-                "contract attempted to fail with non-ContractError status code",
+                "contract attempted to fail with non-ContractError error code",
                 &[error.to_val()],
             ))
         }
@@ -1376,6 +1336,7 @@ impl VmCallerEnv for Host {
         _vmcaller: &mut VmCaller<Host>,
     ) -> Result<BytesObject, Self::Error> {
         self.with_ledger_info(|li| {
+            // FIXME: cache this and a few other such IDs: https://github.com/stellar/rs-soroban-env/issues/681
             self.add_host_object(self.scbytes_from_slice(li.network_id.as_slice())?)
         })
     }
@@ -1385,6 +1346,7 @@ impl VmCallerEnv for Host {
         &self,
         _vmcaller: &mut VmCaller<Host>,
     ) -> Result<AddressObject, HostError> {
+        // FIXME: cache this and a few other such IDs: https://github.com/stellar/rs-soroban-env/issues/681
         self.add_host_object(ScAddress::Contract(
             self.get_current_contract_id_internal()?,
         ))
@@ -1412,7 +1374,7 @@ impl VmCallerEnv for Host {
 
     fn obj_from_u128_pieces(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         hi: u64,
         lo: u64,
     ) -> Result<U128Object, Self::Error> {
@@ -1421,23 +1383,23 @@ impl VmCallerEnv for Host {
 
     fn obj_to_u128_lo64(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: U128Object,
     ) -> Result<u64, Self::Error> {
-        self.visit_obj(obj, move |u: &u128| Ok(int128_helpers::u128_lo(*u)))
+        self.visit_obj(obj, |u: &u128| Ok(int128_helpers::u128_lo(*u)))
     }
 
     fn obj_to_u128_hi64(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: U128Object,
     ) -> Result<u64, Self::Error> {
-        self.visit_obj(obj, move |u: &u128| Ok(int128_helpers::u128_hi(*u)))
+        self.visit_obj(obj, |u: &u128| Ok(int128_helpers::u128_hi(*u)))
     }
 
     fn obj_from_i128_pieces(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         hi: i64,
         lo: u64,
     ) -> Result<I128Object, Self::Error> {
@@ -1446,23 +1408,23 @@ impl VmCallerEnv for Host {
 
     fn obj_to_i128_lo64(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: I128Object,
     ) -> Result<u64, Self::Error> {
-        self.visit_obj(obj, move |i: &i128| Ok(int128_helpers::i128_lo(*i)))
+        self.visit_obj(obj, |i: &i128| Ok(int128_helpers::i128_lo(*i)))
     }
 
     fn obj_to_i128_hi64(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: I128Object,
     ) -> Result<i64, Self::Error> {
-        self.visit_obj(obj, move |i: &i128| Ok(int128_helpers::i128_hi(*i)))
+        self.visit_obj(obj, |i: &i128| Ok(int128_helpers::i128_hi(*i)))
     }
 
     fn obj_from_u256_pieces(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         hi_hi: u64,
         hi_lo: u64,
         lo_hi: u64,
@@ -1473,40 +1435,38 @@ impl VmCallerEnv for Host {
 
     fn u256_val_from_be_bytes(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         bytes: BytesObject,
     ) -> Result<U256Val, HostError> {
-        let num = self.visit_obj(bytes, move |b: &ScBytes| {
+        let num = self.visit_obj(bytes, |b: &ScBytes| {
             Ok(U256::from_be_bytes(self.fixed_length_bytes_from_slice(
                 "U256 bytes",
                 b.as_slice(),
             )?))
         })?;
-        U256Val::try_from_val(self, &num).map_err(|_| ConversionError.into())
+        self.map_err(U256Val::try_from_val(self, &num))
     }
 
     fn u256_val_to_be_bytes(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         val: U256Val,
     ) -> Result<BytesObject, HostError> {
         if let Ok(so) = U256Small::try_from(val) {
             self.add_host_object(self.scbytes_from_slice(&U256::from(so).to_be_bytes())?)
         } else {
             let obj = val.try_into()?;
-            let scb = self.visit_obj(obj, move |u: &U256| {
-                self.scbytes_from_slice(&u.to_be_bytes())
-            })?;
+            let scb = self.visit_obj(obj, |u: &U256| self.scbytes_from_slice(&u.to_be_bytes()))?;
             self.add_host_object(scb)
         }
     }
 
     fn obj_to_u256_hi_hi(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: U256Object,
     ) -> Result<u64, HostError> {
-        self.visit_obj(obj, move |u: &U256| {
+        self.visit_obj(obj, |u: &U256| {
             let (hi_hi, _, _, _) = u256_into_pieces(*u);
             Ok(hi_hi)
         })
@@ -1514,10 +1474,10 @@ impl VmCallerEnv for Host {
 
     fn obj_to_u256_hi_lo(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: U256Object,
     ) -> Result<u64, HostError> {
-        self.visit_obj(obj, move |u: &U256| {
+        self.visit_obj(obj, |u: &U256| {
             let (_, hi_lo, _, _) = u256_into_pieces(*u);
             Ok(hi_lo)
         })
@@ -1525,10 +1485,10 @@ impl VmCallerEnv for Host {
 
     fn obj_to_u256_lo_hi(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: U256Object,
     ) -> Result<u64, HostError> {
-        self.visit_obj(obj, move |u: &U256| {
+        self.visit_obj(obj, |u: &U256| {
             let (_, _, lo_hi, _) = u256_into_pieces(*u);
             Ok(lo_hi)
         })
@@ -1536,10 +1496,10 @@ impl VmCallerEnv for Host {
 
     fn obj_to_u256_lo_lo(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: U256Object,
     ) -> Result<u64, HostError> {
-        self.visit_obj(obj, move |u: &U256| {
+        self.visit_obj(obj, |u: &U256| {
             let (_, _, _, lo_lo) = u256_into_pieces(*u);
             Ok(lo_lo)
         })
@@ -1547,7 +1507,7 @@ impl VmCallerEnv for Host {
 
     fn obj_from_i256_pieces(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         hi_hi: i64,
         hi_lo: u64,
         lo_hi: u64,
@@ -1558,10 +1518,10 @@ impl VmCallerEnv for Host {
 
     fn i256_val_from_be_bytes(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         bytes: BytesObject,
     ) -> Result<I256Val, HostError> {
-        let num = self.visit_obj(bytes, move |b: &ScBytes| {
+        let num = self.visit_obj(bytes, |b: &ScBytes| {
             Ok(I256::from_be_bytes(self.fixed_length_bytes_from_slice(
                 "I256 bytes",
                 b.as_slice(),
@@ -1572,26 +1532,24 @@ impl VmCallerEnv for Host {
 
     fn i256_val_to_be_bytes(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         val: I256Val,
     ) -> Result<BytesObject, HostError> {
         if let Ok(so) = I256Small::try_from(val) {
             self.add_host_object(self.scbytes_from_slice(&I256::from(so).to_be_bytes())?)
         } else {
             let obj = val.try_into()?;
-            let scb = self.visit_obj(obj, move |i: &I256| {
-                self.scbytes_from_slice(&i.to_be_bytes())
-            })?;
+            let scb = self.visit_obj(obj, |i: &I256| self.scbytes_from_slice(&i.to_be_bytes()))?;
             self.add_host_object(scb)
         }
     }
 
     fn obj_to_i256_hi_hi(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: I256Object,
     ) -> Result<i64, HostError> {
-        self.visit_obj(obj, move |i: &I256| {
+        self.visit_obj(obj, |i: &I256| {
             let (hi_hi, _, _, _) = i256_into_pieces(*i);
             Ok(hi_hi)
         })
@@ -1599,10 +1557,10 @@ impl VmCallerEnv for Host {
 
     fn obj_to_i256_hi_lo(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: I256Object,
     ) -> Result<u64, HostError> {
-        self.visit_obj(obj, move |i: &I256| {
+        self.visit_obj(obj, |i: &I256| {
             let (_, hi_lo, _, _) = i256_into_pieces(*i);
             Ok(hi_lo)
         })
@@ -1610,10 +1568,10 @@ impl VmCallerEnv for Host {
 
     fn obj_to_i256_lo_hi(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: I256Object,
     ) -> Result<u64, HostError> {
-        self.visit_obj(obj, move |i: &I256| {
+        self.visit_obj(obj, |i: &I256| {
             let (_, _, lo_hi, _) = i256_into_pieces(*i);
             Ok(lo_hi)
         })
@@ -1621,10 +1579,10 @@ impl VmCallerEnv for Host {
 
     fn obj_to_i256_lo_lo(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         obj: I256Object,
     ) -> Result<u64, HostError> {
-        self.visit_obj(obj, move |i: &I256| {
+        self.visit_obj(obj, |i: &I256| {
             let (_, _, _, lo_lo) = i256_into_pieces(*i);
             Ok(lo_lo)
         })
@@ -1646,13 +1604,13 @@ impl VmCallerEnv for Host {
     impl_bignum_host_fns_rhs_u32!(i256_shl, checked_shl, I256, I256Val, Int256Shift);
     impl_bignum_host_fns_rhs_u32!(i256_shr, checked_shr, I256, I256Val, Int256Shift);
 
+    // endregion "int" module functions
+    // region: "map" module functions
+
     fn map_new(&self, _vmcaller: &mut VmCaller<Host>) -> Result<MapObject, HostError> {
         self.add_host_object(HostMap::new())
     }
 
-    // endregion "int" module functions
-    // region: "map" module functions
-
     fn map_put(
         &self,
         _vmcaller: &mut VmCaller<Host>,
@@ -1673,12 +1631,12 @@ impl VmCallerEnv for Host {
         k: Val,
     ) -> Result<Val, HostError> {
         self.check_val_integrity(k)?;
-        self.visit_obj(m, move |hm: &HostMap| {
+        self.visit_obj(m, |hm: &HostMap| {
             hm.get(&k, self)?.copied().ok_or_else(|| {
                 self.err(
                     ScErrorType::Object,
                     ScErrorCode::MissingValue,
-                    "map key not found",
+                    "map key not found in map_get",
                     &[m.to_val(), k],
                 )
             })
@@ -1697,7 +1655,7 @@ impl VmCallerEnv for Host {
             None => Err(self.err(
                 ScErrorType::Object,
                 ScErrorCode::MissingValue,
-                "map key not found",
+                "map key not found in map_del",
                 &[m.to_val(), k],
             )),
         }
@@ -1715,7 +1673,7 @@ impl VmCallerEnv for Host {
         k: Val,
     ) -> Result<Bool, HostError> {
         self.check_val_integrity(k)?;
-        self.visit_obj(m, move |hm: &HostMap| Ok(hm.contains_key(&k, self)?.into()))
+        self.visit_obj(m, |hm: &HostMap| Ok(hm.contains_key(&k, self)?.into()))
     }
 
     fn map_key_by_pos(
@@ -1891,7 +1849,7 @@ impl VmCallerEnv for Host {
     ) -> Result<VecObject, HostError> {
         let i: u32 = i.into();
         self.check_val_integrity(x)?;
-        let vnew = self.visit_obj(v, move |hv: &HostVec| {
+        let vnew = self.visit_obj(v, |hv: &HostVec| {
             self.validate_index_lt_bound(i, hv.len())?;
             hv.set(i as usize, x, self.as_budget())
         })?;
@@ -1905,7 +1863,8 @@ impl VmCallerEnv for Host {
         i: U32Val,
     ) -> Result<Val, HostError> {
         let i: u32 = i.into();
-        self.visit_obj(v, move |hv: &HostVec| {
+        self.visit_obj(v, |hv: &HostVec| {
+            self.validate_index_lt_bound(i, hv.len())?;
             hv.get(i as usize, self.as_budget()).map(|r| *r)
         })
     }
@@ -1917,7 +1876,7 @@ impl VmCallerEnv for Host {
         i: U32Val,
     ) -> Result<VecObject, HostError> {
         let i: u32 = i.into();
-        let vnew = self.visit_obj(v, move |hv: &HostVec| {
+        let vnew = self.visit_obj(v, |hv: &HostVec| {
             self.validate_index_lt_bound(i, hv.len())?;
             hv.remove(i as usize, self.as_budget())
         })?;
@@ -1936,7 +1895,7 @@ impl VmCallerEnv for Host {
         x: Val,
     ) -> Result<VecObject, HostError> {
         self.check_val_integrity(x)?;
-        let vnew = self.visit_obj(v, move |hv: &HostVec| hv.push_front(x, self.as_budget()))?;
+        let vnew = self.visit_obj(v, |hv: &HostVec| hv.push_front(x, self.as_budget()))?;
         self.add_host_object(vnew)
     }
 
@@ -1945,7 +1904,7 @@ impl VmCallerEnv for Host {
         _vmcaller: &mut VmCaller<Host>,
         v: VecObject,
     ) -> Result<VecObject, HostError> {
-        let vnew = self.visit_obj(v, move |hv: &HostVec| hv.pop_front(self.as_budget()))?;
+        let vnew = self.visit_obj(v, |hv: &HostVec| hv.pop_front(self.as_budget()))?;
         self.add_host_object(vnew)
     }
 
@@ -1956,7 +1915,7 @@ impl VmCallerEnv for Host {
         x: Val,
     ) -> Result<VecObject, HostError> {
         self.check_val_integrity(x)?;
-        let vnew = self.visit_obj(v, move |hv: &HostVec| hv.push_back(x, self.as_budget()))?;
+        let vnew = self.visit_obj(v, |hv: &HostVec| hv.push_back(x, self.as_budget()))?;
         self.add_host_object(vnew)
     }
 
@@ -1965,7 +1924,7 @@ impl VmCallerEnv for Host {
         _vmcaller: &mut VmCaller<Host>,
         v: VecObject,
     ) -> Result<VecObject, HostError> {
-        let vnew = self.visit_obj(v, move |hv: &HostVec| hv.pop_back(self.as_budget()))?;
+        let vnew = self.visit_obj(v, |hv: &HostVec| hv.pop_back(self.as_budget()))?;
         self.add_host_object(vnew)
     }
 
@@ -1990,7 +1949,7 @@ impl VmCallerEnv for Host {
     ) -> Result<VecObject, HostError> {
         let i: u32 = i.into();
         self.check_val_integrity(x)?;
-        let vnew = self.visit_obj(v, move |hv: &HostVec| {
+        let vnew = self.visit_obj(v, |hv: &HostVec| {
             self.validate_index_le_bound(i, hv.len())?;
             hv.insert(i as usize, x, self.as_budget())
         })?;
@@ -2024,7 +1983,7 @@ impl VmCallerEnv for Host {
     ) -> Result<VecObject, HostError> {
         let start: u32 = start.into();
         let end: u32 = end.into();
-        let vnew = self.visit_obj(v, move |hv: &HostVec| {
+        let vnew = self.visit_obj(v, |hv: &HostVec| {
             let range = self.valid_range_from_start_end_bound(start, end, hv.len())?;
             hv.slice(range, self.as_budget())
         })?;
@@ -2289,7 +2248,7 @@ impl VmCallerEnv for Host {
 
     fn bump_contract_instance_and_code(
         &self,
-        vmcaller: &mut VmCaller<Self::VmUserState>,
+        _vmcaller: &mut VmCaller<Self::VmUserState>,
         contract: AddressObject,
         low_expiration_watermark: U32Val,
         high_expiration_watermark: U32Val,
@@ -2420,14 +2379,11 @@ impl VmCallerEnv for Host {
             false,
         );
         if let Err(e) = &res {
-            self.with_events_mut(|events| {
-                self.err_diagnostics(
-                    events,
-                    e.error,
-                    "contract call failed",
-                    &[func.to_val(), args.to_val()],
-                )
-            })?;
+            self.error(
+                e.error,
+                "contract call failed",
+                &[func.to_val(), args.to_val()],
+            );
         }
         res
     }
@@ -2455,19 +2411,34 @@ impl VmCallerEnv for Host {
         match res {
             Ok(rv) => Ok(rv),
             Err(e) => {
-                self.with_events_mut(|events| {
-                    self.err_diagnostics(
-                        events,
-                        e.error,
-                        "contract try_call failed",
-                        &[func.to_val(), args.to_val()],
-                    )
-                })?;
+                self.error(
+                    e.error,
+                    "contract try_call failed",
+                    &[func.to_val(), args.to_val()],
+                );
                 // Only allow to gracefully handle the recoverable errors.
                 // Non-recoverable errors should still cause guest to panic and
                 // abort execution.
                 if e.is_recoverable() {
-                    Ok(e.error.to_val())
+                    // Pass contract errors through.
+                    if e.error.is_type(ScErrorType::Contract) {
+                        Ok(e.error.to_val())
+                    } else {
+                        // Narrow all the remaining host errors down to a single
+                        // error type. We don't want to expose the granular host
+                        // errors to the guest, consistently with how every
+                        // other host function works. This reduces the risk of
+                        // implementation being 'locked' into specific error
+                        // codes due to them being exposed to the guest and
+                        // hashed into blockchain.
+                        // The granular error codes are still observable with
+                        // diagnostic events.
+                        Ok(Error::from_type_and_code(
+                            ScErrorType::Context,
+                            ScErrorCode::InvalidAction,
+                        )
+                        .to_val())
+                    }
                 } else {
                     Err(e)
                 }
@@ -2626,7 +2597,7 @@ impl VmCallerEnv for Host {
     ) -> Result<BytesObject, HostError> {
         let i: u32 = iv.into();
         let u = self.u8_from_u32val_input("u", u)?;
-        let vnew = self.visit_obj(b, move |hv: &ScBytes| {
+        let vnew = self.visit_obj(b, |hv: &ScBytes| {
             let mut vnew: Vec<u8> = hv.metered_clone(self)?.into();
             match vnew.get_mut(i as usize) {
                 None => Err(self.err(
@@ -2673,7 +2644,7 @@ impl VmCallerEnv for Host {
         i: U32Val,
     ) -> Result<BytesObject, HostError> {
         let i: u32 = i.into();
-        let vnew = self.visit_obj(b, move |hv: &ScBytes| {
+        let vnew = self.visit_obj(b, |hv: &ScBytes| {
             self.validate_index_lt_bound(i, hv.len())?;
             let mut vnew: Vec<u8> = hv.metered_clone(self)?.into();
             // len > i has been verified above but use saturating_sub just in case
@@ -2725,7 +2696,7 @@ impl VmCallerEnv for Host {
         u: U32Val,
     ) -> Result<BytesObject, HostError> {
         let u = self.u8_from_u32val_input("u", u)?;
-        let vnew = self.visit_obj(b, move |hv: &ScBytes| {
+        let vnew = self.visit_obj(b, |hv: &ScBytes| {
             // we allocate the new vector to be able to hold `len + 1` bytes, so that the push
             // will not trigger a reallocation, causing data to be cloned twice.
             let len = hv.len().saturating_add(1);
@@ -2744,7 +2715,7 @@ impl VmCallerEnv for Host {
         _vmcaller: &mut VmCaller<Host>,
         b: BytesObject,
     ) -> Result<BytesObject, HostError> {
-        let vnew = self.visit_obj(b, move |hv: &ScBytes| {
+        let vnew = self.visit_obj(b, |hv: &ScBytes| {
             let mut vnew: Vec<u8> = hv.metered_clone(self)?.into();
             // Popping will not trigger reallocation. Here we don't charge anything since this is
             // just a `len` reduction.
@@ -2810,7 +2781,7 @@ impl VmCallerEnv for Host {
     ) -> Result<BytesObject, HostError> {
         let i: u32 = i.into();
         let u = self.u8_from_u32val_input("u", u)?;
-        let vnew = self.visit_obj(b, move |hv: &ScBytes| {
+        let vnew = self.visit_obj(b, |hv: &ScBytes| {
             self.validate_index_le_bound(i, hv.len())?;
             // we allocate the new vector to be able to hold `len + 1` bytes, so that the push
             // will not trigger a reallocation, causing data to be cloned twice.
@@ -2859,7 +2830,7 @@ impl VmCallerEnv for Host {
     ) -> Result<BytesObject, HostError> {
         let start: u32 = start.into();
         let end: u32 = end.into();
-        let vnew = self.visit_obj(b, move |hv: &ScBytes| {
+        let vnew = self.visit_obj(b, |hv: &ScBytes| {
             let range = self.valid_range_from_start_end_bound(start, end, hv.len())?;
             self.metered_slice_to_vec(&hv.as_slice()[range])
         })?;
diff --git a/soroban-env-host/src/host/comparison.rs b/soroban-env-host/src/host/comparison.rs
index 5bea9b03..c6815ed4 100644
--- a/soroban-env-host/src/host/comparison.rs
+++ b/soroban-env-host/src/host/comparison.rs
@@ -689,9 +689,6 @@ mod tests {
                 Val::try_from_val(host, &ScVal::Symbol(xdr::ScSymbol::try_from("").unwrap()))
                     .unwrap()
             }
-            Tag::LedgerKeyContractInstance => {
-                Val::try_from_val(host, &ScVal::LedgerKeyContractInstance).unwrap()
-            }
             Tag::SmallCodeUpperBound => panic!(),
             Tag::ObjectCodeLowerBound => panic!(),
             Tag::U64Object => Val::try_from_val(host, &u64::MAX).unwrap(),
diff --git a/soroban-env-host/src/host/conversion.rs b/soroban-env-host/src/host/conversion.rs
index 6d1935ad..54be959c 100644
--- a/soroban-env-host/src/host/conversion.rs
+++ b/soroban-env-host/src/host/conversion.rs
@@ -194,6 +194,7 @@ impl Host {
         durability: ContractDataDurability,
     ) -> Result<Rc<LedgerKey>, HostError> {
         let key_scval = self.from_host_val(k)?;
+        // FIXME: also check for ScVal::ContractInstance here?
         if let ScVal::LedgerKeyContractInstance | ScVal::LedgerKeyNonce(_) = key_scval {
             return Err(self.err(
                 ScErrorType::Storage,
@@ -205,13 +206,14 @@ impl Host {
         self.storage_key_from_scval(key_scval, durability)
     }
 
-    /// Converts a binary search result into a u64. `res` is `Some(index)`
-    /// if the value was found at `index`, or `Err(index)` if the value was not found
-    /// and would've needed to be inserted at `index`.
-    /// Returns a Some(res_u64) where :
-    /// - the high-32 bits is 0x0001 if element existed or 0x0000 if it didn't
-    /// - the low-32 bits contains the u32 representation of the `index`
-    /// Err(_) if the `index` fails to be converted to an u32.
+    /// Converts a binary search result into a u64. `res` is `Some(index)` if
+    /// the value was found at `index`, or `Err(index)` if the value was not
+    /// found and would've needed to be inserted at `index`. Returns a
+    /// Some(res_u64) where :
+    /// - the high 32 bits is 0x0000_0001 if element existed or 0x0000_0000 if
+    ///   it didn't
+    /// - the low 32 bits contains the u32 representation of the `index` Err(_)
+    /// if the `index` fails to be converted to an u32.
     pub(crate) fn u64_from_binary_search_result(
         &self,
         res: Result<usize, usize>,
diff --git a/soroban-env-host/src/host/crypto.rs b/soroban-env-host/src/host/crypto.rs
index 99819a25..5db712d3 100644
--- a/soroban-env-host/src/host/crypto.rs
+++ b/soroban-env-host/src/host/crypto.rs
@@ -1,9 +1,11 @@
 use crate::{
-    budget::Budget,
+    budget::AsBudget,
     err,
     xdr::{ContractCostType, Hash, ScBytes, ScErrorCode, ScErrorType},
     BytesObject, Host, HostError, U32Val, Val,
 };
+use rand::RngCore;
+use rand_chacha::ChaCha20Rng;
 use sha2::Sha256;
 use sha3::Keccak256;
 
@@ -193,13 +195,12 @@ impl Host {
 
     // SHA256 functions
 
-    pub(crate) fn sha256_hash_from_bytes(&self, bytes: &[u8]) -> Result<Vec<u8>, HostError> {
-        sha256_hash_from_bytes(bytes, self.budget_ref())
-    }
-
-    pub fn sha256_hash_from_bytesobj_input(&self, x: BytesObject) -> Result<Vec<u8>, HostError> {
+    pub(crate) fn sha256_hash_from_bytesobj_input(
+        &self,
+        x: BytesObject,
+    ) -> Result<Vec<u8>, HostError> {
         self.visit_obj(x, |bytes: &ScBytes| {
-            let hash = self.sha256_hash_from_bytes(bytes.as_slice())?;
+            let hash = sha256_hash_from_bytes(bytes.as_slice(), self)?;
             if hash.len() != 32 {
                 return Err(err!(
                     self,
@@ -244,11 +245,26 @@ impl Host {
     }
 }
 
-pub(crate) fn sha256_hash_from_bytes(bytes: &[u8], budget: &Budget) -> Result<Vec<u8>, HostError> {
+pub(crate) fn sha256_hash_from_bytes(
+    bytes: &[u8],
+    budget: impl AsBudget,
+) -> Result<Vec<u8>, HostError> {
     let _span = tracy_span!("sha256");
-    budget.charge(
+    budget.as_budget().charge(
         ContractCostType::ComputeSha256Hash,
         Some(bytes.len() as u64),
     )?;
     Ok(<Sha256 as sha2::Digest>::digest(bytes).as_slice().to_vec())
 }
+
+pub(crate) fn chacha20_fill_bytes(
+    rng: &mut ChaCha20Rng,
+    dest: &mut [u8],
+    budget: impl AsBudget,
+) -> Result<(), HostError> {
+    budget
+        .as_budget()
+        .charge(ContractCostType::ChaCha20DrawBytes, Some(dest.len() as u64))?;
+    rng.fill_bytes(dest);
+    Ok(())
+}
diff --git a/soroban-env-host/src/host/frame.rs b/soroban-env-host/src/host/frame.rs
index 9d4d83aa..51f25d09 100644
--- a/soroban-env-host/src/host/frame.rs
+++ b/soroban-env-host/src/host/frame.rs
@@ -399,32 +399,6 @@ impl Host {
         }
     }
 
-    pub(crate) fn get_invoking_contract_internal(&self) -> Result<Hash, HostError> {
-        let frames = self.try_borrow_context()?;
-        // the previous frame must exist and must be a contract
-        let hash = match frames.as_slice() {
-            [.., c2, _] => match &c2.frame {
-                Frame::ContractVM { vm, .. } => Ok(vm.contract_id.metered_clone(self)?),
-                Frame::HostFunction(_) => Err(self.err(
-                    ScErrorType::Context,
-                    ScErrorCode::UnexpectedType,
-                    "invoker is not a contract",
-                    &[],
-                )),
-                Frame::Token(id, ..) => Ok(id.metered_clone(self)?),
-                #[cfg(any(test, feature = "testutils"))]
-                Frame::TestContract(tc) => Ok(tc.id.metered_clone(self)?), // no metering
-            },
-            _ => Err(self.err(
-                ScErrorType::Context,
-                ScErrorCode::MissingValue,
-                "no frames to derive the invoker from",
-                &[],
-            )),
-        }?;
-        Ok(hash)
-    }
-
     /// Pushes a test contract [`Frame`], runs a closure, and then pops the
     /// frame, rolling back if the closure returned an error. Returns the result
     /// that the closure returned (or any error caused during the frame
diff --git a/soroban-env-host/src/host/num.rs b/soroban-env-host/src/host/num.rs
index a90b451a..0f59a220 100644
--- a/soroban-env-host/src/host/num.rs
+++ b/soroban-env-host/src/host/num.rs
@@ -29,7 +29,7 @@ macro_rules! impl_bignum_host_fns {
     ($host_fn: ident, $method: ident, $num: ty, $valty: ty, $cost: ident) => {
         fn $host_fn(
             &self,
-            vmcaller: &mut VmCaller<Self::VmUserState>,
+            _vmcaller: &mut VmCaller<Self::VmUserState>,
             lhs_val: $valty,
             rhs_val: $valty,
         ) -> Result<$valty, Self::Error> {
@@ -55,7 +55,7 @@ macro_rules! impl_bignum_host_fns_rhs_u32 {
     ($host_fn: ident, $method: ident, $num: ty, $valty: ty, $cost: ident) => {
         fn $host_fn(
             &self,
-            vmcaller: &mut VmCaller<Self::VmUserState>,
+            _vmcaller: &mut VmCaller<Self::VmUserState>,
             lhs_val: $valty,
             rhs_val: U32Val,
         ) -> Result<$valty, Self::Error> {
diff --git a/soroban-env-host/src/host/prng.rs b/soroban-env-host/src/host/prng.rs
index 51fd9a9a..876b827e 100644
--- a/soroban-env-host/src/host/prng.rs
+++ b/soroban-env-host/src/host/prng.rs
@@ -1,4 +1,7 @@
-use super::declared_size::DeclaredSizeForMetering;
+use super::{
+    crypto::chacha20_fill_bytes, declared_size::DeclaredSizeForMetering,
+    metered_clone::MeteredContainer,
+};
 use crate::{
     budget::Budget,
     host::metered_clone::MeteredClone,
@@ -7,10 +10,7 @@ use crate::{
     HostError,
 };
 use rand::{distributions::Uniform, prelude::Distribution, seq::SliceRandom};
-use rand_chacha::{
-    rand_core::{RngCore, SeedableRng},
-    ChaCha20Rng,
-};
+use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng};
 use std::ops::RangeInclusive;
 
 /// PRNG subsystem in the host, which provides best-effort pseudo-randomness to
@@ -86,8 +86,7 @@ static_assertions::const_assert_eq!(SEED_BYTES, 32);
 
 impl Prng {
     fn charge_prng_bytes(&self, budget: &Budget, count: u64) -> Result<(), HostError> {
-        // TODO: add a ContractCostType for drawing PRNG bytes
-        Ok(())
+        budget.charge(ContractCostType::ChaCha20DrawBytes, Some(count))
     }
 
     pub fn new_from_seed(seed: Seed) -> Self {
@@ -129,17 +128,15 @@ impl Prng {
     }
 
     pub(crate) fn bytes_new(&mut self, size: u32, budget: &Budget) -> Result<ScBytes, HostError> {
-        budget.charge(ContractCostType::HostMemAlloc, Some(size as u64))?;
-        self.charge_prng_bytes(budget, size as u64)?;
+        Vec::<u8>::charge_bulk_init_cpy(size as u64, budget)?;
         let mut vec = vec![0u8; size as usize];
-        self.0.fill_bytes(&mut vec);
+        chacha20_fill_bytes(&mut self.0, vec.as_mut_slice(), budget)?;
         Ok(ScBytes::try_from(vec)?)
     }
 
     pub(crate) fn sub_prng(&mut self, budget: &Budget) -> Result<Prng, HostError> {
         let mut new_seed: Seed = [0; SEED_BYTES as usize];
-        self.charge_prng_bytes(budget, SEED_BYTES)?;
-        self.0.fill_bytes(&mut new_seed);
+        chacha20_fill_bytes(&mut self.0, &mut new_seed, budget)?;
         budget.charge(ContractCostType::HostMemCpy, Some(SEED_BYTES))?;
         Ok(Self(ChaCha20Rng::from_seed(new_seed)))
     }
diff --git a/soroban-env-host/src/vm.rs b/soroban-env-host/src/vm.rs
index cbb95baf..34cf6c84 100644
--- a/soroban-env-host/src/vm.rs
+++ b/soroban-env-host/src/vm.rs
@@ -347,7 +347,14 @@ impl Vm {
             match e {
                 wasmi::Error::Trap(trap) => {
                     if let Some(code) = trap.trap_code() {
-                        return Err(code.into());
+                        let err = code.into();
+                        return Err(if host.is_debug()? {
+                            // With diagnostics on: log as much detail as we can from wasmi.
+                            let msg = format!("VM call trapped: {:?}", &code);
+                            host.error(err, &msg, &[func_sym.to_val(), err.to_val()])
+                        } else {
+                            err.into()
+                        });
                     }
                     if let Some(he) = trap.downcast::<HostError>() {
                         host.log_diagnostics(


Test patch:
diff --git a/soroban-env-host/src/test/budget_metering.rs b/soroban-env-host/src/test/budget_metering.rs
index 6ec649b0..f6c34065 100644
--- a/soroban-env-host/src/test/budget_metering.rs
+++ b/soroban-env-host/src/test/budget_metering.rs
@@ -349,6 +349,7 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> {
         (1, None),
         (1, None),
         (1, None),
+        (1, Some(1)),
     ];
 
     for ty in ContractCostType::variants() {
@@ -357,8 +358,8 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> {
     let actual = format!("{:?}", host.as_budget());
     expect![[r#"
         =====================================================================================================================================================================
-        Cpu limit: 100000000; used: 10115314
-        Mem limit: 104857600; used: 276044
+        Cpu limit: 100000000; used: 10120240
+        Mem limit: 41943040; used: 276044
         =====================================================================================================================================================================
         CostType                 iterations     input          cpu_insns      mem_bytes      const_term_cpu      lin_term_cpu        const_term_mem      lin_term_mem        
         WasmInsnExec             246            None           1476           0              6                   0                   0                   0                   
@@ -389,8 +390,9 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> {
         Int256Div                1              None           2333           119            2333                0                   119                 0                   
         Int256Pow                1              None           5212           119            5212                0                   119                 0                   
         Int256Shift              1              None           412            119            412                 0                   119                 0                   
+        ChaCha20DrawBytes        1              Some(1)        4926           0              4907                2461                0                   0                   
         =====================================================================================================================================================================
-        Total # times meter was called: 28
+        Total # times meter was called: 29
 
     "#]]
     .assert_eq(&actual);
diff --git a/soroban-env-host/src/test/invocation.rs b/soroban-env-host/src/test/invocation.rs
index b0d09d43..7a98f762 100644
--- a/soroban-env-host/src/test/invocation.rs
+++ b/soroban-env-host/src/test/invocation.rs
@@ -103,9 +103,8 @@ fn invoke_cross_contract_with_err() -> Result<(), HostError> {
 
     // try_call
     let sv = host.try_call(id_obj, sym, args)?;
-    let code = (ScErrorType::Object, ScErrorCode::IndexBounds);
-    let exp_st: Error = code.into();
-    assert_eq!(sv.get_payload(), exp_st.to_val().get_payload());
+    let generic_host_error: Error = (ScErrorType::Context, ScErrorCode::InvalidAction).into();
+    assert_eq!(sv.get_payload(), generic_host_error.to_val().get_payload());
 
     let events = host.get_events()?.0;
     assert_eq!(events.len(), 5);
@@ -119,7 +118,10 @@ fn invoke_cross_contract_with_err() -> Result<(), HostError> {
 
     // call
     let res = host.call(id_obj, sym, args);
-    assert!(HostError::result_matches_err(res, code));
+    assert!(HostError::result_matches_err(
+        res,
+        (ScErrorType::Object, ScErrorCode::IndexBounds)
+    ));
 
     let events = host.get_events()?.0;
     assert_eq!(events.len(), 10);
@@ -159,14 +161,16 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {
     let args = host.test_vec_obj::<i32>(&[i32::MAX, 1])?;
     let args = host.vec_push_back(args, host.bytes_new()?.to_val())?;
 
-    // try call -- add will trap, and add_with will trap, but we will get a status
-    let status = host.try_call(id0_obj, sym, args)?;
-    let code = (ScErrorType::WasmVm, ScErrorCode::InvalidAction);
-    let exp: Error = code.into();
-    assert_eq!(status.get_payload(), exp.to_val().get_payload());
+    // try call -- add will trap, and add_with will trap, but we will get an error
+    let error = host.try_call(id0_obj, sym, args)?;
+    let generic_host_error: Error = (ScErrorType::Context, ScErrorCode::InvalidAction).into();
+    assert_eq!(
+        error.get_payload(),
+        generic_host_error.to_val().get_payload()
+    );
 
     let events = host.get_events()?.0;
-    assert_eq!(events.len(), 2);
+    assert_eq!(events.len(), 3);
     let last_event = events.last().unwrap();
     // run `UPDATE_EXPECT=true cargo test` to update this.
     let expected = expect![[
@@ -177,10 +181,13 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {
 
     // call
     let res = host.call(id0_obj, sym, args);
-    assert!(HostError::result_matches_err(res, code));
+    assert!(HostError::result_matches_err(
+        res,
+        (ScErrorType::WasmVm, ScErrorCode::InvalidAction)
+    ));
 
     let events = host.get_events()?.0;
-    assert_eq!(events.len(), 4);
+    assert_eq!(events.len(), 6);
     let last_event = events.last().unwrap();
     // run `UPDATE_EXPECT=true cargo test` to update this.
     let expected = expect![[
diff --git a/soroban-env-host/src/test/util.rs b/soroban-env-host/src/test/util.rs
index af7f409b..8e5d5ea7 100644
--- a/soroban-env-host/src/test/util.rs
+++ b/soroban-env-host/src/test/util.rs
@@ -251,18 +251,38 @@ impl Host {
         let ht = HostTracker::start(None);
 
         let val = self.call(contract, func, args);
-        let cpu_consumed = budget
+        let cpu_metered = budget
             .get_cpu_insns_consumed()
             .expect("unable to retrieve cpu consumed");
-        let mem_consumed = budget
+        let mem_metered = budget
             .get_mem_bytes_consumed()
             .expect("unable to retrieve mem consumed");
 
-        let (cpu_insns, mem_bytes, _) = ht.stop();
+        let (cpu_actual, mem_actual, time_nsecs) = ht.stop();
+        let cpu_diff = (cpu_metered - cpu_actual) as i64;
+        let cpu_metered_diff_percent = 100 * cpu_diff / (cpu_metered as i64).max(1);
+        let mem_diff = (mem_metered - mem_actual) as i64;
+        let mem_metered_diff_percent = 100 * mem_diff / (mem_metered as i64).max(1);
+        let metered_insn_nsecs_ratio: f64 = (cpu_metered as f64) / (time_nsecs as f64).max(1.0);
+        let actual_insn_nsecs_ratio: f64 = (cpu_actual as f64) / (time_nsecs as f64).max(1.0);
+        println!();
         println!(
-                "\nmodel cpu consumed: {}, actual cpu insns {} \nmodel mem consumed: {}, actual mem bytes {}",
-                cpu_consumed, cpu_insns, mem_consumed, mem_bytes
-            );
+            "metered cpu insns: {}, actual cpu insns {}, diff: {} ({}%) \n",
+            cpu_metered, cpu_actual, cpu_diff, cpu_metered_diff_percent
+        );
+        println!(
+            "metered mem bytes: {}, actual mem bytes {}, diff: {} ({}%) \n",
+            mem_metered, mem_actual, mem_diff, mem_metered_diff_percent
+        );
+        println!(
+            "metered cpu_insn/time_nsecs ratio: {} \n",
+            metered_insn_nsecs_ratio
+        );
+        println!(
+            "actual cpu_insn/time_nsecs ratio: {} \n",
+            actual_insn_nsecs_ratio
+        );
+        println!();
 
         val
     }
diff --git a/soroban-test-wasms/wasm-workspace/Cargo.lock b/soroban-test-wasms/wasm-workspace/Cargo.lock
index bb92c50a..2a55ebf2 100644
--- a/soroban-test-wasms/wasm-workspace/Cargo.lock
+++ b/soroban-test-wasms/wasm-workspace/Cargo.lock
@@ -1047,7 +1047,7 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
 
 [[package]]
 name = "soroban-env-common"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "arbitrary",
  "crate-git-revision",
@@ -1058,12 +1058,12 @@ dependencies = [
  "soroban-env-macros",
  "soroban-wasmi",
  "static_assertions",
- "stellar-xdr",
+ "stellar-xdr 20.0.0-rc1 (git+https://github.com/stellar/rs-stellar-xdr?rev=c9d24eb4fae1f9968f6620e06750c1fce7a5d6c3)",
 ]
 
 [[package]]
 name = "soroban-env-guest"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "soroban-env-common",
  "static_assertions",
@@ -1071,7 +1071,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-env-host"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "backtrace",
  "ed25519-dalek",
@@ -1093,20 +1093,20 @@ dependencies = [
 
 [[package]]
 name = "soroban-env-macros"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "itertools",
  "proc-macro2",
  "quote",
  "serde",
  "serde_json",
- "stellar-xdr",
+ "stellar-xdr 20.0.0-rc1 (git+https://github.com/stellar/rs-stellar-xdr?rev=c9d24eb4fae1f9968f6620e06750c1fce7a5d6c3)",
  "syn",
 ]
 
 [[package]]
 name = "soroban-ledger-snapshot"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "serde",
  "serde_json",
@@ -1117,7 +1117,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-native-sdk-macros"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "itertools",
  "proc-macro2",
@@ -1127,7 +1127,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-sdk"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "arbitrary",
  "bytes-lit",
@@ -1143,7 +1143,7 @@ dependencies = [
 
 [[package]]
 name = "soroban-sdk-macros"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "crate-git-revision",
  "darling",
@@ -1155,30 +1155,30 @@ dependencies = [
  "soroban-env-common",
  "soroban-spec",
  "soroban-spec-rust",
- "stellar-xdr",
+ "stellar-xdr 20.0.0-rc1 (git+https://github.com/stellar/rs-stellar-xdr?rev=d5ce0c9e7aa83461773a6e81662067f35d39e4c1)",
  "syn",
 ]
 
 [[package]]
 name = "soroban-spec"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "base64 0.13.1",
- "stellar-xdr",
+ "stellar-xdr 20.0.0-rc1 (git+https://github.com/stellar/rs-stellar-xdr?rev=d5ce0c9e7aa83461773a6e81662067f35d39e4c1)",
  "thiserror",
  "wasmparser",
 ]
 
 [[package]]
 name = "soroban-spec-rust"
-version = "20.0.0-rc1"
+version = "20.0.0-rc2"
 dependencies = [
  "prettyplease",
  "proc-macro2",
  "quote",
  "sha2",
  "soroban-spec",
- "stellar-xdr",
+ "stellar-xdr 20.0.0-rc1 (git+https://github.com/stellar/rs-stellar-xdr?rev=d5ce0c9e7aa83461773a6e81662067f35d39e4c1)",
  "syn",
  "thiserror",
 ]
@@ -1237,7 +1237,7 @@ dependencies = [
 [[package]]
 name = "stellar-xdr"
 version = "20.0.0-rc1"
-source = "git+https://github.com/stellar/rs-stellar-xdr?rev=d5ce0c9e7aa83461773a6e81662067f35d39e4c1#d5ce0c9e7aa83461773a6e81662067f35d39e4c1"
+source = "git+https://github.com/stellar/rs-stellar-xdr?rev=c9d24eb4fae1f9968f6620e06750c1fce7a5d6c3#c9d24eb4fae1f9968f6620e06750c1fce7a5d6c3"
 dependencies = [
  "arbitrary",
  "base64 0.13.1",
@@ -1247,6 +1247,17 @@ dependencies = [
  "serde_with",
 ]
 
+[[package]]
+name = "stellar-xdr"
+version = "20.0.0-rc1"
+source = "git+https://github.com/stellar/rs-stellar-xdr?rev=d5ce0c9e7aa83461773a6e81662067f35d39e4c1#d5ce0c9e7aa83461773a6e81662067f35d39e4c1"
+dependencies = [
+ "crate-git-revision",
+ "hex",
+ "serde",
+ "serde_with",
+]
+
 [[package]]
 name = "strsim"
 version = "0.10.0"