Repository: stellar/rs-soroban-env
Pull request #307 (2022-08-02 20:41:43)

Host budget metering
### What
Set of changes needed to cover budget metering for the majority of the host functionalities. Resolves https://github.com/stellar/rs-soroban-env/issues/232.
(See commit log for details) Most notably:
- A set of "proxy" structs (all prefixed with "Metered"), for `im::OrdMap`, `im::Vector`, `BigInt`, in which every major function is proceeded by a `charge_budget` call with a corresponding budget type. 
- Host `Storage` access is metered. 
- Defined a new trait `MeteredClone` which meters cloning. Implemented for `Bytes`-related types and the above-mentioned proxy structs. 
- Miscellaneous other metering charges: se/deser `ScVal`, VM instantantiation, function invocation. Host func/object access. Plus a bunch of comments. 

cc @MonsieurNicolas Since we discussed about this topic numerous times. 

### Why

[TODO: Why this change is being made. Include any context required to understand the why.]

### Known limitations

[TODO or N/A]


jayz22: Thanks @graydon, I've addressed all your points / created a couple of issues to follow up. 
graydon: Thanks for all the work @jayz22 ! This is a big slog, and you've been very patient. Really appreciate the attention to detail.

Patch:
diff --git a/.gitignore b/.gitignore
index ea8c4bf7..9026c77a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,1 +1,2 @@
 /target
+.vscode
diff --git a/Cargo.lock b/Cargo.lock
index 0a9fa3de..8ebb007a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -105,9 +105,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.2"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
+checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813"
 dependencies = [
  "libc",
 ]
@@ -191,9 +191,9 @@ dependencies = [
 
 [[package]]
 name = "either"
-version = "1.7.0"
+version = "1.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
 
 [[package]]
 name = "env_logger"
@@ -281,9 +281,9 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.131"
+version = "0.2.132"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40"
+checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
 
 [[package]]
 name = "libm"
@@ -374,9 +374,9 @@ dependencies = [
 
 [[package]]
 name = "once_cell"
-version = "1.13.0"
+version = "1.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
+checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
 
 [[package]]
 name = "opaque-debug"
@@ -550,18 +550,18 @@ checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
 
 [[package]]
 name = "serde"
-version = "1.0.143"
+version = "1.0.144"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
+checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.143"
+version = "1.0.144"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
+checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -594,9 +594,9 @@ dependencies = [
 
 [[package]]
 name = "signature"
-version = "1.5.0"
+version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4"
+checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a"
 
 [[package]]
 name = "sized-chunks"
diff --git a/soroban-env-host/benches/common/measure.rs b/soroban-env-host/benches/common/measure.rs
index 13aac123..5c7354e2 100644
--- a/soroban-env-host/benches/common/measure.rs
+++ b/soroban-env-host/benches/common/measure.rs
@@ -221,7 +221,7 @@ pub fn measure_costs<HCM: HostCostMeasurement>(
     eprintln!("\nMeasuring costs for CostType::{:?}\n", HCM::COST_TYPE);
     for input_hint in step_range {
         let host = Host::default();
-        host.get_budget_mut(|budget| budget.reset_unlimited());
+        host.get_budget(|budget| budget.reset_unlimited());
         let mut m = HCM::new(&host, input_hint);
         let start = Instant::now();
         mem_tracker.0.store(0, Ordering::SeqCst);
diff --git a/soroban-env-host/src/budget.rs b/soroban-env-host/src/budget.rs
index ba7032d6..cebfabc1 100644
--- a/soroban-env-host/src/budget.rs
+++ b/soroban-env-host/src/budget.rs
@@ -1,35 +1,83 @@
+use std::{
+    cell::{RefCell, RefMut},
+    rc::Rc,
+};
+
 use crate::{xdr::ScVmErrorCode, HostError};
 
 // TODO: move this to an XDR enum
 #[repr(i32)]
 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum CostType {
-    HostVecAllocVec = 0,
-    HostVecAllocCell = 1,
-    HostMapAllocMap = 2,
-    HostMapAllocCell = 3,
-    WasmInsnExec = 4,
-    WasmMemAlloc = 5,
-    HostEventDebug = 6,
-    HostEventContract = 7,
-    HostFunction = 8,
-    VisitObject = 9,
-    PushFrame = 10,
-    PopFrame = 11,
+    WasmInsnExec = 0,
+    WasmMemAlloc = 1,
+    HostEventDebug = 2,
+    HostEventContract = 3,
+    HostFunction = 4,
+    VisitObject = 5,
+    PushFrame = 6,
+    PopFrame = 7,
     // Tracks a single Val (RawVal or primative Object like U64) <=> ScVal
     // conversion cost. Most of these Val counterparts in ScVal (except e.g.
     // Symbol) consumes a single int64 and therefore is a constant overhead.
-    ValXdrConv = 12,
+    ValXdrConv = 8,
+    ValSer = 9,
+    ValDeser = 10,
+    CloneEvents = 11,
+    HostObjAllocSlot = 12,
+    HostVecAllocCell = 13,
+    HostMapAllocCell = 14,
+    HostU64AllocCell = 15,
+    HostI64AllocCell = 16,
+    HostBinAllocCell = 17,
+    HostBigIntAllocCell = 18,
+    ComputeSha256Hash = 19,
+    ComputeEd25519PubKey = 20,
+    ImMapNew = 21,
+    ImMapMutEntry = 22,
+    ImMapImmutEntry = 23,
+    ImVecNew = 24,
+    ImVecMutEntry = 25,
+    ImVecImmutEntry = 26,
+    ScVecFromHostVec = 27,
+    ScMapFromHostMap = 28,
+    ScVecToHostVec = 29,
+    ScMapToHostMap = 30,
+    GuardFrame = 31,
+    CloneVm = 32,
+    VerifyEd25519Sig = 33,
+    BigIntNew = 34,
+    BigIntAddSub = 35,
+    BigIntMul = 36,
+    BigIntDivRem = 37,
+    BigIntBitwiseOp = 38,
+    BigIntShift = 39,
+    BigIntCmp = 40,
+    BigIntGcdLcm = 41,
+    BigIntPow = 42,
+    BigIntPowMod = 43,
+    BigIntSqrt = 44,
+    BigIntFromBytes = 45,
+    BigIntToBytes = 46,
+    BigIntToRadix = 47,
+    VmMemCpy = 48,
+    VmInstantiation = 49,
+    VmInvokeFunction = 50,
+    BytesClone = 51,
+    BytesDel = 52,
+    BytesPush = 53,
+    BytesPop = 54,
+    BytesInsert = 55,
+    BytesAppend = 56,
+    BytesSlice = 57,
+    BytesConcat = 58,
+    CallArgsUnpack = 59,
 }
 
 // TODO: add XDR support for iterating over all the elements of an enum
 impl CostType {
     pub fn variants() -> std::slice::Iter<'static, CostType> {
         static VARIANTS: &'static [CostType] = &[
-            CostType::HostMapAllocMap,
-            CostType::HostMapAllocCell,
-            CostType::HostVecAllocVec,
-            CostType::HostVecAllocCell,
             CostType::WasmInsnExec,
             CostType::WasmMemAlloc,
             CostType::HostEventDebug,
@@ -39,6 +87,57 @@ impl CostType {
             CostType::PushFrame,
             CostType::PopFrame,
             CostType::ValXdrConv,
+            CostType::ValSer,
+            CostType::ValDeser,
+            CostType::CloneEvents,
+            CostType::HostObjAllocSlot,
+            CostType::HostVecAllocCell,
+            CostType::HostMapAllocCell,
+            CostType::HostU64AllocCell,
+            CostType::HostI64AllocCell,
+            CostType::HostBinAllocCell,
+            CostType::HostBigIntAllocCell,
+            CostType::ComputeSha256Hash,
+            CostType::ComputeEd25519PubKey,
+            CostType::ImMapNew,
+            CostType::ImMapMutEntry,
+            CostType::ImMapImmutEntry,
+            CostType::ImVecNew,
+            CostType::ImVecMutEntry,
+            CostType::ImVecImmutEntry,
+            CostType::ScVecFromHostVec,
+            CostType::ScMapFromHostMap,
+            CostType::ScVecToHostVec,
+            CostType::ScMapToHostMap,
+            CostType::GuardFrame,
+            CostType::CloneVm,
+            CostType::VerifyEd25519Sig,
+            CostType::BigIntNew,
+            CostType::BigIntAddSub,
+            CostType::BigIntMul,
+            CostType::BigIntDivRem,
+            CostType::BigIntBitwiseOp,
+            CostType::BigIntShift,
+            CostType::BigIntCmp,
+            CostType::BigIntGcdLcm,
+            CostType::BigIntPow,
+            CostType::BigIntPowMod,
+            CostType::BigIntSqrt,
+            CostType::BigIntFromBytes,
+            CostType::BigIntToBytes,
+            CostType::BigIntToRadix,
+            CostType::VmMemCpy,
+            CostType::VmInstantiation,
+            CostType::VmInvokeFunction,
+            CostType::BytesClone,
+            CostType::BytesDel,
+            CostType::BytesPush,
+            CostType::BytesPop,
+            CostType::BytesInsert,
+            CostType::BytesAppend,
+            CostType::BytesSlice,
+            CostType::BytesConcat,
+            CostType::CallArgsUnpack,
         ];
         VARIANTS.iter()
     }
@@ -212,7 +311,7 @@ impl Default for BudgetDimension {
 }
 
 #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct Budget {
+pub(crate) struct BudgetImpl {
     pub cpu_insns: BudgetDimension,
     pub mem_bytes: BudgetDimension,
     /// Tracks the sums of _input_ values to the cost models, for purposes of
@@ -220,51 +319,85 @@ pub struct Budget {
     inputs: Vec<u64>,
 }
 
+#[derive(Default, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Budget(pub(crate) Rc<RefCell<BudgetImpl>>);
+
 impl Budget {
-    pub fn charge(&mut self, ty: CostType, input: u64) -> Result<(), HostError> {
-        let i = self.get_input_mut(ty);
-        *i = i.saturating_add(input);
+    // Helper function to avoid multiple borrow_mut
+    fn mut_budget<T, F>(&self, f: F) -> Result<T, HostError>
+    where
+        F: FnOnce(RefMut<BudgetImpl>) -> Result<T, HostError>,
+    {
+        f(self.0.borrow_mut())
+    }
 
-        self.cpu_insns.charge(ty, input)?;
-        self.mem_bytes.charge(ty, input)?;
-        Ok(())
+    pub fn charge(&self, ty: CostType, input: u64) -> Result<(), HostError> {
+        self.get_input_mut(ty, |i| *i = i.saturating_add(input));
+        self.mut_budget(|mut b| {
+            b.cpu_insns.charge(ty, input)?;
+            b.mem_bytes.charge(ty, input)
+        })
     }
 
     pub fn get_input(&self, ty: CostType) -> u64 {
-        self.inputs[ty as usize]
+        self.0.borrow().inputs[ty as usize]
     }
 
-    pub fn get_input_mut(&mut self, ty: CostType) -> &mut u64 {
-        &mut self.inputs[ty as usize]
+    fn get_input_mut<F>(&self, ty: CostType, f: F)
+    where
+        F: FnOnce(&mut u64),
+    {
+        f(&mut self.0.borrow_mut().inputs[ty as usize])
     }
 
-    pub fn reset_unlimited(&mut self) {
-        self.cpu_insns.reset(u64::MAX);
-        self.mem_bytes.reset(u64::MAX);
+    pub fn get_cpu_insns_count(&self) -> u64 {
+        self.0.borrow().cpu_insns.get_count()
+    }
+
+    pub fn get_mem_bytes_count(&self) -> u64 {
+        self.0.borrow().mem_bytes.get_count()
+    }
+
+    pub fn reset_unlimited(&self) {
+        self.mut_budget(|mut b| {
+            b.cpu_insns.reset(u64::MAX);
+            b.mem_bytes.reset(u64::MAX);
+            Ok(())
+        })
+        .unwrap(); // impossible to panic
         self.reset_inputs()
     }
 
-    pub fn reset_inputs(&mut self) {
-        for i in self.inputs.iter_mut() {
+    pub fn reset_inputs(&self) {
+        for i in self.0.borrow_mut().inputs.iter_mut() {
             *i = 0;
         }
     }
 
     #[cfg(test)]
-    pub fn reset_limits(&mut self, cpu: u64, mem: u64) {
-        self.cpu_insns.reset(cpu);
-        self.mem_bytes.reset(mem);
+    pub fn reset_limits(&self, cpu: u64, mem: u64) {
+        self.mut_budget(|mut b| {
+            b.cpu_insns.reset(cpu);
+            b.mem_bytes.reset(mem);
+            Ok(())
+        })
+        .unwrap(); // impossible to panic
+
         self.reset_inputs()
     }
 
     #[cfg(test)]
-    pub fn reset_models(&mut self) {
-        self.cpu_insns.reset_models();
-        self.mem_bytes.reset_models();
+    pub fn reset_models(&self) {
+        self.mut_budget(|mut b| {
+            b.cpu_insns.reset_models();
+            b.mem_bytes.reset_models();
+            Ok(())
+        })
+        .unwrap(); // impossible to panic
     }
 }
 
-impl Default for Budget {
+impl Default for BudgetImpl {
     fn default() -> Self {
         let mut b = Self {
             cpu_insns: Default::default(),
diff --git a/soroban-env-host/src/events.rs b/soroban-env-host/src/events.rs
index 7790fb20..4a4af3e2 100644
--- a/soroban-env-host/src/events.rs
+++ b/soroban-env-host/src/events.rs
@@ -145,7 +145,7 @@ impl DebugError {
         let status: Status = status.into();
         Self {
             event: DebugEvent::new().msg("status").arg::<RawVal>(status.into()),
-            status: status,
+            status,
         }
     }
 
diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs
index 456c0880..0aa2b0ab 100644
--- a/soroban-env-host/src/host.rs
+++ b/soroban-env-host/src/host.rs
@@ -5,12 +5,8 @@ use core::cell::RefCell;
 use core::cmp::Ordering;
 use core::fmt::Debug;
 use im_rc::{OrdMap, Vector};
-use num_bigint::{BigInt, Sign};
-use num_integer::Integer;
-use num_traits::cast::ToPrimitive;
-use num_traits::{Pow, Signed, Zero};
-use soroban_env_common::{EnvVal, TryConvert, TryFromVal, TryIntoVal, OK};
-use std::ops::{Add, BitAnd, BitOr, BitXor, Mul, Neg, Not, Rem, Shl, Shr, Sub};
+use num_bigint::Sign;
+use soroban_env_common::{EnvVal, TryConvert, TryFromVal, TryIntoVal, OK, UNKNOWN_ERROR};
 
 use soroban_env_common::xdr::{
     AccountId, ContractEvent, ContractEventBody, ContractEventType, ContractEventV0,
@@ -36,20 +32,30 @@ use crate::CheckedEnv;
 use crate::SymbolStr;
 #[cfg(feature = "vm")]
 use crate::Vm;
-use crate::{EnvBase, IntoVal, Object, RawVal, RawValConvertible, Symbol, Val, UNKNOWN_ERROR};
+use crate::{EnvBase, IntoVal, Object, RawVal, RawValConvertible, Symbol, Val};
 
 mod conversion;
 mod data_helper;
 mod err_helper;
 mod error;
+pub(crate) mod metered_bigint;
+pub(crate) mod metered_clone;
+pub(crate) mod metered_map;
+pub(crate) mod metered_vector;
 mod validity;
 pub use error::HostError;
 
+use self::metered_bigint::MeteredBigInt;
+use self::metered_clone::MeteredClone;
+use self::metered_map::MeteredOrdMap;
+use self::metered_vector::MeteredVector;
+
 /// Saves host state (storage and objects) for rolling back a (sub-)transaction
 /// on error. A helper type used by [`FrameGuard`].
+// Notes on metering: `RollbackPoint` are metered under Frame operations
 #[derive(Clone)]
 pub(crate) struct RollbackPoint {
-    storage: OrdMap<LedgerKey, Option<LedgerEntry>>,
+    storage: MeteredOrdMap<LedgerKey, Option<LedgerEntry>>,
     objects: usize,
 }
 
@@ -102,8 +108,16 @@ pub(crate) struct HostImpl {
     objects: RefCell<Vec<HostObject>>,
     storage: RefCell<Storage>,
     context: RefCell<Vec<Frame>>,
-    budget: RefCell<Budget>,
+    // Note: budget is refcounted and is _not_ deep-cloned when you call HostImpl::deep_clone,
+    // 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
+    budget: Budget,
     events: RefCell<Events>,
+    // Note: we're not going to charge metering for testutils because it's out of the scope
+    // of what users will be charged for in production -- it's scaffolding for testing a contract,
+    // but shouldn't be charged to the contract itself (and will never be compiled-in to
+    // production hosts)
     #[cfg(feature = "testutils")]
     contracts: RefCell<std::collections::HashMap<Hash, Rc<dyn ContractFunctionSet>>>,
 }
@@ -149,13 +163,13 @@ impl Host {
     /// Constructs a new [`Host`] that will use the provided [`Storage`] for
     /// contract-data access functions such as
     /// [`CheckedEnv::get_contract_data`].
-    pub fn with_storage(storage: Storage) -> Self {
+    pub fn with_storage_and_budget(storage: Storage, budget: Budget) -> Self {
         Self(Rc::new(HostImpl {
             ledger: RefCell::new(None),
             objects: Default::default(),
             storage: RefCell::new(storage),
             context: Default::default(),
-            budget: Default::default(),
+            budget,
             events: Default::default(),
             #[cfg(feature = "testutils")]
             contracts: Default::default(),
@@ -179,22 +193,15 @@ impl Host {
     /// Helper for mutating the [`Budget`] held in this [`Host`], either to
     /// allocate it on contract creation or to deplete it on callbacks from
     /// the VM or host functions.
-    pub fn get_budget_mut<T, F>(&self, f: F) -> T
-    where
-        F: FnOnce(&mut Budget) -> T,
-    {
-        f(&mut *self.0.budget.borrow_mut())
-    }
-
     pub fn get_budget<T, F>(&self, f: F) -> T
     where
-        F: FnOnce(&Budget) -> T,
+        F: FnOnce(Budget) -> T,
     {
-        f(&*self.0.budget.borrow())
+        f(self.0.budget.clone())
     }
 
     pub fn charge_budget(&self, ty: CostType, input: u64) -> Result<(), HostError> {
-        self.get_budget_mut(|budget| budget.charge(ty, input))
+        self.0.budget.clone().charge(ty, input)
     }
 
     pub(crate) fn get_events_mut<F, U>(&self, f: F) -> Result<U, HostError>
@@ -262,7 +269,7 @@ impl Host {
         Rc::try_unwrap(self.0)
             .map(|host_impl| {
                 let storage = host_impl.storage.into_inner();
-                let budget = host_impl.budget.into_inner();
+                let budget = host_impl.budget;
                 let events = host_impl.events.into_inner();
                 (storage, budget, events)
             })
@@ -304,6 +311,8 @@ impl Host {
     /// Applies a function to the top [`Frame`] of the context stack. Returns
     /// [`HostError`] if the context stack is empty, otherwise returns result of
     /// function call.
+    // Notes on metering: aquiring the current frame is cheap and not charged.
+    /// Metering happens in the passed-in closure where actual work is being done.
     fn with_current_frame<F, U>(&self, f: F) -> Result<U, HostError>
     where
         F: FnOnce(&Frame) -> Result<U, HostError>,
@@ -319,10 +328,14 @@ impl Host {
     /// Pushes a [`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 push/pop).
+    // Notes on metering: `GuardFrame` charges on the work done on protecting the `context`.
+    /// It does not cover the cost of the actual closure call. The closure needs to be
+    /// metered separately.
     pub(crate) fn with_frame<F, U>(&self, frame: Frame, f: F) -> Result<U, HostError>
     where
         F: FnOnce() -> Result<U, HostError>,
     {
+        self.charge_budget(CostType::GuardFrame, 1)?;
         let start_depth = self.0.context.borrow().len();
         let rp = self.push_frame(frame)?;
         let res = f();
@@ -345,16 +358,18 @@ impl Host {
     fn get_current_contract_id(&self) -> Result<Hash, HostError> {
         self.with_current_frame(|frame| match frame {
             #[cfg(feature = "vm")]
-            Frame::ContractVM(vm) => Ok(vm.contract_id.clone()),
+            Frame::ContractVM(vm) => vm.contract_id.metered_clone(&self.0.budget),
             Frame::HostFunction(_) => {
                 Err(self.err_general("Host function context has no contract ID"))
             }
-            Frame::Token(id) => Ok(id.clone()),
+            Frame::Token(id) => id.metered_clone(&self.0.budget),
             #[cfg(feature = "testutils")]
             Frame::TestContract(id) => Ok(id.clone()),
         })
     }
 
+    // Notes on metering: closure call needs to be metered separatedly. `VisitObject` only covers
+    /// the cost of visiting an object.
     unsafe fn unchecked_visit_val_obj<F, U>(&self, val: RawVal, f: F) -> Result<U, HostError>
     where
         F: FnOnce(Option<&HostObject>) -> Result<U, HostError>,
@@ -365,6 +380,8 @@ impl Host {
         f(r.get(index))
     }
 
+    // Notes on metering: object visiting part is covered by unchecked_visit_val_obj. Closure function
+    /// needs to be metered separately.
     fn visit_obj<HOT: HostObjectType, F, U>(&self, obj: Object, f: F) -> Result<U, HostError>
     where
         F: FnOnce(&HOT) -> Result<U, HostError>,
@@ -380,19 +397,25 @@ impl Host {
         }
     }
 
+    // Notes on metering: free
     fn reassociate_val(hv: &mut HostVal, weak: WeakHost) {
         hv.env = weak;
     }
 
+    // Notes on metering: free
     pub(crate) fn get_weak(&self) -> WeakHost {
         WeakHost(Rc::downgrade(&self.0))
     }
 
+    // Notes on metering: free
     pub(crate) fn associate_raw_val(&self, val: RawVal) -> HostVal {
         let env = self.get_weak();
         HostVal { env, val }
     }
 
+    // Notes on metering: free. Any non-trivial work involved in converting from CVT to RawVal
+    // needs to go through host functions and involves host objects, which are all covered by
+    // the components' metering.
     pub(crate) fn associate_env_val_type<V: Val, CVT: IntoVal<WeakHost, RawVal>>(
         &self,
         v: CVT,
@@ -405,14 +428,16 @@ impl Host {
     }
 
     // Testing interface to create values directly for later use via Env functions.
+    // Notes on metering: covered by `to_host_val`
     pub fn inject_val(&self, v: &ScVal) -> Result<RawVal, HostError> {
         self.to_host_val(v).map(Into::into)
     }
 
-    pub fn get_events(&self) -> Events {
-        self.0.events.borrow().clone()
+    pub fn get_events(&self) -> Result<Events, HostError> {
+        self.0.events.borrow().metered_clone(&self.0.budget)
     }
 
+    // Notes on metering: free
     #[cfg(feature = "vm")]
     fn decode_vmslice(&self, pos: RawVal, len: RawVal) -> Result<VmSlice, HostError> {
         let pos: u32 = self.u32_from_rawval_input("pos", pos)?;
@@ -457,13 +482,21 @@ impl Host {
                     None => Err(self.err_status(ScHostObjErrorCode::UnknownReference)),
                     Some(ho) => match ho {
                         HostObject::Vec(vv) => {
-                            let mut sv = Vec::new();
-                            for e in vv.iter() {
-                                sv.push(self.from_host_val(e.val)?);
-                            }
+                            // Here covers the cost of space allocating and maneuvering needed to go
+                            // from one structure to the other. The actual conversion work (heavy lifting)
+                            // is covered by `from_host_val`, which is recursive.
+                            self.charge_budget(CostType::ScVecFromHostVec, vv.len() as u64)?;
+                            let sv = vv
+                                .iter()
+                                .map(|e| self.from_host_val(e.val))
+                                .collect::<Result<Vec<ScVal>, HostError>>()?;
                             Ok(ScObject::Vec(ScVec(self.map_err(sv.try_into())?)))
                         }
                         HostObject::Map(mm) => {
+                            // Here covers the cost of space allocating and maneuvering needed to go
+                            // from one structure to the other. The actual conversion work (heavy lifting)
+                            // is covered by `from_host_val`, which is recursive.
+                            self.charge_budget(CostType::ScMapFromHostMap, mm.len() as u64)?;
                             let mut mv = Vec::new();
                             for (k, v) in mm.iter() {
                                 let key = self.from_host_val(k.val)?;
@@ -474,9 +507,9 @@ impl Host {
                         }
                         HostObject::U64(u) => Ok(ScObject::U64(*u)),
                         HostObject::I64(i) => Ok(ScObject::I64(*i)),
-                        HostObject::Bin(b) => {
-                            Ok(ScObject::Bytes(self.map_err(b.clone().try_into())?))
-                        }
+                        HostObject::Bin(b) => Ok(ScObject::Bytes(
+                            self.map_err(b.metered_clone(&self.0.budget)?.try_into())?,
+                        )),
                         HostObject::BigInt(bi) => self.scobj_from_bigint(bi),
                         HostObject::Hash(h) => Ok(ScObject::Hash(h.clone())),
                         HostObject::PublicKey(pk) => Ok(ScObject::PublicKey(pk.clone())),
@@ -491,29 +524,41 @@ impl Host {
         self.charge_budget(CostType::ValXdrConv, 1)?;
         match ob {
             ScObject::Vec(v) => {
-                let mut vv = Vector::new();
-                for e in v.0.iter() {
-                    vv.push_back(self.to_host_val(e)?);
-                }
-                self.add_host_object(vv)
+                self.charge_budget(CostType::ScVecToHostVec, v.len() as u64)?;
+                let vv =
+                    v.0.iter()
+                        .map(|e| self.to_host_val(e))
+                        .collect::<Result<Vector<HostVal>, HostError>>()?;
+                self.add_host_object(MeteredVector::from_vec(self.0.budget.clone(), vv)?)
             }
             ScObject::Map(m) => {
+                self.charge_budget(CostType::ScMapToHostMap, m.len() as u64)?;
                 let mut mm = OrdMap::new();
                 for pair in m.0.iter() {
                     let k = self.to_host_val(&pair.key)?;
                     let v = self.to_host_val(&pair.val)?;
                     mm.insert(k, v);
                 }
-                self.add_host_object(mm)
+                self.add_host_object(HostMap::from_map(self.0.budget.clone(), mm)?)
             }
             ScObject::U64(u) => self.add_host_object(*u),
             ScObject::I64(i) => self.add_host_object(*i),
-            ScObject::Bytes(b) => self.add_host_object::<Vec<u8>>(b.clone().into()),
+            ScObject::Bytes(b) => {
+                self.add_host_object::<Vec<u8>>(b.as_vec().metered_clone(&self.0.budget)?.into())
+            }
             ScObject::BigInt(sbi) => {
                 let bi = match sbi {
-                    ScBigInt::Zero => BigInt::default(),
-                    ScBigInt::Positive(bytes) => BigInt::from_bytes_be(Sign::Plus, bytes.as_ref()),
-                    ScBigInt::Negative(bytes) => BigInt::from_bytes_be(Sign::Minus, bytes.as_ref()),
+                    ScBigInt::Zero => MeteredBigInt::new(self.0.budget.clone())?,
+                    ScBigInt::Positive(bytes) => MeteredBigInt::from_bytes_be(
+                        Sign::Plus,
+                        bytes.as_ref(),
+                        self.0.budget.clone(),
+                    )?,
+                    ScBigInt::Negative(bytes) => MeteredBigInt::from_bytes_be(
+                        Sign::Minus,
+                        bytes.as_ref(),
+                        self.0.budget.clone(),
+                    )?,
                 };
                 self.add_host_object(bi)
             }
@@ -527,19 +572,27 @@ impl Host {
         &self,
         ho: HostObject,
     ) -> Result<HostObject, HostError> {
+        self.charge_budget(CostType::HostObjAllocSlot, 1)?;
         match &ho {
             HostObject::Vec(v) => {
-                self.charge_budget(CostType::HostVecAllocVec, 1)?;
                 self.charge_budget(CostType::HostVecAllocCell, v.len() as u64)?;
             }
             HostObject::Map(m) => {
-                self.charge_budget(CostType::HostMapAllocMap, 1)?;
                 self.charge_budget(CostType::HostMapAllocCell, m.len() as u64)?;
             }
-            HostObject::U64(_) => {}
-            HostObject::I64(_) => {}
-            HostObject::Bin(_) => {}
-            HostObject::BigInt(_) => {}
+            HostObject::U64(_) => {
+                self.charge_budget(CostType::HostU64AllocCell, 1)?;
+            }
+            HostObject::I64(_) => {
+                self.charge_budget(CostType::HostI64AllocCell, 1)?;
+            }
+            HostObject::Bin(b) => {
+                self.charge_budget(CostType::HostBinAllocCell, b.len() as u64)?;
+            }
+            HostObject::BigInt(bi) => {
+                self.charge_budget(CostType::HostBigIntAllocCell, bi.bits() as u64)?;
+                // TODO: are we double counting by charging bi.bits()?
+            }
             HostObject::Hash(_) => {}
             HostObject::PublicKey(_) => {}
             HostObject::ContractCode(_) => {}
@@ -551,6 +604,8 @@ impl Host {
     /// object array, returning a [`HostObj`] containing the new object's array
     /// index, tagged with the [`xdr::ScObjectType`] and associated with the current
     /// host via a weak reference.
+    // Notes on metering: new object is charged by `charge_for_new_host_object`. The
+    // rest is free.
     pub(crate) fn add_host_object<HOT: HostObjectType>(
         &self,
         hot: HOT,
@@ -568,13 +623,15 @@ impl Host {
         Ok(EnvVal { env, val: v })
     }
 
+    // Notes on metering: this is covered by the called components.
     pub fn create_contract_with_id(
         &self,
         contract: ScContractCode,
         id_obj: Object,
     ) -> Result<(), HostError> {
         let new_contract_id = self.hash_from_obj_input("id_obj", id_obj)?;
-        let storage_key = self.contract_code_ledger_key(new_contract_id.clone());
+        let storage_key =
+            self.contract_code_ledger_key(new_contract_id.metered_clone(&self.0.budget)?);
         if self.0.storage.borrow_mut().has(&storage_key)? {
             return Err(self.err_general("Contract already exists"));
         }
@@ -592,6 +649,7 @@ impl Host {
         Ok(id_obj)
     }
 
+    // Notes on metering: this is covered by the called components.
     fn call_contract_fn(
         &self,
         id: &Hash,
@@ -599,11 +657,11 @@ impl Host {
         args: &[RawVal],
     ) -> Result<RawVal, HostError> {
         // Create key for storage
-        let storage_key = self.contract_code_ledger_key(id.clone());
+        let storage_key = self.contract_code_ledger_key(id.metered_clone(&self.0.budget)?);
         match self.retrieve_contract_code_from_storage(&storage_key)? {
             #[cfg(feature = "vm")]
             ScContractCode::Wasm(wasm) => {
-                let vm = Vm::new(self, id.clone(), wasm.as_slice())?;
+                let vm = Vm::new(self, id.metered_clone(&self.0.budget)?, wasm.as_slice())?;
                 vm.invoke_function_raw(self, SymbolStr::from(func).as_ref(), args)
             }
             #[cfg(not(feature = "vm"))]
@@ -615,10 +673,12 @@ impl Host {
         }
     }
 
+    // Notes on metering: this is covered by the called components.
     fn call_n(&self, contract: Object, func: Symbol, args: &[RawVal]) -> Result<RawVal, HostError> {
         // Get contract ID
         let id = self.hash_from_obj_input("contract", contract)?;
 
+        // "testutils" is not covered by budget metering.
         #[cfg(feature = "testutils")]
         {
             // This looks a little un-idiomatic, but this avoids maintaining a borrow of
@@ -639,6 +699,7 @@ impl Host {
         return self.call_contract_fn(&id, &func, args);
     }
 
+    // Notes on metering: covered by the called components.
     pub fn invoke_function_raw(&self, hf: HostFunction, args: ScVec) -> Result<RawVal, HostError> {
         match hf {
             HostFunction::Call => {
@@ -648,6 +709,7 @@ impl Host {
                     self.with_frame(Frame::HostFunction(hf), || {
                         let object = self.to_host_obj(scobj)?.to_object();
                         let symbol = <Symbol>::try_from(scsym)?;
+                        self.charge_budget(CostType::CallArgsUnpack, rest.len() as u64)?;
                         let args = rest
                             .iter()
                             .map(|scv| self.to_host_val(scv).map(|hv| hv.val))
@@ -684,11 +746,13 @@ impl Host {
         }
     }
 
+    // Notes on metering: covered by the called components.
     pub fn invoke_function(&self, hf: HostFunction, args: ScVec) -> Result<ScVal, HostError> {
         let rv = self.invoke_function_raw(hf, args)?;
         self.from_host_val(rv)
     }
 
+    // "testutils" is not covered by budget metering.
     #[cfg(feature = "testutils")]
     pub fn register_test_contract(
         &self,
@@ -705,6 +769,7 @@ impl Host {
         }
     }
 
+    // "testutils" is not covered by budget metering.
     #[cfg(feature = "testutils")]
     pub fn register_test_contract_wasm(
         &self,
@@ -727,6 +792,7 @@ impl Host {
     }
 }
 
+// Notes on metering: these are called from the guest and thus charged on the VM instructions.
 impl EnvBase for Host {
     fn as_mut_any(&mut self) -> &mut dyn std::any::Any {
         todo!()
@@ -751,7 +817,7 @@ impl EnvBase for Host {
                     vs.iter_mut().for_each(|v| v.env = new_weak.clone());
                 }
                 HostObject::Map(m) => {
-                    *m = m
+                    let mnew = m
                         .clone()
                         .into_iter()
                         .map(|(mut k, mut v)| {
@@ -759,7 +825,11 @@ impl EnvBase for Host {
                             v.env = new_weak.clone();
                             (k, v)
                         })
-                        .collect();
+                        .collect::<OrdMap<HostVal, HostVal>>();
+                    *m = HostMap {
+                        budget: self.0.budget.clone(),
+                        map: mnew,
+                    }
                 }
                 _ => (),
             }
@@ -847,24 +917,26 @@ impl EnvBase for Host {
 impl CheckedEnv for Host {
     type Error = HostError;
 
+    // Notes on metering: covered by the components
     fn log_value(&self, v: RawVal) -> Result<RawVal, HostError> {
         self.record_debug_event(DebugEvent::new().msg("log").arg(v))?;
         Ok(RawVal::from_void())
     }
 
+    // Notes on metering: covered by the components
     fn get_invoking_contract(&self) -> Result<Object, HostError> {
         let frames = self.0.context.borrow();
         // the previous frame must exist and must be a contract
         let hash: Hash = if frames.len() >= 2 {
             match &frames[frames.len() - 2] {
                 #[cfg(feature = "vm")]
-                Frame::ContractVM(vm) => Ok(vm.contract_id.clone()),
+                Frame::ContractVM(vm) => Ok(vm.contract_id.metered_clone(&self.0.budget)?),
                 Frame::HostFunction(_) => {
                     Err(self.err_general("Host function context has no contract ID"))
                 }
                 Frame::Token(id) => Ok(id.clone()),
                 #[cfg(feature = "testutils")]
-                Frame::TestContract(id) => Ok(id.clone()),
+                Frame::TestContract(id) => Ok(id.clone()), // no metering
             }
         } else {
             Err(self.err_general("no invoking contract"))
@@ -872,6 +944,8 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(<Vec<u8>>::from(hash.0))?.into())
     }
 
+    // FIXME: the `cmp` method is not metered. Need a "metered" version (similar to metered_clone)
+    // and use that.
     fn obj_cmp(&self, a: RawVal, b: RawVal) -> Result<i64, HostError> {
         let res = unsafe {
             self.unchecked_visit_val_obj(a, |ao| {
@@ -892,37 +966,44 @@ impl CheckedEnv for Host {
         Ok(OK.into())
     }
 
+    // Notes on metering: covered by the components.
     fn get_current_contract(&self) -> Result<Object, HostError> {
         let hash: Hash = self.get_current_contract_id()?;
         Ok(self.add_host_object(<Vec<u8>>::from(hash.0))?.into())
     }
 
+    // Notes on metering: covered by `add_host_object`.
     fn obj_from_u64(&self, u: u64) -> Result<Object, HostError> {
         Ok(self.add_host_object(u)?.into())
     }
 
+    // Notes on metering: covered by `visit_obj`.
     fn obj_to_u64(&self, obj: Object) -> Result<u64, HostError> {
         self.visit_obj(obj, |u: &u64| Ok(*u))
     }
 
+    // Notes on metering: covered by `add_host_object`.
     fn obj_from_i64(&self, i: i64) -> Result<Object, HostError> {
         Ok(self.add_host_object(i)?.into())
     }
 
+    // Notes on metering: covered by `visit_obj`.
     fn obj_to_i64(&self, obj: Object) -> Result<i64, HostError> {
         self.visit_obj(obj, |i: &i64| Ok(*i))
     }
 
     fn map_new(&self) -> Result<Object, HostError> {
-        Ok(self.add_host_object(HostMap::new())?.into())
+        Ok(self
+            .add_host_object(HostMap::new(self.0.budget.clone())?)?
+            .into())
     }
 
     fn map_put(&self, m: Object, k: RawVal, v: RawVal) -> Result<Object, HostError> {
         let k = self.associate_raw_val(k);
         let v = self.associate_raw_val(v);
         let mnew = self.visit_obj(m, move |hm: &HostMap| {
-            let mut mnew = hm.clone();
-            mnew.insert(k, v);
+            let mut mnew = hm.metered_clone(&self.0.budget)?;
+            mnew.insert(k, v)?;
             Ok(mnew)
         })?;
         Ok(self.add_host_object(mnew)?.into())
@@ -931,7 +1012,7 @@ impl CheckedEnv for Host {
     fn map_get(&self, m: Object, k: RawVal) -> Result<RawVal, HostError> {
         let k = self.associate_raw_val(k);
         self.visit_obj(m, move |hm: &HostMap| {
-            hm.get(&k)
+            hm.get(&k)?
                 .map(|v| v.to_raw())
                 .ok_or_else(|| self.err_general("map key not found")) // FIXME: need error code
         })
@@ -940,10 +1021,8 @@ impl CheckedEnv for Host {
     fn map_del(&self, m: Object, k: RawVal) -> Result<Object, HostError> {
         let k = self.associate_raw_val(k);
         let mnew = self.visit_obj(m, |hm: &HostMap| {
-            let mut mnew = hm.clone();
-            mnew.remove(&k)
-                .map(|_| mnew)
-                .ok_or_else(|| self.err_general("map key not found")) // FIXME: error code
+            let mut mnew = hm.metered_clone(&self.0.budget)?;
+            mnew.remove(&k).map(|_| mnew)
         })?;
         Ok(self.add_host_object(mnew)?.into())
     }
@@ -955,7 +1034,7 @@ impl CheckedEnv for Host {
 
     fn map_has(&self, m: Object, k: RawVal) -> Result<RawVal, HostError> {
         let k = self.associate_raw_val(k);
-        self.visit_obj(m, move |hm: &HostMap| Ok(hm.contains_key(&k).into()))
+        self.visit_obj(m, move |hm: &HostMap| Ok(hm.contains_key(&k)?.into()))
     }
 
     fn map_prev_key(&self, m: Object, k: RawVal) -> Result<RawVal, HostError> {
@@ -967,16 +1046,16 @@ impl CheckedEnv for Host {
             // Note on performance: OrdMap does "lazy cloning", which only happens if data is modified,
             // and we are only modifying one entry. The cloned object will be thrown away in the end
             // so there is no cost associated with host object creation and allocation.
-            if let Some((pk, pv)) = hm.get_prev(&k) {
+            if let Some((pk, _)) = hm.get_prev(&k)? {
                 if *pk != k {
                     Ok(pk.to_raw())
                 } else {
-                    if let Some((pk2, pv2)) = hm
-                        .clone()
-                        .extract(pk) // removes (pk, pv) and returns an Option<(pv, updated_map)>
+                    if let Some((pk2, _)) = hm
+                        .metered_clone(&self.0.budget)?
+                        .extract(pk)? // removes (pk, pv) and returns an Option<(pv, updated_map)>
                         .ok_or_else(|| self.err_general("key not exist"))?
                         .1
-                        .get_prev(pk)
+                        .get_prev(pk)?
                     {
                         Ok(pk2.to_raw())
                     } else {
@@ -992,16 +1071,16 @@ impl CheckedEnv for Host {
     fn map_next_key(&self, m: Object, k: RawVal) -> Result<RawVal, HostError> {
         let k = self.associate_raw_val(k);
         self.visit_obj(m, |hm: &HostMap| {
-            if let Some((pk, pv)) = hm.get_next(&k) {
+            if let Some((pk, _)) = hm.get_next(&k)? {
                 if *pk != k {
                     Ok(pk.to_raw())
                 } else {
-                    if let Some((pk2, pv2)) = hm
-                        .clone()
-                        .extract(pk) // removes (pk, pv) and returns an Option<(pv, updated_map)>
+                    if let Some((pk2, _)) = hm
+                        .metered_clone(&self.0.budget)?
+                        .extract(pk)? // removes (pk, pv) and returns an Option<(pv, updated_map)>
                         .ok_or_else(|| self.err_general("key not exist"))?
                         .1
-                        .get_next(pk)
+                        .get_next(pk)?
                     {
                         Ok(pk2.to_raw())
                     } else {
@@ -1015,16 +1094,20 @@ impl CheckedEnv for Host {
     }
 
     fn map_min_key(&self, m: Object) -> Result<RawVal, HostError> {
-        self.visit_obj(m, |hm: &HostMap| match hm.get_min() {
-            Some((pk, pv)) => Ok(pk.to_raw()),
-            None => Ok(UNKNOWN_ERROR.to_raw()), //FIXME: replace with the actual status code
+        self.visit_obj(m, |hm: &HostMap| {
+            match hm.get_min()? {
+                Some((pk, pv)) => Ok(pk.to_raw()),
+                None => Ok(UNKNOWN_ERROR.to_raw()), //FIXME: replace with the actual status code
+            }
         })
     }
 
     fn map_max_key(&self, m: Object) -> Result<RawVal, HostError> {
-        self.visit_obj(m, |hm: &HostMap| match hm.get_max() {
-            Some((pk, pv)) => Ok(pk.to_raw()),
-            None => Ok(UNKNOWN_ERROR.to_raw()), //FIXME: replace with the actual status code
+        self.visit_obj(m, |hm: &HostMap| {
+            match hm.get_max()? {
+                Some((pk, pv)) => Ok(pk.to_raw()),
+                None => Ok(UNKNOWN_ERROR.to_raw()), //FIXME: replace with the actual status code
+            }
         })
     }
 
@@ -1032,7 +1115,7 @@ impl CheckedEnv for Host {
         self.visit_obj(m, |hm: &HostMap| {
             let cap = self.usize_to_u32(hm.len(), "host map too large")?;
             let mut vec = self.vec_new(cap.into())?;
-            for k in hm.keys() {
+            for k in hm.keys()? {
                 vec = self.vec_push(vec, k.to_raw())?;
             }
             Ok(vec)
@@ -1043,7 +1126,7 @@ impl CheckedEnv for Host {
         self.visit_obj(m, |hm: &HostMap| {
             let cap = self.usize_to_u32(hm.len(), "host map too large")?;
             let mut vec = self.vec_new(cap.into())?;
-            for k in hm.values() {
+            for k in hm.values()? {
                 vec = self.vec_push(vec, k.to_raw())?;
             }
             Ok(vec)
@@ -1057,7 +1140,9 @@ impl CheckedEnv for Host {
             self.usize_from_rawval_u32_input("c", c)?
         };
         // TODO: optimize the vector based on capacity
-        Ok(self.add_host_object(HostVec::new())?.into())
+        Ok(self
+            .add_host_object(HostVec::new(self.0.budget.clone())?)?
+            .into())
     }
 
     fn vec_put(&self, v: Object, i: RawVal, x: RawVal) -> Result<Object, HostError> {
@@ -1065,8 +1150,8 @@ impl CheckedEnv for Host {
         let x = self.associate_raw_val(x);
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             self.validate_index_lt_bound(i, hv.len())?;
-            let mut vnew = hv.clone();
-            vnew.set(i as usize, x);
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            vnew.set(i as usize, x)?;
             Ok(vnew)
         })?;
         Ok(self.add_host_object(vnew)?.into())
@@ -1074,19 +1159,15 @@ impl CheckedEnv for Host {
 
     fn vec_get(&self, v: Object, i: RawVal) -> Result<RawVal, HostError> {
         let i: usize = self.usize_from_rawval_u32_input("i", i)?;
-        self.visit_obj(v, move |hv: &HostVec| {
-            hv.get(i)
-                .map(|hval| hval.to_raw())
-                .ok_or_else(|| self.err_status(ScHostObjErrorCode::VecIndexOutOfBound))
-        })
+        self.visit_obj(v, move |hv: &HostVec| hv.get(i).map(|hval| hval.to_raw()))
     }
 
     fn vec_del(&self, v: Object, i: RawVal) -> Result<Object, HostError> {
         let i = self.u32_from_rawval_input("i", i)?;
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             self.validate_index_lt_bound(i, hv.len())?;
-            let mut vnew = hv.clone();
-            vnew.remove(i as usize);
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            vnew.remove(i as usize)?;
             Ok(vnew)
         })?;
         Ok(self.add_host_object(vnew)?.into())
@@ -1100,8 +1181,8 @@ impl CheckedEnv for Host {
     fn vec_push(&self, v: Object, x: RawVal) -> Result<Object, HostError> {
         let x = self.associate_raw_val(x);
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
-            let mut vnew = hv.clone();
-            vnew.push_back(x);
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            vnew.push_back(x)?;
             Ok(vnew)
         })?;
         Ok(self.add_host_object(vnew)?.into())
@@ -1109,28 +1190,18 @@ impl CheckedEnv for Host {
 
     fn vec_pop(&self, v: Object) -> Result<Object, HostError> {
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
-            let mut vnew = hv.clone();
-            vnew.pop_back()
-                .map(|_| vnew)
-                .ok_or_else(|| self.err_status(ScHostObjErrorCode::VecIndexOutOfBound))
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            vnew.pop_back().map(|_| vnew)
         })?;
         Ok(self.add_host_object(vnew)?.into())
     }
 
     fn vec_front(&self, v: Object) -> Result<RawVal, HostError> {
-        self.visit_obj(v, |hv: &HostVec| {
-            hv.front()
-                .map(|hval| hval.to_raw())
-                .ok_or_else(|| self.err_status(ScHostObjErrorCode::VecIndexOutOfBound))
-        })
+        self.visit_obj(v, |hv: &HostVec| hv.front().map(|hval| hval.to_raw()))
     }
 
     fn vec_back(&self, v: Object) -> Result<RawVal, HostError> {
-        self.visit_obj(v, |hv: &HostVec| {
-            hv.back()
-                .map(|hval| hval.to_raw())
-                .ok_or_else(|| self.err_status(ScHostObjErrorCode::VecIndexOutOfBound))
-        })
+        self.visit_obj(v, |hv: &HostVec| hv.back().map(|hval| hval.to_raw()))
     }
 
     fn vec_insert(&self, v: Object, i: RawVal, x: RawVal) -> Result<Object, HostError> {
@@ -1138,20 +1209,20 @@ impl CheckedEnv for Host {
         let x = self.associate_raw_val(x);
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             self.validate_index_le_bound(i, hv.len())?;
-            let mut vnew = hv.clone();
-            vnew.insert(i as usize, x);
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            vnew.insert(i as usize, x)?;
             Ok(vnew)
         })?;
         Ok(self.add_host_object(vnew)?.into())
     }
 
     fn vec_append(&self, v1: Object, v2: Object) -> Result<Object, HostError> {
-        let mut vnew = self.visit_obj(v1, |hv: &HostVec| Ok(hv.clone()))?;
-        let v2 = self.visit_obj(v2, |hv: &HostVec| Ok(hv.clone()))?;
+        let mut vnew = self.visit_obj(v1, |hv: &HostVec| Ok(hv.metered_clone(&self.0.budget)?))?;
+        let v2 = self.visit_obj(v2, |hv: &HostVec| Ok(hv.metered_clone(&self.0.budget)?))?;
         if v2.len() > u32::MAX as usize - vnew.len() {
             return Err(self.err_status_msg(ScHostFnErrorCode::InputArgsInvalid, "u32 overflow"));
         }
-        vnew.append(v2);
+        vnew.append(v2)?;
         Ok(self.add_host_object(vnew)?.into())
     }
 
@@ -1160,11 +1231,12 @@ impl CheckedEnv for Host {
         let end = self.u32_from_rawval_input("end", end)?;
         let vnew = self.visit_obj(v, move |hv: &HostVec| {
             let range = self.valid_range_from_start_end_bound(start, end, hv.len())?;
-            Ok(hv.clone().slice(range))
+            hv.metered_clone(&self.0.budget)?.slice(range)
         })?;
         Ok(self.add_host_object(vnew)?.into())
     }
 
+    // Notes on metering: covered by components
     fn put_contract_data(&self, k: RawVal, v: RawVal) -> Result<RawVal, HostError> {
         let key = self.contract_data_key_from_rawval(k)?;
         let data = LedgerEntryData::ContractData(ContractDataEntry {
@@ -1181,12 +1253,14 @@ impl CheckedEnv for Host {
         Ok(().into())
     }
 
+    // Notes on metering: covered by components
     fn has_contract_data(&self, k: RawVal) -> Result<RawVal, HostError> {
         let key = self.storage_key_from_rawval(k)?;
         let res = self.0.storage.borrow_mut().has(&key)?;
         Ok(RawVal::from_bool(res))
     }
 
+    // Notes on metering: covered by components
     fn get_contract_data(&self, k: RawVal) -> Result<RawVal, HostError> {
         let key = self.storage_key_from_rawval(k)?;
         match self.0.storage.borrow_mut().get(&key)?.data {
@@ -1202,12 +1276,14 @@ impl CheckedEnv for Host {
         }
     }
 
+    // Notes on metering: covered by components
     fn del_contract_data(&self, k: RawVal) -> Result<RawVal, HostError> {
         let key = self.contract_data_key_from_rawval(k)?;
         self.0.storage.borrow_mut().del(&key)?;
         Ok(().into())
     }
 
+    // Notes on metering: covered by the components.
     fn create_contract_from_ed25519(
         &self,
         v: Object,
@@ -1222,6 +1298,11 @@ impl CheckedEnv for Host {
         let params = self.visit_obj(v, |bin: &Vec<u8>| {
             let separator = "create_contract_from_ed25519(contract: Vec<u8>, salt: u256, key: u256, sig: Vec<u8>)";
             let params = [separator.as_bytes(), salt_val.as_ref(), bin].concat();
+            // Another charge-after-work. Easier to get the num bytes this way.
+            // TODO: 1. pre calcualte the bytes and charge before concat. 
+            // 2. Might be overkill to have a separate type for this. Maybe can consolidate
+            // with `BytesClone` or `BytesAppend`.
+            self.charge_budget(CostType::BytesConcat, params.len() as u64)?;
             Ok(params)
         })?;
         let hash = self.compute_hash_sha256(self.add_host_object(params)?.into())?;
@@ -1238,6 +1319,7 @@ impl CheckedEnv for Host {
         self.create_contract_with_id_preimage(wasm, buf)
     }
 
+    // Notes on metering: covered by the components.
     fn create_contract_from_contract(&self, v: Object, salt: Object) -> Result<Object, HostError> {
         let contract_id = self.get_current_contract_id()?;
         let salt = self.uint256_from_obj_input("salt", salt)?;
@@ -1266,6 +1348,8 @@ impl CheckedEnv for Host {
             let separator = "create_token_from_ed25519(salt: u256, key: u256, sig: Vec<u8>)";
             [separator.as_bytes(), salt_val.as_ref()].concat()
         };
+        // Another charge-after-work. Easier to get the num bytes this way.
+        self.charge_budget(CostType::BytesConcat, params.len() as u64)?;
         let hash = self.compute_hash_sha256(self.add_host_object(params)?.into())?;
 
         self.verify_sig_ed25519(hash, key, sig)?;
@@ -1281,13 +1365,16 @@ impl CheckedEnv for Host {
         self.create_contract_with_id_preimage(ScContractCode::Token, buf)
     }
 
+    // Notes on metering: here covers the args unpacking. The actual VM work is changed at lower layers.
     fn call(&self, contract: Object, func: Symbol, args: Object) -> Result<RawVal, HostError> {
         let args: Vec<RawVal> = self.visit_obj(args, |hv: &HostVec| {
+            self.charge_budget(CostType::CallArgsUnpack, hv.len() as u64)?;
             Ok(hv.iter().map(|a| a.to_raw()).collect())
         })?;
         self.call_n(contract, func, args.as_slice())
     }
 
+    // Notes on metering: covered by the components.
     fn try_call(&self, contract: Object, func: Symbol, args: Object) -> Result<RawVal, HostError> {
         match self.call(contract, func, args) {
             Ok(rv) => Ok(rv),
@@ -1302,215 +1389,225 @@ impl CheckedEnv for Host {
     }
 
     fn bigint_from_u64(&self, x: u64) -> Result<Object, HostError> {
-        Ok(self.add_host_object(Into::<BigInt>::into(x))?.into())
+        Ok(self
+            .add_host_object(MeteredBigInt::from_u64(self.0.budget.clone(), x)?)?
+            .into())
     }
 
+    // Notes on metering: visiting object is covered. Conversion from BigInt to u64 is free.
     fn bigint_to_u64(&self, x: Object) -> Result<u64, HostError> {
-        self.visit_obj(x, |bi: &BigInt| {
+        self.visit_obj(x, |bi: &MeteredBigInt| {
             bi.to_u64()
                 .ok_or_else(|| self.err_conversion_into_rawval::<u64>(x.into()))
         })
     }
 
+    // Notes on metering: new object adding is covered. Conversion from i64 to BigInt is free.
     fn bigint_from_i64(&self, x: i64) -> Result<Object, HostError> {
-        Ok(self.add_host_object(Into::<BigInt>::into(x))?.into())
+        Ok(self
+            .add_host_object(MeteredBigInt::from_i64(self.0.budget.clone(), x)?)?
+            .into())
     }
 
+    // Notes on metering: visiting object is covered. Conversion from BigInt to i64 is free.
     fn bigint_to_i64(&self, x: Object) -> Result<i64, HostError> {
-        self.visit_obj(x, |bi: &BigInt| {
+        self.visit_obj(x, |bi: &MeteredBigInt| {
             bi.to_i64()
                 .ok_or_else(|| self.err_conversion_into_rawval::<i64>(x.into()))
         })
     }
 
+    // Notes on metering: fully covered.
+    // Notes on calibration: use equal length objects to get the result upper bound.
     fn bigint_add(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| self.visit_obj(y, |b: &BigInt| Ok(a.add(b))))?;
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.add(b))
+        })?;
         Ok(self.add_host_object(res)?.into())
     }
 
+    // Notes on metering and calibration: see `bigint_add`
     fn bigint_sub(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| self.visit_obj(y, |b: &BigInt| Ok(a.sub(b))))?;
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.sub(b))
+        })?;
         Ok(self.add_host_object(res)?.into())
     }
 
+    // Notes on metering and model calibration:
+    // Use equal length objects for the upper bound measurement.
+    // Make sure to measure three length ranges (in terms of no. u64 digits):
+    // - [0, 32]
+    // - (32, 256]
+    // - [256, )
+    // As they use different algorithms that have different performance and involves different complexity of intermediate objects.
     fn bigint_mul(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| self.visit_obj(y, |b: &BigInt| Ok(a.mul(b))))?;
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.mul(b))
+        })?;
         Ok(self.add_host_object(res)?.into())
     }
 
+    // Notes on metering and model calibration:
+    // Use uneven length numbers for the upper bound measurement.
     fn bigint_div(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |b: &BigInt| {
-                a.checked_div(b)
-                    .ok_or_else(|| self.err_general("bigint division by zero"))
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| {
+                if b.is_zero() {
+                    return Err(self.err_general("bigint division by zero"));
+                }
+                a.div(b)
             })
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_rem(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |b: &BigInt| {
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| {
                 if b.is_zero() {
                     return Err(self.err_general("bigint division by zero"));
                 }
-                Ok(a.rem(b))
+                a.rem(b)
             })
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
+    // Notes on metering and model calibration:
+    // Use equal length numbers for the upper bound measurement.
     fn bigint_and(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |b: &BigInt| Ok(a.bitand(b)))
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.bitand(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_or(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |b: &BigInt| Ok(a.bitor(b)))
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.bitor(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_xor(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |b: &BigInt| Ok(a.bitxor(b)))
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.bitxor(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_shl(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |b: &BigInt| {
-                if b.is_negative() {
-                    return Err(self.err_general("attempt to shift left with negative"));
-                }
-                if let Some(u) = b.to_usize() {
-                    Ok(a.shl(u))
-                } else {
-                    Err(self.err_general("left-shift overflow"))
-                }
-            })
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.shl(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
+    // Notes on calibration: choose small y for the upper bound.
     fn bigint_shr(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |b: &BigInt| {
-                if b.is_negative() {
-                    return Err(self.err_general("attempt to shift right with negative"));
-                }
-                if let Some(u) = b.to_usize() {
-                    Ok(a.shr(u))
-                } else {
-                    Err(self.err_general("right-shift overflow"))
-                }
-            })
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.shr(b))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
+    // Notes on calibration: choose x == y for upper bound.
+    // TODO: this function will be removed
     fn bigint_cmp(&self, x: Object, y: Object) -> Result<RawVal, HostError> {
-        self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |b: &BigInt| Ok((a.cmp(b) as i32).into()))
+        self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| Ok((a.cmp(b) as i32).into()))
         })
     }
 
+    // Notes on metering: covered by `visit_obj`. `is_zero` call is free.
     fn bigint_is_zero(&self, x: Object) -> Result<RawVal, HostError> {
-        self.visit_obj(x, |a: &BigInt| Ok(a.is_zero().into()))
+        self.visit_obj(x, |a: &MeteredBigInt| Ok(a.is_zero().into()))
     }
 
+    // Notes on metering: covered by `visit_obj`. `neg` call is free.
     fn bigint_neg(&self, x: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| Ok(a.neg()))?;
+        let res = self.visit_obj(x, |a: &MeteredBigInt| Ok(a.neg()))?;
         Ok(self.add_host_object(res)?.into())
     }
 
+    // Notes on metering: covered by `visit_obj`. `not` call is free.
     fn bigint_not(&self, x: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| Ok(a.not()))?;
+        let res = self.visit_obj(x, |a: &MeteredBigInt| Ok(a.not()))?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_gcd(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| self.visit_obj(y, |b: &BigInt| Ok(a.gcd(b))))?;
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.gcd(b))
+        })?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_lcm(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| self.visit_obj(y, |b: &BigInt| Ok(a.lcm(b))))?;
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |b: &MeteredBigInt| a.lcm(b))
+        })?;
         Ok(self.add_host_object(res)?.into())
     }
 
+    // Note on calibration: pick y with all 1-bits to get the upper bound.
     fn bigint_pow(&self, x: Object, y: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            self.visit_obj(y, |e: &BigInt| {
-                if e.is_negative() {
-                    return Err(self.err_general("negative exponentiation not supported"));
-                }
-                if let Some(u) = e.to_usize() {
-                    Ok(Pow::pow(a, u))
-                } else {
-                    Err(self.err_general("pow overflow"))
-                }
-            })
+        let res = self.visit_obj(x, |a: &MeteredBigInt| {
+            self.visit_obj(y, |e: &MeteredBigInt| a.pow(e))
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_pow_mod(&self, p: Object, q: Object, m: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(p, |a: &BigInt| {
-            self.visit_obj(q, |exponent: &BigInt| {
-                if exponent.is_negative() {
-                    return Err(self.err_general("negative exponentiation not supported"));
-                }
-                self.visit_obj(m, |modulus: &BigInt| {
-                    if modulus.is_zero() {
-                        return Err(self.err_general("zero modulus not supported"));
-                    }
-                    Ok(a.modpow(exponent, modulus))
-                })
+        let res = self.visit_obj(p, |a: &MeteredBigInt| {
+            self.visit_obj(q, |exponent: &MeteredBigInt| {
+                self.visit_obj(m, |modulus: &MeteredBigInt| a.modpow(exponent, modulus))
             })
         })?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_sqrt(&self, x: Object) -> Result<Object, HostError> {
-        let res = self.visit_obj(x, |a: &BigInt| {
-            if a.is_negative() {
-                return Err(self.err_general("sqrt is imaginary"));
-            }
-            Ok(a.sqrt())
-        })?;
+        let res = self.visit_obj(x, |a: &MeteredBigInt| a.sqrt())?;
         Ok(self.add_host_object(res)?.into())
     }
 
     fn bigint_bits(&self, x: Object) -> Result<u64, HostError> {
-        self.visit_obj(x, |a: &BigInt| Ok(a.bits()))
+        self.visit_obj(x, |a: &MeteredBigInt| Ok(a.bits()))
     }
 
     fn bigint_to_bytes_be(&self, x: Object) -> Result<Object, Self::Error> {
-        let bytes: Vec<u8> = self.visit_obj(x, |a: &BigInt| Ok(a.to_bytes_be().1))?;
-        Ok(self.add_host_object(bytes)?.into())
+        let sign_bytes = self.visit_obj(x, |a: &MeteredBigInt| a.to_bytes_be())?;
+        Ok(self.add_host_object(sign_bytes.1)?.into())
     }
 
     fn bigint_to_radix_be(&self, x: Object, radix: RawVal) -> Result<Object, Self::Error> {
         let r: u32 = self.u32_from_rawval_input("radix", radix)?;
-        let bytes: Vec<u8> = self.visit_obj(x, |a: &BigInt| Ok(a.to_radix_be(r).1))?;
-        Ok(self.add_host_object(bytes)?.into())
+        let sign_bytes = self.visit_obj(x, |a: &MeteredBigInt| a.to_radix_be(r))?;
+        Ok(self.add_host_object(sign_bytes.1)?.into())
     }
 
+    // Notes on metering: covered by components
     fn serialize_to_binary(&self, v: RawVal) -> Result<Object, HostError> {
         let scv = self.from_host_val(v)?;
         let mut buf = Vec::<u8>::new();
         scv.write_xdr(&mut buf)
             .map_err(|_| self.err_general("failed to serialize ScVal"))?;
+        // Notes on metering": "write first charge later" means we could potentially underestimate
+        // the cost by the largest sized host object. Since we are bounding the memory limit of a
+        // host object, it is probably fine.
+        // Ideally, `charge` should go before `write_xdr`, which would require us to either 1.
+        // make serialization an iterative / chunked operation. Or 2. have a XDR method to
+        // calculate the serialized size. Both would require non-trivial XDR changes.
+        self.charge_budget(CostType::ValSer, buf.len() as u64)?;
         Ok(self.add_host_object(buf)?.into())
     }
 
+    // Notes on metering: covered by components
     fn deserialize_from_binary(&self, b: Object) -> Result<RawVal, HostError> {
         let scv = self.visit_obj(b, |hv: &Vec<u8>| {
+            self.charge_budget(CostType::ValDeser, hv.len() as u64)?;
             ScVal::read_xdr(&mut hv.as_slice())
                 .map_err(|_| self.err_general("failed to de-serialize ScVal"))
         })?;
@@ -1533,6 +1630,7 @@ impl CheckedEnv for Host {
             self.visit_obj(b, move |hv: &Vec<u8>| {
                 let range = self.valid_range_from_start_span_bound(b_pos, len, hv.len())?;
                 vm.with_memory_access(self, |mem| {
+                    self.charge_budget(CostType::VmMemCpy, hv.len() as u64)?;
                     self.map_err(mem.set(pos, &hv.as_slice()[range]))
                 })?;
                 Ok(().into())
@@ -1553,7 +1651,8 @@ impl CheckedEnv for Host {
         {
             let VmSlice { vm, pos, len } = self.decode_vmslice(lm_pos, len)?;
             let b_pos = u32::try_from(b_pos)?;
-            let mut vnew = self.visit_obj(b, |hv: &Vec<u8>| Ok(hv.clone()))?;
+            let mut vnew =
+                self.visit_obj(b, |hv: &Vec<u8>| Ok(hv.metered_clone(&self.0.budget)?))?;
             let end_idx = b_pos.checked_add(len).ok_or_else(|| {
                 self.err_status_msg(ScHostFnErrorCode::InputArgsInvalid, "u32 overflow")
             })? as usize;
@@ -1563,6 +1662,7 @@ impl CheckedEnv for Host {
                 vnew.resize(end_idx, 0);
             }
             vm.with_memory_access(self, |mem| {
+                self.charge_budget(CostType::VmMemCpy, len as u64)?;
                 Ok(self.map_err(
                     mem.get_into(pos, &mut vnew.as_mut_slice()[b_pos as usize..end_idx]),
                 )?)
@@ -1583,21 +1683,24 @@ impl CheckedEnv for Host {
             let VmSlice { vm, pos, len } = self.decode_vmslice(lm_pos, len)?;
             let mut vnew: Vec<u8> = vec![0; len as usize];
             vm.with_memory_access(self, |mem| {
+                self.charge_budget(CostType::VmMemCpy, len as u64)?;
                 self.map_err(mem.get_into(pos, vnew.as_mut_slice()))
             })?;
             Ok(self.add_host_object(vnew)?.into())
         }
     }
 
+    // Notes on metering: covered by `add_host_object`
     fn binary_new(&self) -> Result<Object, HostError> {
         Ok(self.add_host_object(Vec::<u8>::new())?.into())
     }
 
+    // Notes on metering: `get_mut` is free
     fn binary_put(&self, b: Object, i: RawVal, u: RawVal) -> Result<Object, HostError> {
         let i = self.usize_from_rawval_u32_input("i", i)?;
         let u = self.u8_from_rawval_input("u", u)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
-            let mut vnew = hv.clone();
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
             match vnew.get_mut(i) {
                 None => Err(self.err_status(ScHostObjErrorCode::VecIndexOutOfBound)),
                 Some(v) => {
@@ -1609,6 +1712,7 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
+    // Notes on metering: `get` is free
     fn binary_get(&self, b: Object, i: RawVal) -> Result<RawVal, HostError> {
         let i = self.usize_from_rawval_u32_input("i", i)?;
         self.visit_obj(b, |hv: &Vec<u8>| {
@@ -1622,31 +1726,39 @@ impl CheckedEnv for Host {
         let i = self.u32_from_rawval_input("i", i)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
             self.validate_index_lt_bound(i, hv.len())?;
-            let mut vnew = hv.clone();
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            self.charge_budget(CostType::BytesDel, hv.len() as u64)?; // O(n) worst case
             vnew.remove(i as usize);
             Ok(vnew)
         })?;
         Ok(self.add_host_object(vnew)?.into())
     }
 
+    // Notes on metering: `len` is free
     fn binary_len(&self, b: Object) -> Result<RawVal, HostError> {
         let len = self.visit_obj(b, |hv: &Vec<u8>| Ok(hv.len()))?;
         self.usize_to_rawval_u32(len)
     }
 
+    // Notes on metering: `push` is free
     fn binary_push(&self, b: Object, u: RawVal) -> Result<Object, HostError> {
         let u = self.u8_from_rawval_input("u", u)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
-            let mut vnew = hv.clone();
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            // Passing `len()` since worse case can cause reallocation.
+            self.charge_budget(CostType::BytesPush, hv.len() as u64)?;
             vnew.push(u);
             Ok(vnew)
         })?;
         Ok(self.add_host_object(vnew)?.into())
     }
 
+    // Notes on metering: `pop` is free
     fn binary_pop(&self, b: Object) -> Result<Object, HostError> {
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
-            let mut vnew = hv.clone();
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            // Passing `len()` since worse case can cause reallocation.
+            self.charge_budget(CostType::BytesPop, hv.len() as u64)?;
             vnew.pop()
                 .map(|_| vnew)
                 .ok_or_else(|| self.err_status(ScHostObjErrorCode::VecIndexOutOfBound))
@@ -1654,6 +1766,7 @@ impl CheckedEnv for Host {
         Ok(self.add_host_object(vnew)?.into())
     }
 
+    // Notes on metering: `first` is free
     fn binary_front(&self, b: Object) -> Result<RawVal, HostError> {
         self.visit_obj(b, |hv: &Vec<u8>| {
             hv.first()
@@ -1662,6 +1775,7 @@ impl CheckedEnv for Host {
         })
     }
 
+    // Notes on metering: `last` is free
     fn binary_back(&self, b: Object) -> Result<RawVal, HostError> {
         self.visit_obj(b, |hv: &Vec<u8>| {
             hv.last()
@@ -1675,7 +1789,8 @@ impl CheckedEnv for Host {
         let u = self.u8_from_rawval_input("u", u)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
             self.validate_index_le_bound(i, hv.len())?;
-            let mut vnew = hv.clone();
+            let mut vnew = hv.metered_clone(&self.0.budget)?;
+            self.charge_budget(CostType::BytesInsert, hv.len() as u64)?; // insert is O(n) worst case
             vnew.insert(i as usize, u);
             Ok(vnew)
         })?;
@@ -1683,11 +1798,12 @@ impl CheckedEnv for Host {
     }
 
     fn binary_append(&self, b1: Object, b2: Object) -> Result<Object, HostError> {
-        let mut vnew = self.visit_obj(b1, |hv: &Vec<u8>| Ok(hv.clone()))?;
-        let mut b2 = self.visit_obj(b2, |hv: &Vec<u8>| Ok(hv.clone()))?;
+        let mut vnew = self.visit_obj(b1, |hv: &Vec<u8>| Ok(hv.metered_clone(&self.0.budget)?))?;
+        let mut b2 = self.visit_obj(b2, |hv: &Vec<u8>| Ok(hv.metered_clone(&self.0.budget)?))?;
         if b2.len() > u32::MAX as usize - vnew.len() {
             return Err(self.err_status_msg(ScHostFnErrorCode::InputArgsInvalid, "u32 overflow"));
         }
+        self.charge_budget(CostType::BytesAppend, (vnew.len() + b2.len()) as u64)?; // worst case can cause rellocation
         vnew.append(&mut b2);
         Ok(self.add_host_object(vnew)?.into())
     }
@@ -1697,6 +1813,7 @@ impl CheckedEnv for Host {
         let end = self.u32_from_rawval_input("end", end)?;
         let vnew = self.visit_obj(b, move |hv: &Vec<u8>| {
             let range = self.valid_range_from_start_end_bound(start, end, hv.len())?;
+            self.charge_budget(CostType::BytesSlice, hv.len() as u64)?;
             Ok(hv.as_slice()[range].to_vec())
         })?;
         Ok(self.add_host_object(vnew)?.into())
@@ -1718,16 +1835,19 @@ impl CheckedEnv for Host {
         todo!()
     }
 
+    // Notes on metering: covered by components.
     fn compute_hash_sha256(&self, x: Object) -> Result<Object, HostError> {
         let hash = self.sha256_hash_from_binary_input(x)?;
         Ok(self.add_host_object(hash)?.into())
     }
 
+    // Notes on metering: covered by components.
     fn verify_sig_ed25519(&self, x: Object, k: Object, s: Object) -> Result<RawVal, HostError> {
         use ed25519_dalek::Verifier;
         let public_key = self.ed25519_pub_key_from_obj_input(k)?;
         let sig = self.signature_from_obj_input("sig", s)?;
         let res = self.visit_obj(x, |bin: &Vec<u8>| {
+            self.charge_budget(CostType::VerifyEd25519Sig, bin.len() as u64)?;
             public_key
                 .verify(bin, &sig)
                 .map_err(|_| self.err_general("Failed ED25519 verification"))
@@ -1735,31 +1855,39 @@ impl CheckedEnv for Host {
         Ok(res?.into())
     }
 
+    // Notes on metering: covered by components.
     fn account_get_low_threshold(&self, a: Object) -> Result<RawVal, Self::Error> {
         let threshold = self.load_account(a)?.thresholds.0[ThresholdIndexes::Low as usize];
         let threshold = Into::<u32>::into(threshold);
         Ok(threshold.into())
     }
 
+    // Notes on metering: covered by components.
     fn account_get_medium_threshold(&self, a: Object) -> Result<RawVal, Self::Error> {
         let threshold = self.load_account(a)?.thresholds.0[ThresholdIndexes::Med as usize];
         let threshold = Into::<u32>::into(threshold);
         Ok(threshold.into())
     }
 
+    // Notes on metering: covered by components.
     fn account_get_high_threshold(&self, a: Object) -> Result<RawVal, Self::Error> {
         let threshold = self.load_account(a)?.thresholds.0[ThresholdIndexes::High as usize];
         let threshold = Into::<u32>::into(threshold);
         Ok(threshold.into())
     }
 
+    // Notes on metering: some covered. The for loop and comparisons are free (for now).
     fn account_get_signer_weight(&self, a: Object, s: Object) -> Result<RawVal, Self::Error> {
         use xdr::{Signer, SignerKey};
 
         let target_signer = self.to_u256(s)?;
 
         let ae = self.load_account(a)?;
-        if ae.account_id == AccountId(PublicKey::PublicKeyTypeEd25519(target_signer.clone())) {
+        if ae.account_id
+            == AccountId(PublicKey::PublicKeyTypeEd25519(
+                target_signer.metered_clone(&self.0.budget)?,
+            ))
+        {
             // Target signer is the master key, so return the master weight
             let threshold = ae.thresholds.0[ThresholdIndexes::MasterWeight as usize];
             let threshold = Into::<u32>::into(threshold);
diff --git a/soroban-env-host/src/host/conversion.rs b/soroban-env-host/src/host/conversion.rs
index ecb602d7..2dbcaa3e 100644
--- a/soroban-env-host/src/host/conversion.rs
+++ b/soroban-env-host/src/host/conversion.rs
@@ -9,10 +9,13 @@ use crate::{
     Host, HostError, Object, RawVal, Tag,
 };
 use ed25519_dalek::{PublicKey, Signature, SIGNATURE_LENGTH};
-use num_bigint::{BigInt, Sign};
+use num_bigint::Sign;
 use sha2::{Digest, Sha256};
 
+use super::metered_bigint::MeteredBigInt;
+
 impl Host {
+    // Notes on metering: free
     pub(crate) fn usize_to_u32(&self, u: usize, msg: &'static str) -> Result<u32, HostError> {
         match u32::try_from(u) {
             Ok(v) => Ok(v),
@@ -20,6 +23,7 @@ impl Host {
         }
     }
 
+    // Notes on metering: free
     pub(crate) fn usize_to_rawval_u32(&self, u: usize) -> Result<RawVal, HostError> {
         match u32::try_from(u) {
             Ok(v) => Ok(v.into()),
@@ -27,6 +31,7 @@ impl Host {
         }
     }
 
+    // Notes on metering: free
     pub(crate) fn usize_from_rawval_u32_input(
         &self,
         name: &'static str,
@@ -35,6 +40,7 @@ impl Host {
         self.u32_from_rawval_input(name, r).map(|u| u as usize)
     }
 
+    // Notes on metering: free
     pub(crate) fn u32_from_rawval_input(
         &self,
         name: &'static str,
@@ -53,11 +59,13 @@ impl Host {
 
     pub(crate) fn to_u256(&self, a: Object) -> Result<Uint256, HostError> {
         self.visit_obj(a, |bin: &Vec<u8>| {
+            self.charge_budget(CostType::BytesClone, 32)?;
             bin.try_into()
                 .map_err(|_| self.err_general("bad u256 length"))
         })
     }
 
+    // Notes on metering: free
     pub(crate) fn u8_from_rawval_input(
         &self,
         name: &'static str,
@@ -109,7 +117,10 @@ impl Host {
     {
         self.visit_obj(obj, |bin: &Vec<u8>| {
             match <[u8; N]>::try_from(bin.as_slice()) {
-                Ok(arr) => Ok(arr.into()),
+                Ok(arr) => {
+                    self.charge_budget(CostType::BytesClone, N as u64)?;
+                    Ok(arr.into())
+                }
                 Err(cvt) => Err(self.err(
                     DebugError::new(ScHostObjErrorCode::ContractHashWrongLength) // TODO: this should be renamed to be more generic
                         .msg("{} {} has wrong length for input {}")
@@ -123,6 +134,7 @@ impl Host {
 
     pub fn ed25519_pub_key_from_obj_input(&self, k: Object) -> Result<PublicKey, HostError> {
         self.visit_obj(k, |bin: &Vec<u8>| {
+            self.charge_budget(CostType::ComputeEd25519PubKey, bin.len() as u64)?;
             PublicKey::from_bytes(bin).map_err(|_| {
                 self.err_status_msg(ScHostObjErrorCode::UnexpectedType, "invalid public key")
             })
@@ -131,6 +143,7 @@ impl Host {
 
     pub fn sha256_hash_from_binary_input(&self, x: Object) -> Result<Vec<u8>, HostError> {
         self.visit_obj(x, |bin: &Vec<u8>| {
+            self.charge_budget(CostType::ComputeSha256Hash, bin.len() as u64)?;
             let hash = Sha256::digest(bin).as_slice().to_vec();
             if hash.len() != 32 {
                 return Err(self.err_general("incorrect hash size"));
@@ -141,6 +154,7 @@ impl Host {
 
     /// Converts a [`RawVal`] to an [`ScVal`] and combines it with the currently-executing
     /// [`ContractID`] to produce a [`Key`], that can be used to access ledger [`Storage`].
+    // Notes on metering: covered by components.
     pub fn storage_key_from_rawval(&self, k: RawVal) -> Result<LedgerKey, HostError> {
         Ok(LedgerKey::ContractData(LedgerKeyContractData {
             contract_id: self.get_current_contract_id()?,
@@ -148,6 +162,7 @@ impl Host {
         }))
     }
 
+    // Notes on metering: covered by components.
     pub fn contract_data_key_from_rawval(&self, k: RawVal) -> Result<LedgerKey, HostError> {
         if self.from_host_val(k)? == ScVal::Static(ScStatic::LedgerKeyContractCode) {
             return Err(self.err_status_msg(
@@ -158,9 +173,8 @@ impl Host {
         self.storage_key_from_rawval(k)
     }
 
-    // TODO: impl a `TryFrom` trait once the "metered_" class is ready
-    pub(crate) fn scobj_from_bigint(&self, bi: &BigInt) -> Result<ScObject, HostError> {
-        let (sign, data) = bi.to_bytes_be();
+    pub(crate) fn scobj_from_bigint(&self, bi: &MeteredBigInt) -> Result<ScObject, HostError> {
+        let (sign, data) = bi.to_bytes_be()?;
         match sign {
             Sign::Minus => Ok(ScObject::BigInt(ScBigInt::Negative(
                 self.map_err(data.try_into())?,
diff --git a/soroban-env-host/src/host/data_helper.rs b/soroban-env-host/src/host/data_helper.rs
index 359906df..f1fd7790 100644
--- a/soroban-env-host/src/host/data_helper.rs
+++ b/soroban-env-host/src/host/data_helper.rs
@@ -1,3 +1,4 @@
+use crate::budget::CostType;
 use crate::xdr::{
     ContractDataEntry, HashIdPreimage, HashIdPreimageContractId, HashIdPreimageEd25519ContractId,
     LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey, LedgerKeyAccount,
@@ -8,6 +9,7 @@ use crate::{Host, HostError, Object};
 use soroban_env_common::xdr::{AccountEntry, AccountId, Hash, PublicKey, Uint256, WriteXdr};
 
 impl Host {
+    // Notes on metering: free
     pub fn contract_code_ledger_key(&self, contract_id: Hash) -> LedgerKey {
         LedgerKey::ContractData(LedgerKeyContractData {
             contract_id,
@@ -15,6 +17,7 @@ impl Host {
         })
     }
 
+    // Notes on metering: retrieving from storage covered. Rest are free.
     pub fn retrieve_contract_code_from_storage(
         &self,
         key: &LedgerKey,
@@ -34,6 +37,7 @@ impl Host {
         }
     }
 
+    // Notes on metering: `from_host_obj` and `put` to storage covered, rest are free.
     pub fn store_contract_code(
         &self,
         contract: ScContractCode,
@@ -54,23 +58,25 @@ impl Host {
         Ok(())
     }
 
+    // notes on metering: covers the key and salt. Rest are free.
     pub fn id_preimage_from_ed25519(
         &self,
         key: Uint256,
         salt: Uint256,
     ) -> Result<Vec<u8>, HostError> {
-        //Create contract and contractID
         let pre_image = HashIdPreimage::ContractIdFromEd25519(HashIdPreimageEd25519ContractId {
             ed25519: key,
             salt,
         });
         let mut buf = Vec::new();
+        self.charge_budget(CostType::BytesClone, 64)?; // key + salt
         pre_image
             .write_xdr(&mut buf)
             .map_err(|_| self.err_general("invalid hash"))?;
         Ok(buf)
     }
 
+    // notes on metering: covers the key and salt. Rest are free.
     pub fn id_preimage_from_contract(
         &self,
         contract_id: Hash,
@@ -79,12 +85,14 @@ impl Host {
         let pre_image =
             HashIdPreimage::ContractIdFromContract(HashIdPreimageContractId { contract_id, salt });
         let mut buf = Vec::new();
+        self.charge_budget(CostType::BytesClone, 64)?; // key + salt
         pre_image
             .write_xdr(&mut buf)
             .map_err(|_| self.err_general("invalid hash"))?;
         Ok(buf)
     }
 
+    // notes on metering: `get` from storage and `to_u256` covered. Rest are free.
     pub fn load_account(&self, a: Object) -> Result<AccountEntry, HostError> {
         let acc = LedgerKey::Account(LedgerKeyAccount {
             account_id: AccountId(PublicKey::PublicKeyTypeEd25519(self.to_u256(a)?)),
diff --git a/soroban-env-host/src/host/err_helper.rs b/soroban-env-host/src/host/err_helper.rs
index 5914179f..7d1e4523 100644
--- a/soroban-env-host/src/host/err_helper.rs
+++ b/soroban-env-host/src/host/err_helper.rs
@@ -16,7 +16,8 @@ impl Host {
             e
         } else {
             let mut he: HostError = ds.status.into();
-            he.events = Some(self.0.events.borrow().clone());
+            // If `get_events` causes an out-of-budget err, the events will not be recorded.
+            he.events = self.get_events().ok();
             he
         }
     }
diff --git a/soroban-env-host/src/host/metered_bigint.rs b/soroban-env-host/src/host/metered_bigint.rs
new file mode 100644
index 00000000..f5d1b56e
--- /dev/null
+++ b/soroban-env-host/src/host/metered_bigint.rs
@@ -0,0 +1,363 @@
+use super::MeteredClone;
+use crate::{
+    budget::{Budget, CostType},
+    xdr::ScUnknownErrorCode,
+    HostError,
+};
+use num_bigint::{BigInt, Sign};
+use num_integer::Integer;
+use num_traits::{Pow, Zero};
+use num_traits::{Signed, ToPrimitive};
+use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Rem, Shl, Shr, Sub};
+use std::{cmp, rc::Rc};
+
+pub(crate) struct MeteredBigInt {
+    budget: Budget,
+    num: BigInt,
+}
+
+// implement all the "charge" functions in here
+// TODO: can we do it with macro?
+impl MeteredBigInt {
+    // BigInt creation involves heap allocation
+    fn charge_new(&self) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntNew, 1)
+    }
+
+    fn charge_add_sub(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntAddSub, x)
+    }
+
+    fn charge_mul(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntMul, x)
+    }
+
+    fn charge_div_rem(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntDivRem, x)
+    }
+
+    fn charge_bitwise_op(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntBitwiseOp, x)
+    }
+
+    fn charge_shift(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntShift, x)
+    }
+
+    fn charge_cmp(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntCmp, x)
+    }
+
+    fn charge_gcd_lcm(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntGcdLcm, x)
+    }
+
+    fn charge_pow(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntPow, x)
+    }
+
+    fn charge_pow_mod(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntPowMod, x)
+    }
+
+    fn charge_sqrt(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntSqrt, x)
+    }
+
+    fn charge_from_bytes(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntFromBytes, x)
+    }
+
+    fn charge_to_bytes(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntToBytes, x)
+    }
+
+    fn charge_to_radix(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::BigIntToRadix, x)
+    }
+}
+
+impl MeteredBigInt {
+    pub(crate) fn new(budget: Budget) -> Result<Self, HostError> {
+        budget.charge(CostType::BigIntNew, 1)?;
+        Ok(Self {
+            budget,
+            num: BigInt::default(),
+        })
+    }
+
+    // Notes on mtering: free.
+    pub(crate) fn bits(&self) -> u64 {
+        self.num.bits()
+    }
+
+    pub(crate) fn from_u64(budget: Budget, x: u64) -> Result<Self, HostError> {
+        budget.charge(CostType::BigIntNew, 1)?;
+        Ok(Self {
+            budget,
+            num: Into::<BigInt>::into(x),
+        })
+    }
+
+    pub(crate) fn to_u64(&self) -> Option<u64> {
+        self.num.to_u64()
+    }
+
+    pub(crate) fn from_i64(budget: Budget, x: i64) -> Result<Self, HostError> {
+        budget.charge(CostType::BigIntNew, 1)?;
+        Ok(Self {
+            budget,
+            num: Into::<BigInt>::into(x),
+        })
+    }
+
+    pub(crate) fn to_i64(&self) -> Option<i64> {
+        self.num.to_i64()
+    }
+
+    pub(crate) fn add(&self, other: &Self) -> Result<Self, HostError> {
+        self.charge_add_sub(cmp::max(self.bits(), other.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).add(&other.num),
+        })
+    }
+
+    pub(crate) fn sub(&self, other: &Self) -> Result<Self, HostError> {
+        self.charge_add_sub(cmp::max(self.bits(), other.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).sub(&other.num),
+        })
+    }
+
+    pub(crate) fn mul(&self, other: &Self) -> Result<Self, HostError> {
+        self.charge_mul(cmp::max(self.bits(), other.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).mul(&other.num),
+        })
+    }
+
+    pub(crate) fn div(&self, other: &Self) -> Result<Self, HostError> {
+        assert!(!other.is_zero());
+        self.charge_div_rem(cmp::max(self.bits(), other.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).div(&other.num),
+        })
+    }
+
+    pub(crate) fn rem(&self, other: &Self) -> Result<Self, HostError> {
+        assert!(!other.is_zero());
+        self.charge_div_rem(cmp::max(self.bits(), other.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).rem(&other.num),
+        })
+    }
+
+    pub(crate) fn bitand(&self, other: &Self) -> Result<Self, HostError> {
+        self.charge_bitwise_op(cmp::max(self.bits(), other.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).bitand(&other.num),
+        })
+    }
+
+    pub(crate) fn bitor(&self, other: &Self) -> Result<Self, HostError> {
+        self.charge_bitwise_op(cmp::max(self.bits(), other.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).bitor(&other.num),
+        })
+    }
+
+    pub(crate) fn bitxor(&self, other: &Self) -> Result<Self, HostError> {
+        self.charge_bitwise_op(cmp::max(self.bits(), other.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).bitxor(&other.num),
+        })
+    }
+
+    pub(crate) fn shl(&self, rhs: &Self) -> Result<Self, HostError> {
+        self.charge_shift(self.bits())?;
+        if rhs.is_negative() {
+            // TODO: Replace with proper err code "attempt to shift left with negative"
+            return Err(ScUnknownErrorCode::General.into());
+        }
+        if let Some(u) = rhs.num.to_usize() {
+            Ok(Self {
+                budget: self.budget.clone(),
+                num: (&self.num).shl(u),
+            })
+        } else {
+            // TODO: Replace with proper err code "left-shift overflow"
+            return Err(ScUnknownErrorCode::General.into());
+        }
+    }
+
+    pub(crate) fn shr(&self, rhs: &Self) -> Result<Self, HostError> {
+        self.charge_shift(self.bits())?;
+        if rhs.is_negative() {
+            // TODO: Replace with proper err code "attempt to shift right with negative"
+            return Err(ScUnknownErrorCode::General.into());
+        }
+        if let Some(u) = rhs.num.to_usize() {
+            Ok(Self {
+                budget: self.budget.clone(),
+                num: (&self.num).shr(u),
+            })
+        } else {
+            // TODO: Replace with proper err code "left-shift overflow"
+            return Err(ScUnknownErrorCode::General.into());
+        }
+    }
+
+    pub(crate) fn is_zero(&self) -> bool {
+        self.num.is_zero()
+    }
+
+    pub(crate) fn is_negative(&self) -> bool {
+        self.num.is_negative()
+    }
+
+    pub(crate) fn neg(&self) -> Self {
+        Self {
+            budget: self.budget.clone(),
+            num: (&self.num).neg(),
+        }
+    }
+
+    pub(crate) fn not(&self) -> Self {
+        Self {
+            budget: self.budget.clone(),
+            num: (&self.num).not(),
+        }
+    }
+
+    pub(crate) fn gcd(&self, other: &Self) -> Result<Self, HostError> {
+        self.charge_gcd_lcm(cmp::max((&self.num).bits(), other.num.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).gcd(&other.num),
+        })
+    }
+
+    pub(crate) fn lcm(&self, other: &Self) -> Result<Self, HostError> {
+        self.charge_gcd_lcm(cmp::max((&self.num).bits(), other.num.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).lcm(&other.num),
+        })
+    }
+
+    pub(crate) fn pow(&self, rhs: &Self) -> Result<Self, HostError> {
+        if rhs.is_negative() {
+            // TODO: Replace with proper err code "negative exponentiation not supported"
+            return Err(ScUnknownErrorCode::General.into());
+        }
+        if let Some(u) = rhs.num.to_usize() {
+            // Notes on metering: the worst case complexity O(m*2^n)
+            self.charge_pow(self.bits().saturating_mul(2 << rhs.bits()))?;
+            Ok(Self {
+                budget: self.budget.clone(),
+                num: Pow::pow(&self.num, u),
+            })
+        } else {
+            // TODO: Replace with proper err code "pow overflow"
+            return Err(ScUnknownErrorCode::General.into());
+        }
+    }
+
+    pub(crate) fn modpow(&self, exponent: &Self, modulus: &Self) -> Result<Self, HostError> {
+        // TODO: Replace with proper err code "negative exponentiation not supported"
+        if exponent.is_negative() {
+            return Err(ScUnknownErrorCode::General.into());
+        }
+        // TODO: Replace with proper err code "zero modulus not supported"
+        if modulus.is_zero() {
+            return Err(ScUnknownErrorCode::General.into());
+        }
+        // TODO: I did not fully look into how the algorithm works, instead assumed the cost profile is simliar to pow
+        self.charge_pow_mod(self.bits().saturating_mul(2 << exponent.bits()))?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).modpow(&exponent.num, &modulus.num),
+        })
+    }
+
+    pub(crate) fn sqrt(&self) -> Result<Self, HostError> {
+        // TODO: Replace with proper err code "sqrt is imaginary"
+        if self.is_negative() {
+            return Err(ScUnknownErrorCode::General.into());
+        }
+        self.charge_sqrt(self.bits())?;
+        Ok(Self {
+            budget: self.budget.clone(),
+            num: (&self.num).sqrt(),
+        })
+    }
+
+    pub(crate) fn from_bytes_be(
+        sign: Sign,
+        bytes: &[u8],
+        budget: Budget,
+    ) -> Result<Self, HostError> {
+        budget.charge(CostType::BigIntFromBytes, bytes.len() as u64)?;
+        Ok(Self {
+            budget,
+            num: BigInt::from_bytes_be(sign, bytes),
+        })
+    }
+
+    pub(crate) fn to_bytes_be(&self) -> Result<(Sign, Vec<u8>), HostError> {
+        self.charge_to_bytes(self.bits())?;
+        Ok((&self.num).to_bytes_be())
+    }
+
+    pub(crate) fn to_radix_be(&self, r: u32) -> Result<(Sign, Vec<u8>), HostError> {
+        self.charge_to_radix(self.bits())?;
+        Ok((&self.num).to_radix_be(r))
+    }
+}
+
+impl Clone for MeteredBigInt {
+    fn clone(&self) -> Self {
+        Self {
+            budget: self.budget.clone(),
+            num: (&self.num).clone(),
+        }
+    }
+}
+
+impl MeteredClone for MeteredBigInt {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
+        assert!(Rc::ptr_eq(&self.budget.0, &budget.0));
+        self.charge_new()?;
+        Ok(self.clone())
+    }
+}
+
+impl PartialEq for MeteredBigInt {
+    fn eq(&self, other: &Self) -> bool {
+        Rc::ptr_eq(&self.budget.0, &other.budget.0) && self.num == other.num
+    }
+}
+
+impl Eq for MeteredBigInt {}
+
+impl PartialOrd for MeteredBigInt {
+    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+        assert!(Rc::ptr_eq(&self.budget.0, &other.budget.0));
+        self.num.partial_cmp(&other.num)
+    }
+}
+
+impl Ord for MeteredBigInt {
+    fn cmp(&self, other: &Self) -> cmp::Ordering {
+        assert!(Rc::ptr_eq(&self.budget.0, &other.budget.0));
+        self.num.cmp(&other.num)
+    }
+}
diff --git a/soroban-env-host/src/host/metered_clone.rs b/soroban-env-host/src/host/metered_clone.rs
new file mode 100644
index 00000000..3d1737ab
--- /dev/null
+++ b/soroban-env-host/src/host/metered_clone.rs
@@ -0,0 +1,45 @@
+use crate::{
+    budget::{Budget, CostType},
+    host::Events,
+    host::MeteredOrdMap,
+    xdr::{Hash, Uint256},
+    HostError,
+};
+use std::rc::Rc;
+
+pub trait MeteredClone: Clone {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError>;
+}
+impl MeteredClone for Hash {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
+        budget.charge(CostType::BytesClone, 32)?;
+        Ok(self.clone())
+    }
+}
+impl MeteredClone for Vec<u8> {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
+        budget.charge(CostType::BytesClone, self.len() as u64)?;
+        Ok(self.clone())
+    }
+}
+impl MeteredClone for Uint256 {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
+        budget.charge(CostType::BytesClone, 32 as u64)?;
+        Ok(self.clone())
+    }
+}
+
+impl MeteredClone for Events {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
+        budget.charge(CostType::CloneEvents, self.0.len() as u64)?;
+        Ok(self.clone())
+    }
+}
+
+impl<K, V> MeteredClone for MeteredOrdMap<K, V> {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
+        assert!(Rc::ptr_eq(&self.budget.0, &budget.0));
+        self.charge_new()?;
+        Ok(self.clone())
+    }
+}
diff --git a/soroban-env-host/src/host/metered_map.rs b/soroban-env-host/src/host/metered_map.rs
new file mode 100644
index 00000000..a0e4e2ca
--- /dev/null
+++ b/soroban-env-host/src/host/metered_map.rs
@@ -0,0 +1,222 @@
+use crate::{
+    budget::{Budget, CostType},
+    HostError,
+};
+use im_rc::ordmap::{ConsumingIter, Iter, Keys, Values};
+use im_rc::OrdMap;
+use std::{borrow::Borrow, rc::Rc};
+
+pub struct MeteredOrdMap<K, V> {
+    pub(crate) budget: Budget,
+    pub(crate) map: OrdMap<K, V>,
+}
+
+impl<K, V> MeteredOrdMap<K, V> {
+    pub(crate) fn charge_new(&self) -> Result<(), HostError> {
+        self.budget.charge(CostType::ImMapNew, 1)
+    }
+
+    pub(crate) fn charge_mut_access(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::ImMapMutEntry, x)
+    }
+
+    pub(crate) fn charge_immut_access(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::ImMapImmutEntry, x)
+    }
+}
+
+impl<K, V> Default for MeteredOrdMap<K, V> {
+    fn default() -> Self {
+        Self {
+            budget: Default::default(),
+            map: Default::default(),
+        }
+    }
+}
+
+impl<K, V> MeteredOrdMap<K, V>
+where
+    K: Ord + Clone,
+    V: Clone,
+{
+    pub fn new(budget: Budget) -> Result<Self, HostError> {
+        budget.charge(CostType::ImMapNew, 1)?;
+        Ok(MeteredOrdMap {
+            budget,
+            map: OrdMap::new(),
+        })
+    }
+
+    pub fn from_map(budget: Budget, map: OrdMap<K, V>) -> Result<Self, HostError> {
+        budget.charge(CostType::ImMapNew, 1)?;
+        Ok(MeteredOrdMap { budget, map })
+    }
+
+    // Time: O(log n)
+    pub fn insert(&mut self, key: K, value: V) -> Result<Option<V>, HostError> {
+        self.charge_mut_access(self.map.len() as u64)?;
+        Ok(self.map.insert(key, value))
+    }
+
+    // Time: O(log n)
+    pub fn get<BK>(&self, key: &BK) -> Result<Option<&V>, HostError>
+    where
+        BK: Ord + ?Sized,
+        K: Borrow<BK>,
+    {
+        self.charge_immut_access(self.map.len() as u64)?;
+        Ok(self.map.get(key))
+    }
+
+    // Time: O(log n)
+    pub fn remove<BK>(&mut self, k: &BK) -> Result<Option<V>, HostError>
+    where
+        BK: Ord + ?Sized,
+        K: Borrow<BK>,
+    {
+        self.charge_mut_access(self.map.len() as u64)?;
+        Ok(self.map.remove(k))
+    }
+
+    // Time: O(1). Free of charge.
+    #[inline]
+    pub fn len(&self) -> usize {
+        self.map.len()
+    }
+
+    // Time: O(log n)
+    pub fn contains_key<BK>(&self, k: &BK) -> Result<bool, HostError>
+    where
+        BK: Ord + ?Sized,
+        K: Borrow<BK>,
+    {
+        self.charge_immut_access(self.map.len() as u64)?;
+        Ok(self.map.contains_key(k))
+    }
+
+    /// Time: O(log n)
+    pub fn extract<BK>(&self, k: &BK) -> Result<Option<(V, Self)>, HostError>
+    where
+        BK: Ord + ?Sized,
+        K: Borrow<BK>,
+    {
+        self.charge_immut_access(self.map.len() as u64)?;
+        if let Some((v, m)) = self.map.extract(k) {
+            Ok(Some((v, Self::from_map(self.budget.clone(), m)?)))
+        } else {
+            Ok(None)
+        }
+    }
+
+    pub fn get_prev<BK>(&self, key: &BK) -> Result<Option<(&K, &V)>, HostError>
+    where
+        BK: Ord + ?Sized,
+        K: Borrow<BK>,
+    {
+        self.charge_immut_access(self.map.len() as u64)?;
+        Ok(self.map.get_prev(key))
+    }
+
+    pub fn get_next<BK>(&self, key: &BK) -> Result<Option<(&K, &V)>, HostError>
+    where
+        BK: Ord + ?Sized,
+        K: Borrow<BK>,
+    {
+        self.charge_immut_access(self.map.len() as u64)?;
+        Ok(self.map.get_next(key))
+    }
+
+    /// Time: O(log n)
+    pub fn get_min(&self) -> Result<Option<&(K, V)>, HostError> {
+        self.charge_immut_access(self.map.len() as u64)?;
+        Ok(self.map.get_min())
+    }
+
+    /// Time: O(log n)
+    pub fn get_max(&self) -> Result<Option<&(K, V)>, HostError> {
+        self.charge_immut_access(self.map.len() as u64)?;
+        Ok(self.map.get_max())
+    }
+
+    pub fn keys(&self) -> Result<Keys<'_, K, V>, HostError> {
+        self.charge_immut_access(self.map.len() as u64)?;
+        Ok(self.map.keys())
+    }
+
+    pub fn values(&self) -> Result<Values<'_, K, V>, HostError> {
+        self.charge_immut_access(self.map.len() as u64)?;
+        Ok(self.map.values())
+    }
+
+    #[inline]
+    pub fn iter(&self) -> Iter<'_, K, V> {
+        self.map.iter()
+    }
+}
+
+impl<K, V> Clone for MeteredOrdMap<K, V> {
+    fn clone(&self) -> Self {
+        Self {
+            budget: self.budget.clone(),
+            map: self.map.clone(),
+        }
+    }
+}
+
+impl<K, V> PartialEq for MeteredOrdMap<K, V>
+where
+    K: Ord + PartialEq,
+    V: PartialEq,
+{
+    fn eq(&self, other: &Self) -> bool {
+        Rc::ptr_eq(&self.budget.0, &other.budget.0) && self.map == other.map
+    }
+}
+impl<K: Ord + Eq, V: Eq> Eq for MeteredOrdMap<K, V> {}
+
+impl<K, V> PartialOrd for MeteredOrdMap<K, V>
+where
+    K: Ord,
+    V: PartialOrd,
+{
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        assert!(Rc::ptr_eq(&self.budget.0, &other.budget.0));
+        self.map.partial_cmp(&other.map)
+    }
+}
+
+impl<K, V> Ord for MeteredOrdMap<K, V>
+where
+    K: Ord,
+    V: Ord,
+{
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        assert!(Rc::ptr_eq(&self.budget.0, &other.budget.0));
+        self.map.cmp(&other.map)
+    }
+}
+
+impl<'a, K, V> IntoIterator for &'a MeteredOrdMap<K, V>
+where
+    K: Ord,
+{
+    type Item = (&'a K, &'a V);
+    type IntoIter = Iter<'a, K, V>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.map.into_iter()
+    }
+}
+
+impl<K, V> IntoIterator for MeteredOrdMap<K, V>
+where
+    K: Ord + Clone,
+    V: Clone,
+{
+    type Item = (K, V);
+    type IntoIter = ConsumingIter<(K, V)>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.map.into_iter()
+    }
+}
diff --git a/soroban-env-host/src/host/metered_vector.rs b/soroban-env-host/src/host/metered_vector.rs
new file mode 100644
index 00000000..d95beb3b
--- /dev/null
+++ b/soroban-env-host/src/host/metered_vector.rs
@@ -0,0 +1,175 @@
+use super::MeteredClone;
+use crate::xdr::ScHostObjErrorCode;
+use crate::{
+    budget::{Budget, CostType},
+    HostError,
+};
+use im_rc::{vector::Iter, vector::IterMut, Vector};
+use std::ops::RangeBounds;
+use std::rc::Rc;
+
+pub(crate) struct MeteredVector<A> {
+    budget: Budget,
+    vec: Vector<A>,
+}
+
+impl<A> MeteredVector<A> {
+    fn charge_new(&self) -> Result<(), HostError> {
+        self.budget.charge(CostType::ImVecNew, 1)
+    }
+
+    fn charge_mut_access(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::ImVecMutEntry, x)
+    }
+
+    fn charge_immut_access(&self, x: u64) -> Result<(), HostError> {
+        self.budget.charge(CostType::ImVecImmutEntry, x)
+    }
+}
+
+// TODO: this whole section can probably be done with macro
+impl<A: Clone> MeteredVector<A> {
+    pub fn new(budget: Budget) -> Result<Self, HostError> {
+        budget.charge(CostType::ImVecNew, 1)?;
+        Ok(MeteredVector {
+            budget,
+            vec: Vector::new(),
+        })
+    }
+
+    pub fn from_vec(budget: Budget, vec: Vector<A>) -> Result<Self, HostError> {
+        budget.charge(CostType::ImVecNew, 1)?;
+        Ok(MeteredVector { budget, vec })
+    }
+
+    // Time: O(log n)
+    pub fn set(&mut self, index: usize, value: A) -> Result<A, HostError> {
+        self.charge_mut_access(self.len() as u64)?;
+        Ok(self.vec.set(index, value))
+    }
+
+    // Time: O(log n)
+    pub fn get(&self, index: usize) -> Result<&A, HostError> {
+        self.charge_immut_access(self.len() as u64)?;
+        self.vec
+            .get(index)
+            .ok_or_else(|| ScHostObjErrorCode::VecIndexOutOfBound.into())
+    }
+
+    // Time: O(log n)
+    pub fn remove(&mut self, index: usize) -> Result<A, HostError> {
+        self.charge_mut_access(self.len() as u64)?;
+        Ok(self.vec.remove(index))
+    }
+
+    // Time: O(1). Free of charge.
+    #[inline]
+    pub fn len(&self) -> usize {
+        self.vec.len()
+    }
+
+    // Time: O(log n) worst case, O(1) ammortized
+    pub fn push_back(&mut self, value: A) -> Result<(), HostError> {
+        self.charge_immut_access(self.len() as u64)?;
+        Ok(self.vec.push_back(value))
+    }
+
+    // Time: O(1)
+    pub fn pop_back(&mut self) -> Result<A, HostError> {
+        self.charge_immut_access(self.len() as u64)?;
+        self.vec
+            .pop_back()
+            .ok_or_else(|| ScHostObjErrorCode::VecIndexOutOfBound.into())
+    }
+
+    // Time: O(log n)
+    pub fn front(&self) -> Result<&A, HostError> {
+        self.charge_immut_access(self.len() as u64)?;
+        self.vec
+            .front()
+            .ok_or_else(|| ScHostObjErrorCode::VecIndexOutOfBound.into())
+    }
+
+    // Time: O(log n)
+    pub fn back(&self) -> Result<&A, HostError> {
+        self.charge_immut_access(self.len() as u64)?;
+        self.vec
+            .back()
+            .ok_or_else(|| ScHostObjErrorCode::VecIndexOutOfBound.into())
+    }
+
+    // Time: O(log n)
+    pub fn insert(&mut self, index: usize, value: A) -> Result<(), HostError> {
+        self.charge_mut_access(self.len() as u64)?;
+        Ok(self.vec.insert(index, value))
+    }
+
+    // Time: O(log n)
+    pub fn append(&mut self, other: Self) -> Result<(), HostError> {
+        self.charge_mut_access((self.len() + other.len()) as u64)?;
+        Ok(self.vec.append(other.vec))
+    }
+
+    // Time: O(log n)
+    pub fn slice<R>(&mut self, range: R) -> Result<Self, HostError>
+    where
+        R: RangeBounds<usize>,
+    {
+        self.charge_mut_access(self.len() as u64)?;
+        Ok(MeteredVector {
+            budget: self.budget.clone(),
+            vec: self.vec.slice(range),
+        })
+    }
+
+    /// Time: O(1)
+    #[inline]
+    pub fn iter(&self) -> Iter<'_, A> {
+        self.vec.iter()
+    }
+
+    /// Time: O(1)
+    #[inline]
+    pub fn iter_mut(&mut self) -> IterMut<'_, A> {
+        self.vec.iter_mut()
+    }
+}
+
+impl<A: Clone> Clone for MeteredVector<A> {
+    fn clone(&self) -> Self {
+        Self {
+            budget: self.budget.clone(),
+            vec: self.vec.clone(),
+        }
+    }
+}
+
+impl<A: Clone> MeteredClone for MeteredVector<A> {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
+        assert!(Rc::ptr_eq(&self.budget.0, &budget.0));
+        self.charge_new()?;
+        Ok(self.clone())
+    }
+}
+
+impl<A: Clone + PartialEq> PartialEq for MeteredVector<A> {
+    fn eq(&self, other: &Self) -> bool {
+        Rc::ptr_eq(&self.budget.0, &other.budget.0) && self.vec == other.vec
+    }
+}
+
+impl<A: Clone + Eq> Eq for MeteredVector<A> {}
+
+impl<A: Clone + PartialOrd> PartialOrd for MeteredVector<A> {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        assert!(Rc::ptr_eq(&self.budget.0, &other.budget.0));
+        self.vec.partial_cmp(&other.vec)
+    }
+}
+
+impl<A: Clone + Ord> Ord for MeteredVector<A> {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        assert!(Rc::ptr_eq(&self.budget.0, &other.budget.0));
+        self.vec.cmp(&other.vec)
+    }
+}
diff --git a/soroban-env-host/src/host/validity.rs b/soroban-env-host/src/host/validity.rs
index 307cc2d7..9b44a92e 100644
--- a/soroban-env-host/src/host/validity.rs
+++ b/soroban-env-host/src/host/validity.rs
@@ -5,6 +5,7 @@ use crate::xdr::{ScHostFnErrorCode, ScHostObjErrorCode};
 use crate::{Host, HostError, RawVal};
 
 impl Host {
+    // Notes on metering: free
     pub(crate) fn validate_index_lt_bound(
         &self,
         index: u32,
@@ -19,6 +20,7 @@ impl Host {
         Ok(())
     }
 
+    // Notes on metering: free
     pub(crate) fn validate_index_le_bound(
         &self,
         index: u32,
@@ -33,6 +35,7 @@ impl Host {
         Ok(())
     }
 
+    // Notes on metering: free
     pub(crate) fn valid_range_from_start_end_bound(
         &self,
         start: u32,
@@ -65,6 +68,7 @@ impl Host {
         })
     }
 
+    // Notes on metering: free
     pub(crate) fn valid_range_from_start_span_bound(
         &self,
         start: u32,
diff --git a/soroban-env-host/src/host_object.rs b/soroban-env-host/src/host_object.rs
index 9c4492fb..02391fbd 100644
--- a/soroban-env-host/src/host_object.rs
+++ b/soroban-env-host/src/host_object.rs
@@ -1,16 +1,16 @@
 use super::{
+    host::metered_bigint::MeteredBigInt,
+    host::metered_map::MeteredOrdMap,
+    host::metered_vector::MeteredVector,
     weak_host::WeakHost,
     xdr::{self, ScObjectType},
     EnvVal, Object, RawVal,
 };
 
-use im_rc::{OrdMap, Vector};
-use num_bigint::BigInt;
-
 pub(crate) type HostObj = EnvVal<WeakHost, Object>;
 pub(crate) type HostVal = EnvVal<WeakHost, RawVal>;
-pub(crate) type HostMap = OrdMap<HostVal, HostVal>;
-pub(crate) type HostVec = Vector<HostVal>;
+pub(crate) type HostMap = MeteredOrdMap<HostVal, HostVal>;
+pub(crate) type HostVec = MeteredVector<HostVal>;
 
 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
 pub(crate) enum HostObject {
@@ -19,7 +19,7 @@ pub(crate) enum HostObject {
     U64(u64),
     I64(i64),
     Bin(Vec<u8>),
-    BigInt(BigInt),
+    BigInt(MeteredBigInt),
     Hash(xdr::ScHash),
     PublicKey(xdr::PublicKey),
     ContractCode(xdr::ScContractCode),
@@ -58,7 +58,7 @@ declare_host_object_type!(HostVec, Vec, Vec);
 declare_host_object_type!(u64, U64, U64);
 declare_host_object_type!(i64, I64, I64);
 declare_host_object_type!(Vec<u8>, Bytes, Bin);
-declare_host_object_type!(BigInt, BigInt, BigInt);
+declare_host_object_type!(MeteredBigInt, BigInt, BigInt);
 declare_host_object_type!(xdr::ScHash, Hash, Hash);
 declare_host_object_type!(xdr::PublicKey, PublicKey, PublicKey);
 declare_host_object_type!(xdr::ScContractCode, ContractCode, ContractCode);
diff --git a/soroban-env-host/src/storage.rs b/soroban-env-host/src/storage.rs
index 3254af77..1f4aa156 100644
--- a/soroban-env-host/src/storage.rs
+++ b/soroban-env-host/src/storage.rs
@@ -10,8 +10,7 @@
 use std::rc::Rc;
 
 use crate::xdr::{LedgerEntry, LedgerKey, ScHostStorageErrorCode};
-use crate::HostError;
-use im_rc::OrdMap;
+use crate::{host::metered_map::MeteredOrdMap, HostError};
 
 /// A helper type used by [Footprint] to designate which ways
 /// a given [LedgerKey] is accessed, or is allowed to be accessed,
@@ -41,29 +40,32 @@ pub trait SnapshotSource {
 /// transaction has an unknown [Footprint] it can be calculated by
 /// running a "preflight" execution in [FootprintMode::Recording],
 /// against a suitably fresh [SnapshotSource].
+// Notes on metering: covered by the underneath `MeteredOrdMap`.
 #[derive(Clone, Default)]
-pub struct Footprint(pub OrdMap<LedgerKey, AccessType>);
+pub struct Footprint(pub MeteredOrdMap<LedgerKey, AccessType>);
 
 impl Footprint {
-    pub fn record_access(&mut self, key: &LedgerKey, ty: AccessType) {
-        if let Some(existing) = self.0.get(key) {
+    pub fn record_access(&mut self, key: &LedgerKey, ty: AccessType) -> Result<(), HostError> {
+        if let Some(existing) = self.0.get(key)? {
             match (existing, ty.clone()) {
-                (AccessType::ReadOnly, AccessType::ReadOnly) => (),
+                (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
                 (AccessType::ReadOnly, AccessType::ReadWrite) => {
                     // The only interesting case is an upgrade
                     // from previously-read-only to read-write.
-                    self.0.insert(key.clone(), ty);
+                    self.0.insert(key.clone(), ty)?;
+                    Ok(())
                 }
-                (AccessType::ReadWrite, AccessType::ReadOnly) => (),
-                (AccessType::ReadWrite, AccessType::ReadWrite) => (),
+                (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
+                (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
             }
         } else {
-            self.0.insert(key.clone(), ty);
+            self.0.insert(key.clone(), ty)?;
+            Ok(())
         }
     }
 
     pub fn enforce_access(&mut self, key: &LedgerKey, ty: AccessType) -> Result<(), HostError> {
-        if let Some(existing) = self.0.get(key) {
+        if let Some(existing) = self.0.get(key)? {
             match (existing, ty) {
                 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
                 (AccessType::ReadOnly, AccessType::ReadWrite) => {
@@ -107,16 +109,18 @@ impl Default for FootprintMode {
 pub struct Storage {
     pub footprint: Footprint,
     pub mode: FootprintMode,
-    pub map: OrdMap<LedgerKey, Option<LedgerEntry>>,
+    pub map: MeteredOrdMap<LedgerKey, Option<LedgerEntry>>,
 }
 
+// Notes on metering: all storage operations: `put`, `get`, `del`, `has` are
+// covered by the underneath `MeteredOrdMap` and the `Footprint`'s own map.
 impl Storage {
     /// Constructs a new [Storage] in [FootprintMode::Enforcing] using a
     /// given [Footprint] and a storage map populated with all the keys
     /// listed in the [Footprint].
     pub fn with_enforcing_footprint_and_map(
         footprint: Footprint,
-        map: OrdMap<LedgerKey, Option<LedgerEntry>>,
+        map: MeteredOrdMap<LedgerKey, Option<LedgerEntry>>,
     ) -> Self {
         Self {
             mode: FootprintMode::Enforcing,
@@ -150,18 +154,18 @@ impl Storage {
         let ty = AccessType::ReadOnly;
         match self.mode {
             FootprintMode::Recording(ref src) => {
-                self.footprint.record_access(key, ty);
+                self.footprint.record_access(key, ty)?;
                 // In recording mode we treat the map as a cache
                 // that misses read-through to the underlying src.
-                if !self.map.contains_key(key) {
-                    self.map.insert(key.clone(), Some(src.get(key)?));
+                if !self.map.contains_key(key)? {
+                    self.map.insert(key.clone(), Some(src.get(key)?))?;
                 }
             }
             FootprintMode::Enforcing => {
                 self.footprint.enforce_access(key, ty)?;
             }
         };
-        match self.map.get(key) {
+        match self.map.get(key)? {
             None => Err(ScHostStorageErrorCode::MissingKeyInGet.into()),
             Some(None) => Err(ScHostStorageErrorCode::GetOnDeletedKey.into()),
             Some(Some(val)) => Ok(val.clone()),
@@ -172,13 +176,13 @@ impl Storage {
         let ty = AccessType::ReadWrite;
         match self.mode {
             FootprintMode::Recording(_) => {
-                self.footprint.record_access(key, ty);
+                self.footprint.record_access(key, ty)?;
             }
             FootprintMode::Enforcing => {
                 self.footprint.enforce_access(key, ty)?;
             }
         };
-        self.map.insert(key.clone(), val);
+        self.map.insert(key.clone(), val)?;
         Ok(())
     }
 
@@ -221,10 +225,10 @@ impl Storage {
         let ty = AccessType::ReadOnly;
         match self.mode {
             FootprintMode::Recording(ref src) => {
-                self.footprint.record_access(key, ty);
+                self.footprint.record_access(key, ty)?;
                 // We don't cache has() calls but we do
                 // consult the cache before answering them.
-                match self.map.get(key) {
+                match self.map.get(key)? {
                     Some(None) => Ok(false),
                     Some(Some(_)) => Ok(true),
                     None => src.has(key),
@@ -232,7 +236,7 @@ impl Storage {
             }
             FootprintMode::Enforcing => {
                 self.footprint.enforce_access(key, ty)?;
-                match self.map.get(key) {
+                match self.map.get(key)? {
                     Some(None) => Ok(false),
                     Some(Some(_)) => Ok(true),
                     None => Ok(false),
@@ -246,10 +250,12 @@ impl Storage {
 mod test_footprint {
 
     use super::*;
+    use crate::budget::Budget;
     use crate::xdr::{LedgerKeyContractData, ScVal};
+    use im_rc::OrdMap;
 
     #[test]
-    fn footprint_record_access() {
+    fn footprint_record_access() -> Result<(), HostError> {
         let mut fp = Footprint::default();
         // record when key not exist
         let contract_id = [0; 32].into();
@@ -257,34 +263,37 @@ mod test_footprint {
             contract_id,
             key: ScVal::I32(0),
         });
-        fp.record_access(&key, AccessType::ReadOnly);
-        assert_eq!(fp.0.contains_key(&key), true);
-        assert_eq!(fp.0.get(&key), Some(&AccessType::ReadOnly));
+        fp.record_access(&key, AccessType::ReadOnly)?;
+        assert_eq!(fp.0.contains_key(&key)?, true);
+        assert_eq!(fp.0.get(&key)?, Some(&AccessType::ReadOnly));
         // record and change access
-        fp.record_access(&key, AccessType::ReadWrite);
-        assert_eq!(fp.0.get(&key), Some(&AccessType::ReadWrite));
-        fp.record_access(&key, AccessType::ReadOnly);
-        assert_eq!(fp.0.get(&key), Some(&AccessType::ReadWrite));
+        fp.record_access(&key, AccessType::ReadWrite)?;
+        assert_eq!(fp.0.get(&key)?, Some(&AccessType::ReadWrite));
+        fp.record_access(&key, AccessType::ReadOnly)?;
+        assert_eq!(fp.0.get(&key)?, Some(&AccessType::ReadWrite));
+        Ok(())
     }
 
     #[test]
     fn footprint_enforce_access() -> Result<(), HostError> {
+        let budget = Budget::default();
         let contract_id = [0; 32].into();
         let key = LedgerKey::ContractData(LedgerKeyContractData {
             contract_id,
             key: ScVal::I32(0),
         });
         let om = OrdMap::unit(key.clone(), AccessType::ReadOnly);
-        let mut fp = Footprint(om);
+        let mom = MeteredOrdMap::from_map(budget, om)?;
+        let mut fp = Footprint(mom);
         fp.enforce_access(&key, AccessType::ReadOnly)?;
-        fp.0.insert(key.clone(), AccessType::ReadWrite);
+        fp.0.insert(key.clone(), AccessType::ReadWrite)?;
         fp.enforce_access(&key, AccessType::ReadOnly)?;
         fp.enforce_access(&key, AccessType::ReadWrite)?;
         Ok(())
     }
 
     #[test]
-    fn footprint_enforce_access_not_exist() {
+    fn footprint_enforce_access_not_exist() -> Result<(), HostError> {
         let mut fp = Footprint::default();
         let contract_id = [0; 32].into();
         let key = LedgerKey::ContractData(LedgerKeyContractData {
@@ -296,27 +305,32 @@ mod test_footprint {
             res,
             ScHostStorageErrorCode::AccessToUnknownEntry
         ));
+        Ok(())
     }
 
     #[test]
-    fn footprint_attempt_to_write_readonly_entry() {
+    fn footprint_attempt_to_write_readonly_entry() -> Result<(), HostError> {
+        let budget = Budget::default();
         let contract_id = [0; 32].into();
         let key = LedgerKey::ContractData(LedgerKeyContractData {
             contract_id,
             key: ScVal::I32(0),
         });
         let om = OrdMap::unit(key.clone(), AccessType::ReadOnly);
-        let mut fp = Footprint(om);
+        let mom = MeteredOrdMap::from_map(budget, om)?;
+        let mut fp = Footprint(mom);
         let res = fp.enforce_access(&key, AccessType::ReadWrite);
         assert!(HostError::result_matches_err_status(
             res,
             ScHostStorageErrorCode::ReadwriteAccessToReadonlyEntry
         ));
+        Ok(())
     }
 }
 
 #[cfg(test)]
 mod test_storage {
+    use im_rc::OrdMap;
     use soroban_env_common::xdr::ScUnknownErrorCode;
 
     use super::*;
diff --git a/soroban-env-host/src/vm.rs b/soroban-env-host/src/vm.rs
index eaa8857f..8392782d 100644
--- a/soroban-env-host/src/vm.rs
+++ b/soroban-env-host/src/vm.rs
@@ -226,6 +226,7 @@ impl Vm {
         contract_id: Hash,
         module_wasm_code: &[u8],
     ) -> Result<Rc<Self>, HostError> {
+        host.charge_budget(CostType::VmInstantiation, module_wasm_code.len() as u64)?;
         let elements_module: elements::Module =
             host.map_err(elements::deserialize_buffer(module_wasm_code))?;
 
@@ -272,6 +273,7 @@ impl Vm {
         func: &str,
         args: &[RawVal],
     ) -> Result<RawVal, HostError> {
+        host.charge_budget(CostType::VmInvokeFunction, args.len() as u64)?;
         host.with_frame(Frame::ContractVM(self.clone()), || {
             let wasm_args: Vec<_> = args
                 .iter()
diff --git a/soroban-env-host/src/vm/dispatch.rs b/soroban-env-host/src/vm/dispatch.rs
index efca4960..5498b584 100644
--- a/soroban-env-host/src/vm/dispatch.rs
+++ b/soroban-env-host/src/vm/dispatch.rs
@@ -120,6 +120,11 @@ macro_rules! generate_dispatch_functions {
                 pub(crate) fn $fn_id(host: &mut Host, _vmargs: RuntimeArgs) ->
                     Result<RuntimeValue, wasmi::Trap>
                 {
+                    // Notes on metering: a flat charge per host function invocation.
+                    // This does not account for the actual work being done in those functions,
+                    // which are accounted for individually at the operation level.
+                    // This is analogous to a flat toll charge for getting on the highway,
+                    // whereas the actual work are the tickets to the attractions.
                     host.charge_budget(CostType::HostFunction, _vmargs.len() as u64)?;
                     Ok(dispatch_function_helper!{host, _vmargs, fn $fn_id $args }?.into())
                 }


Test patch:
diff --git a/soroban-env-host/src/test/binary.rs b/soroban-env-host/src/test/binary.rs
index 6e46b29d..b46f9824 100644
--- a/soroban-env-host/src/test/binary.rs
+++ b/soroban-env-host/src/test/binary.rs
@@ -168,6 +168,8 @@ fn binary_xdr_roundtrip() -> Result<(), HostError> {
 #[test]
 fn invoke_memcpy() -> Result<(), HostError> {
     use crate::xdr::ScContractCode;
+    use crate::{budget::Budget, host::metered_map::MeteredOrdMap};
+
     let contract_id: Hash = [0; 32].into();
     let key = ScVal::Static(ScStatic::LedgerKeyContractCode);
     let storage_key = LedgerKey::ContractData(LedgerKeyContractData {
@@ -188,11 +190,17 @@ fn invoke_memcpy() -> Result<(), HostError> {
     };
     let map = OrdMap::unit(storage_key.clone(), Some(le));
     let mut footprint = Footprint::default();
-    footprint.record_access(&storage_key, AccessType::ReadOnly);
+    footprint.record_access(&storage_key, AccessType::ReadOnly)?;
 
     // initialize storage and host
-    let storage = Storage::with_enforcing_footprint_and_map(footprint, map);
-    let host = Host::with_storage(storage);
+    let storage = Storage::with_enforcing_footprint_and_map(
+        footprint,
+        MeteredOrdMap {
+            map,
+            budget: Budget::default(),
+        },
+    );
+    let host = Host::with_storage_and_budget(storage, Budget::default());
     // create a dummy contract obj as the caller
     let id_obj = host.test_bin_obj(&[0; 32])?;
     // tests binary_new_from_linear_memory
diff --git a/soroban-env-host/src/test/budget_metering.rs b/soroban-env-host/src/test/budget_metering.rs
index 09cadab1..06a72ce0 100644
--- a/soroban-env-host/src/test/budget_metering.rs
+++ b/soroban-env-host/src/test/budget_metering.rs
@@ -1,5 +1,5 @@
 use crate::{
-    budget::CostType,
+    budget::{Budget, CostType},
     xdr::{ScMap, ScMapEntry, ScObject, ScVal},
     CheckedEnv, Host, HostError, Symbol,
 };
@@ -28,8 +28,8 @@ fn xdr_object_conversion() -> Result<(), HostError> {
 
     host.get_budget(|budget| {
         assert_eq!(budget.get_input(CostType::ValXdrConv), 5);
-        assert_eq!(budget.cpu_insns.get_count(), 50);
-        assert_eq!(budget.mem_bytes.get_count(), 5);
+        assert_eq!(budget.get_cpu_insns_count(), 50);
+        assert_eq!(budget.get_mem_bytes_count(), 5);
     });
     Ok(())
 }
@@ -37,8 +37,10 @@ fn xdr_object_conversion() -> Result<(), HostError> {
 #[test]
 fn vm_hostfn_invocation() -> Result<(), HostError> {
     let dummy_id = [0; 32];
-    let storage = Host::test_storage_with_contracts(vec![dummy_id.into()], vec![VEC]);
-    let host = Host::with_storage(storage)
+    let budget = Budget::default();
+    let storage =
+        Host::test_storage_with_contracts(vec![dummy_id.into()], vec![VEC], budget.clone());
+    let host = Host::with_storage_and_budget(storage, budget)
         .test_budget()
         .enable_model(CostType::HostFunction);
 
@@ -52,8 +54,8 @@ fn vm_hostfn_invocation() -> Result<(), HostError> {
     host.try_call(obj.to_object(), sym.into(), args.clone().into())?;
     host.get_budget(|budget| {
         assert_eq!(budget.get_input(CostType::HostFunction), 4);
-        assert_eq!(budget.cpu_insns.get_count(), 40);
-        assert_eq!(budget.mem_bytes.get_count(), 4);
+        assert_eq!(budget.get_cpu_insns_count(), 40);
+        assert_eq!(budget.get_mem_bytes_count(), 4);
     });
 
     Ok(())
diff --git a/soroban-env-host/src/test/contract_event.rs b/soroban-env-host/src/test/contract_event.rs
index 268f6a2e..8dfaf3ce 100644
--- a/soroban-env-host/src/test/contract_event.rs
+++ b/soroban-env-host/src/test/contract_event.rs
@@ -54,7 +54,7 @@ fn contract_event() -> Result<(), HostError> {
 
     // Fish out the last contract event and check that it is
     // correct, and formats as expected.
-    let events = host.get_events();
+    let events = host.get_events()?;
     match events.0.last() {
         Some(HostEvent::Contract(ce)) => {
             assert_eq!(*ce, event_ref)
diff --git a/soroban-env-host/src/test/invocation.rs b/soroban-env-host/src/test/invocation.rs
index 1f16ac45..859d6291 100644
--- a/soroban-env-host/src/test/invocation.rs
+++ b/soroban-env-host/src/test/invocation.rs
@@ -1,6 +1,7 @@
 use soroban_env_common::{xdr::ScVmErrorCode, RawVal};
 
 use crate::{
+    budget::Budget,
     vm::Vm,
     xdr::{Hash, ScHostObjErrorCode, ScVal, ScVec},
     CheckedEnv, Host, HostError, Status, Symbol, Tag,
@@ -34,8 +35,10 @@ fn invoke_single_contract_function() -> Result<(), HostError> {
 #[test]
 fn invoke_cross_contract() -> Result<(), HostError> {
     let dummy_id = [0; 32];
-    let storage = Host::test_storage_with_contracts(vec![dummy_id.into()], vec![ADD_I32]);
-    let host = Host::with_storage(storage);
+    let budget = Budget::default();
+    let storage =
+        Host::test_storage_with_contracts(vec![dummy_id.into()], vec![ADD_I32], budget.clone());
+    let host = Host::with_storage_and_budget(storage, budget);
     let obj = host.test_bin_obj(&dummy_id)?;
     // prepare arguments
     let sym = Symbol::from_str("add");
@@ -51,8 +54,10 @@ fn invoke_cross_contract() -> Result<(), HostError> {
 #[test]
 fn invoke_cross_contract_with_err() -> Result<(), HostError> {
     let dummy_id = [0; 32];
-    let storage = Host::test_storage_with_contracts(vec![dummy_id.into()], vec![VEC]);
-    let host = Host::with_storage(storage);
+    let budget = Budget::default();
+    let storage =
+        Host::test_storage_with_contracts(vec![dummy_id.into()], vec![VEC], budget.clone());
+    let host = Host::with_storage_and_budget(storage, budget);
 
     let obj = host.test_bin_obj(&dummy_id)?;
     // prepare arguments
@@ -76,11 +81,13 @@ fn invoke_cross_contract_with_err() -> Result<(), HostError> {
 fn invoke_cross_contract_indirect() -> Result<(), HostError> {
     let dummy_id0 = [0; 32]; // the calling contract
     let dummy_id1 = [1; 32]; // the called contract
+    let budget = Budget::default();
     let storage = Host::test_storage_with_contracts(
         vec![dummy_id0.into(), dummy_id1.into()],
         vec![INVOKE_CONTRACT, ADD_I32],
+        budget.clone(),
     );
-    let host = Host::with_storage(storage);
+    let host = Host::with_storage_and_budget(storage, budget);
     // prepare arguments
     let id0_obj = host.test_bin_obj(&dummy_id0)?;
     let id1_obj = host.test_bin_obj(&dummy_id1)?;
@@ -97,12 +104,14 @@ fn invoke_cross_contract_indirect() -> Result<(), HostError> {
 #[test]
 fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {
     let dummy_id0 = [0; 32]; // the calling contract
-    let dummy_id1 = [1; 32]; // the called (failing) contract
+    let dummy_id1 = [1; 32]; // the called contract
+    let budget = Budget::default();
     let storage = Host::test_storage_with_contracts(
         vec![dummy_id0.into(), dummy_id1.into()],
         vec![INVOKE_CONTRACT, ADD_I32],
+        budget.clone(),
     );
-    let host = Host::with_storage(storage);
+    let host = Host::with_storage_and_budget(storage, budget);
     // prepare arguments
     let id0_obj = host.test_bin_obj(&dummy_id0)?;
     let id1_obj = host.test_bin_obj(&dummy_id1)?;
diff --git a/soroban-env-host/src/test/lifecycle.rs b/soroban-env-host/src/test/lifecycle.rs
index 154ef1a2..ab745821 100644
--- a/soroban-env-host/src/test/lifecycle.rs
+++ b/soroban-env-host/src/test/lifecycle.rs
@@ -1,4 +1,6 @@
 use crate::{
+    budget::Budget,
+    host::metered_map::MeteredOrdMap,
     host::Frame,
     storage::{AccessType, Footprint, Storage},
     xdr::{
@@ -68,11 +70,18 @@ fn create_contract_test_helper(
     });
 
     let mut footprint = Footprint::default();
-    footprint.record_access(&storage_key, AccessType::ReadWrite);
+    footprint.record_access(&storage_key, AccessType::ReadWrite)?;
 
     // Initialize storage and host
-    let storage = Storage::with_enforcing_footprint_and_map(footprint, OrdMap::new());
-    let host = Host::with_storage(storage);
+    let budget = Budget::default();
+    let storage = Storage::with_enforcing_footprint_and_map(
+        footprint,
+        MeteredOrdMap {
+            map: OrdMap::new(),
+            budget: budget.clone(),
+        },
+    );
+    let host = Host::with_storage_and_budget(storage, budget);
 
     // Create contract
     let obj_code = host.test_bin_obj(&code)?;
@@ -205,7 +214,8 @@ fn create_contract_using_parent_id_test() {
 
     host.visit_storage(|s: &mut Storage| {
         s.footprint
-            .record_access(&child_storage_key, AccessType::ReadWrite);
+            .record_access(&child_storage_key, AccessType::ReadWrite)
+            .unwrap();
         Ok(())
     })
     .unwrap();
diff --git a/soroban-env-host/src/test/util.rs b/soroban-env-host/src/test/util.rs
index cf5e649a..e5b3ce79 100644
--- a/soroban-env-host/src/test/util.rs
+++ b/soroban-env-host/src/test/util.rs
@@ -1,5 +1,6 @@
 use crate::{
-    budget::CostType,
+    budget::{Budget, CostType},
+    host::metered_map::MeteredOrdMap,
     host_object::{HostObj, HostVal},
     im_rc::OrdMap,
     storage::{AccessType, Footprint, Storage},
@@ -34,7 +35,7 @@ impl Host {
     }
 
     pub(crate) fn test_budget(self) -> Self {
-        self.get_budget_mut(|budget| {
+        self.get_budget(|budget| {
             budget.reset_limits(100_000, 100_000); // something big but finite that we may exceed
             budget.reset_models();
         });
@@ -42,9 +43,19 @@ impl Host {
     }
 
     pub(crate) fn enable_model(self, ty: CostType) -> Self {
-        self.get_budget_mut(|budget| {
-            budget.cpu_insns.get_cost_model_mut(ty).lin_param = 10;
-            budget.mem_bytes.get_cost_model_mut(ty).lin_param = 1;
+        self.get_budget(|budget| {
+            budget
+                .0
+                .borrow_mut()
+                .cpu_insns
+                .get_cost_model_mut(ty)
+                .lin_param = 10;
+            budget
+                .0
+                .borrow_mut()
+                .mem_bytes
+                .get_cost_model_mut(ty)
+                .lin_param = 1;
         });
         self
     }
@@ -52,6 +63,7 @@ impl Host {
     pub(crate) fn test_storage_with_contracts(
         ids: Vec<Hash>,
         codes: Vec<&'static [u8]>,
+        budget: Budget,
     ) -> Storage {
         let it = ids.iter().zip(codes.iter());
         let mut footprint = Footprint::default();
@@ -76,9 +88,11 @@ impl Host {
                 ext: LedgerEntryExt::V0,
             };
             map.insert(storage_key.clone(), Some(le));
-            footprint.record_access(&storage_key, AccessType::ReadOnly);
+            footprint
+                .record_access(&storage_key, AccessType::ReadOnly)
+                .unwrap();
         }
-        Storage::with_enforcing_footprint_and_map(footprint, map)
+        Storage::with_enforcing_footprint_and_map(footprint, MeteredOrdMap { budget, map })
     }
 
     pub(crate) fn test_scvec<T: AsScVal>(&self, vals: &[T]) -> Result<ScVec, HostError> {
diff --git a/soroban-env-host/tests/integration.rs b/soroban-env-host/tests/integration.rs
index 58b55db9..3dea2cb7 100644
--- a/soroban-env-host/tests/integration.rs
+++ b/soroban-env-host/tests/integration.rs
@@ -40,7 +40,7 @@ fn debug_fmt() {
 
     // Fish out the last debug event and check that it is
     // correct, and formats as expected.
-    let events = host.get_events();
+    let events = host.get_events().unwrap();
     match events.0.last() {
         Some(HostEvent::Debug(de)) => {
             assert_eq!(