Repository: stellar/rs-soroban-env
Pull request #572 (2022-11-12 01:11:12)

Env changes to decouple contract instance from source (CAP-46-02)
### What

This change implements separation WASM sources as per updated CAP-46-02. It also uses the update host function call interface.

### Why

This allows to efficiently the reuse the contract sources, so that it's possible to have multiple contract instances that share the same single ledger entry with WASM code.

### Known limitations

This temporarily removes the host fn that allows to create a contract from contract on behalf of a `ed25519` public key (they weren't implemented anyway). We may bring this back in a bit different form if we change the authorization.


dmkozh: PTAL

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 4606b05f..8f17458b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1175,7 +1175,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 [[package]]
 name = "stellar-xdr"
 version = "0.0.7"
-source = "git+https://github.com/stellar/rs-stellar-xdr?rev=e88f9fa7#e88f9fa7ab3d0f54efb5cee8499ef9080f4d41f4"
+source = "git+https://github.com/stellar/rs-stellar-xdr?rev=5fd7c7d4#5fd7c7d44d66452209a951be89c0c3602ab9f302"
 dependencies = [
  "base64",
  "hex",
diff --git a/Cargo.toml b/Cargo.toml
index 542bb509..2948c1e3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,7 +27,7 @@ soroban-native-sdk-macros = { version = "0.0.9", path = "soroban-native-sdk-macr
 [workspace.dependencies.stellar-xdr]
 version = "0.0.7"
 git = "https://github.com/stellar/rs-stellar-xdr"
-rev = "e88f9fa7"
+rev = "5fd7c7d4"
 default-features = false
 
 [workspace.dependencies.wasmi]
diff --git a/soroban-env-common/src/env.rs b/soroban-env-common/src/env.rs
index 4c42c11f..4c0d0a8f 100644
--- a/soroban-env-common/src/env.rs
+++ b/soroban-env-common/src/env.rs
@@ -304,21 +304,13 @@ macro_rules! call_macro_with_all_host_functions {
                 {"0", fn has_contract_data(k:RawVal) -> RawVal}
                 {"1", fn get_contract_data(k:RawVal) -> RawVal}
                 {"2", fn del_contract_data(k:RawVal) -> RawVal}
-                {"3", fn create_contract_from_ed25519(v: Object, salt: Object, key: Object, sig: Object) -> Object}
-                {"4", fn create_contract_from_contract(v: Object, salt: Object) -> Object}
-                {"5", fn create_token_from_ed25519(salt: Object, key: Object, sig: Object) -> Object}
-                {"6", fn create_token_from_contract(salt: Object) -> Object}
-                {"7", fn create_token_from_asset(asset: Object) -> Object}
-                /// Create a contract using the source account and salt as input
-                /// to the contract ID that gets created. Pass as arguments a
-                /// Bytes for the wasm code and a Bytes for the salt. Returned
-                /// will be a Bytes of length 32 bytes.
-                {"8", fn create_contract_from_source_account(v: Object, salt: Object) -> Object}
-                /// Create a token contract using the source account and salt
-                /// as input to the contract ID that gets created. Pass as
-                /// arguments a Bytes for the salt. Returned
-                /// will be a Bytes of length 32 bytes.
-                {"9", fn create_token_from_source_account(salt: Object) -> Object}
+                /// Deploys a contract from the current contract. `wasm_hash` must
+                /// be a hash of the contract code that has already been installed
+                /// on this network. `salt` is used to create a unique contract id.
+                {"3", fn create_contract_from_contract(wasm_hash: Object, salt: Object) -> Object}
+                /// Deploys a built-in token contract from the current contract.
+                /// `salt` is used to create a unique contract id for the token.
+                {"4", fn create_token_from_contract(salt: Object) -> Object}
             }
 
             mod call "d" {
diff --git a/soroban-env-common/src/meta.rs b/soroban-env-common/src/meta.rs
index e1d58a7d..9a130b7e 100644
--- a/soroban-env-common/src/meta.rs
+++ b/soroban-env-common/src/meta.rs
@@ -58,5 +58,5 @@
 pub const ENV_META_V0_SECTION_NAME: &'static str = "contractenvmetav0";
 
 soroban_env_macros::generate_env_meta_consts!(
-    interface_version: 24,
+    interface_version: 25,
 );
diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs
index 53179275..a95310de 100644
--- a/soroban-env-host/src/host.rs
+++ b/soroban-env-host/src/host.rs
@@ -10,28 +10,23 @@ use im_rc::{OrdMap, Vector};
 use num_bigint::Sign;
 use sha2::{Digest, Sha256};
 use soroban_env_common::{
+    xdr::{
+        AccountId, Asset, ContractCodeEntry, ContractDataEntry, ContractEvent, ContractEventBody,
+        ContractEventType, ContractEventV0, ContractId, CreateContractArgs, ExtensionPoint, Hash,
+        HashIdPreimage, HostFunction, HostFunctionType, InstallContractCodeArgs, LedgerEntry,
+        LedgerEntryData, LedgerKey, LedgerKeyContractCode, ScBigInt, ScContractCode,
+        ScHostContextErrorCode, ScHostFnErrorCode, ScHostObjErrorCode, ScHostStorageErrorCode,
+        ScHostValErrorCode, ScMap, ScMapEntry, ScObject, ScStatusType, ScVal, ScVec,
+        ThresholdIndexes,
+    },
     EnvVal, InvokerType, Status, TryConvert, TryFromVal, TryIntoVal, VmCaller, VmCallerCheckedEnv,
 };
 
-use soroban_env_common::xdr::{
-    AccountId, Asset, ContractEvent, ContractEventBody, ContractEventType, ContractEventV0,
-    ExtensionPoint, Hash, ScStatus, ScStatusType, ThresholdIndexes,
-};
-
-#[cfg(any(test, feature = "testutils"))]
-use soroban_env_common::xdr::ScUnknownErrorCode;
-
 use crate::budget::{Budget, CostType};
 use crate::events::{DebugError, DebugEvent, Events};
 use crate::storage::Storage;
 use crate::weak_host::WeakHost;
 
-use crate::xdr::{
-    ContractDataEntry, HostFunction, LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey,
-    ScBigInt, ScContractCode, ScHostContextErrorCode, ScHostFnErrorCode, ScHostObjErrorCode,
-    ScHostStorageErrorCode, ScHostValErrorCode, ScMap, ScMapEntry, ScObject, ScVal, ScVec,
-};
-
 use crate::host_object::{HostMap, HostObj, HostObject, HostObjectType, HostVal, HostVec};
 #[cfg(feature = "vm")]
 use crate::SymbolStr;
@@ -108,7 +103,7 @@ impl TestContractFrame {
 pub(crate) enum Frame {
     #[cfg(feature = "vm")]
     ContractVM(Rc<Vm>, Symbol),
-    HostFunction(HostFunction),
+    HostFunction(HostFunctionType),
     Token(Hash, Symbol),
     #[cfg(any(test, feature = "testutils"))]
     TestContract(TestContractFrame),
@@ -218,6 +213,11 @@ impl Host {
         *self.0.source_account.borrow_mut() = Some(source_account);
     }
 
+    #[cfg(any(test, feature = "testutils"))]
+    pub fn remove_source_account(&self) {
+        *self.0.source_account.borrow_mut() = None;
+    }
+
     pub fn source_account(&self) -> Result<AccountId, HostError> {
         if let Some(account_id) = self.0.source_account.borrow().as_ref() {
             Ok(account_id.clone())
@@ -717,41 +717,67 @@ impl Host {
     }
 
     // Notes on metering: this is covered by the called components.
-    pub fn create_contract_with_id(
+    fn create_contract_with_id(
         &self,
-        contract: ScContractCode,
-        id_obj: Object,
+        contract_id: Object,
+        contract_source: ScContractCode,
     ) -> Result<(), HostError> {
-        let new_contract_id = self.hash_from_obj_input("id_obj", id_obj)?;
+        let new_contract_id = self.hash_from_obj_input("id_obj", contract_id)?;
         let storage_key =
-            self.contract_code_ledger_key(new_contract_id.metered_clone(&self.0.budget)?);
+            self.contract_source_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"));
         }
-        self.store_contract_code(contract, new_contract_id, &storage_key)?;
+        // Make sure the contract code exists. With immutable contracts and
+        // without this check it would be possible to accidentally create a
+        // contract that never may be invoked (just by providing a bad hash).
+        if let ScContractCode::WasmRef(wasm_hash) = &contract_source {
+            let wasm_storage_key =
+                self.contract_code_ledger_key(wasm_hash.metered_clone(&self.0.budget)?);
+            if !self.0.storage.borrow_mut().has(&wasm_storage_key)? {
+                return Err(self.err_general("Contract code was not installed"));
+            }
+        }
+        self.store_contract_source(contract_source, new_contract_id, &storage_key)?;
         Ok(())
     }
 
-    pub fn create_contract_with_id_preimage(
+    fn maybe_initialize_asset_token(
         &self,
-        contract: ScContractCode,
-        id_preimage: Vec<u8>,
+        contract_id: Object,
+        id_preimage: HashIdPreimage,
+    ) -> Result<(), HostError> {
+        if let HashIdPreimage::ContractIdFromAsset(asset_preimage) = id_preimage {
+            let mut asset_bytes: Vec<u8> = Default::default();
+            self.metered_write_xdr(&asset_preimage.asset, &mut asset_bytes)?;
+            self.call_n(
+                contract_id,
+                Symbol::from_str("init_asset"),
+                &[self.add_host_object(asset_bytes)?.into()],
+                false,
+            )?;
+            Ok(())
+        } else {
+            Ok(())
+        }
+    }
+
+    fn create_contract_with_id_preimage(
+        &self,
+        contract_source: ScContractCode,
+        id_preimage: HashIdPreimage,
     ) -> Result<Object, HostError> {
-        let id_obj = self.compute_hash_sha256(
-            &mut VmCaller::none(),
-            self.add_host_object(id_preimage)?.into(),
-        )?;
-        self.create_contract_with_id(contract, id_obj)?;
+        let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?;
+        let id_obj = self.add_host_object(id_arr.to_vec())?.to_object();
+        self.create_contract_with_id(id_obj, contract_source.metered_clone(self.budget_ref())?)?;
+        self.maybe_initialize_asset_token(id_obj, id_preimage)?;
         Ok(id_obj)
     }
 
     pub fn get_contract_id_from_asset(&self, asset: Asset) -> Result<Object, HostError> {
         let id_preimage = self.id_preimage_from_asset(asset)?;
-        let id_obj = self.compute_hash_sha256(
-            &mut VmCaller::none(),
-            self.add_host_object(id_preimage)?.into(),
-        )?;
-        Ok(id_obj)
+        let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?;
+        Ok(self.add_host_object(id_arr.to_vec())?.to_object())
     }
 
     // Notes on metering: this is covered by the called components.
@@ -762,15 +788,20 @@ impl Host {
         args: &[RawVal],
     ) -> Result<RawVal, HostError> {
         // Create key for storage
-        let storage_key = self.contract_code_ledger_key(id.metered_clone(&self.0.budget)?);
-        match self.retrieve_contract_code_from_storage(&storage_key)? {
+        let storage_key = self.contract_source_ledger_key(id.metered_clone(&self.0.budget)?);
+        match self.retrieve_contract_source_from_storage(&storage_key)? {
             #[cfg(feature = "vm")]
-            ScContractCode::Wasm(wasm) => {
-                let vm = Vm::new(self, id.metered_clone(&self.0.budget)?, wasm.as_slice())?;
+            ScContractCode::WasmRef(wasm_hash) => {
+                let code_entry = self.retrieve_contract_code_from_storage(wasm_hash)?;
+                let vm = Vm::new(
+                    self,
+                    id.metered_clone(&self.0.budget)?,
+                    code_entry.code.as_slice(),
+                )?;
                 vm.invoke_function_raw(self, SymbolStr::from(func).as_ref(), args)
             }
             #[cfg(not(feature = "vm"))]
-            ScContractCode::Wasm(_) => Err(self.err_general("could not dispatch")),
+            ScContractCode::WasmRef(_) => Err(self.err_general("could not dispatch")),
             ScContractCode::Token => {
                 self.with_frame(Frame::Token(id.clone(), func.clone()), || {
                     use crate::native_contract::{NativeContract, Token};
@@ -827,6 +858,7 @@ impl Host {
                 return self.with_frame(Frame::TestContract(frame), || {
                     use std::any::Any;
                     use std::panic::{catch_unwind, set_hook, take_hook, AssertUnwindSafe};
+                    use crate::xdr::ScUnknownErrorCode;
                     type PanicVal = Box<dyn Any + Send>;
 
                     // We're directly invoking a native rust contract here,
@@ -897,23 +929,21 @@ impl Host {
     }
 
     // Notes on metering: covered by the called components.
-    pub fn invoke_function_raw(&self, hf: HostFunction, args: ScVec) -> Result<RawVal, HostError> {
+    fn invoke_function_raw(&self, hf: HostFunction) -> Result<RawVal, HostError> {
+        let hf_type = hf.discriminant();
         //TODO: should the create_* methods below return a RawVal instead of Object to avoid this conversion?
         match hf {
-            HostFunction::InvokeContract => {
+            HostFunction::InvokeContract(args) => {
                 if let [ScVal::Object(Some(scobj)), ScVal::Symbol(scsym), rest @ ..] =
                     args.as_slice()
                 {
-                    self.with_frame(Frame::HostFunction(hf), || {
+                    self.with_frame(Frame::HostFunction(hf_type), || {
                         // Metering: conversions to host objects are covered. Cost of collecting
                         // RawVals into Vec is ignored. Since 1. RawVals are cheap to clone 2. the
                         // max number of args is fairly limited.
                         let object = self.to_host_obj(scobj)?.to_object();
                         let symbol = <Symbol>::try_from(scsym)?;
-                        let args = rest
-                            .iter()
-                            .map(|scv| self.to_host_val(scv).map(|hv| hv.val))
-                            .collect::<Result<Vec<RawVal>, HostError>>()?;
+                        let args = self.scvals_to_rawvals(rest)?;
                         // since the `HostFunction` frame must be the bottom of the call stack,
                         // reentry is irrelevant, we always pass in `allow_reentry = false`.
                         self.call_n(object, symbol, &args[..], false)
@@ -925,63 +955,20 @@ impl Host {
                     ))
                 }
             }
-            HostFunction::CreateContractWithEd25519 => Err(self.err_status_msg(
-                ScHostFnErrorCode::UnknownError,
-                "CreateContractWithEd25519 is not yet implemented",
-            )),
-            HostFunction::CreateContractWithSourceAccount => {
-                if let [ScVal::Object(Some(c_obj)), ScVal::Object(Some(s_obj))] = args.as_slice() {
-                    self.with_frame(Frame::HostFunction(hf), || {
-                        let contract = self.to_host_obj(c_obj)?.to_object();
-                        let salt = self.to_host_obj(s_obj)?.to_object();
-                        self.create_contract_from_source_account(
-                            &mut VmCaller::none(),
-                            contract,
-                            salt,
-                        )
-                        .map(|obj| <RawVal>::from(obj))
-                    })
-                } else {
-                    Err(self.err_status_msg(
-                        ScHostFnErrorCode::InputArgsWrongLength,
-                        "unexpected arguments to 'CreateContractWithSourceAccount' host function",
-                    ))
-                }
-            }
-            HostFunction::CreateTokenContractWithAsset => {
-                if let [ScVal::Object(Some(a_obj))] = args.as_slice() {
-                    self.with_frame(Frame::HostFunction(hf), || {
-                        let asset_bytes = self.to_host_obj(a_obj)?.to_object();
-                        self.create_token_from_asset(&mut VmCaller::none(), asset_bytes)
-                            .map(|obj| <RawVal>::from(obj))
-                    })
-                } else {
-                    Err(self.err_status_msg(
-                        ScHostFnErrorCode::InputArgsWrongLength,
-                        "unexpected arguments to 'CreateTokenContractWithAsset' host function",
-                    ))
-                }
-            }
-            HostFunction::CreateTokenContractWithSourceAccount => {
-                if let [ScVal::Object(Some(s_obj))] = args.as_slice() {
-                    self.with_frame(Frame::HostFunction(hf), || {
-                        let salt = self.to_host_obj(s_obj)?.to_object();
-                        self.create_token_from_source_account(&mut VmCaller::none(), salt)
-                            .map(|obj| <RawVal>::from(obj))
-                    })
-                } else {
-                    Err(self.err_status_msg(
-                        ScHostFnErrorCode::InputArgsWrongLength,
-                        "unexpected arguments to 'CreateTokenContractWithSourceAccount' host function",
-                    ))
-                }
-            }
+            HostFunction::CreateContract(args) => self
+                .with_frame(Frame::HostFunction(hf_type), || {
+                    self.create_contract(args).map(|obj| <RawVal>::from(obj))
+                }),
+            HostFunction::InstallContractCode(args) => self
+                .with_frame(Frame::HostFunction(hf_type), || {
+                    self.install_contract(args).map(|obj| <RawVal>::from(obj))
+                }),
         }
     }
 
     // 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)?;
+    pub fn invoke_function(&self, hf: HostFunction) -> Result<ScVal, HostError> {
+        let rv = self.invoke_function_raw(hf)?;
         self.from_host_val(rv)
     }
 
@@ -1002,24 +989,6 @@ impl Host {
         }
     }
 
-    // "testutils" is not covered by budget metering.
-    #[cfg(any(test, feature = "testutils"))]
-    pub fn register_test_contract_wasm(
-        &self,
-        contract_id: Object,
-        contract_wasm: &[u8],
-    ) -> Result<(), HostError> {
-        let contract_code =
-            ScContractCode::Wasm(contract_wasm.try_into().map_err(|_| self.err_general(""))?);
-        self.create_contract_with_id(contract_code, contract_id)
-    }
-
-    // "testutils" is not covered by budget metering.
-    #[cfg(any(test, feature = "testutils"))]
-    pub fn register_test_contract_token(&self, contract_id: Object) -> Result<(), HostError> {
-        self.create_contract_with_id(ScContractCode::Token, contract_id)
-    }
-
     // Writes an arbitrary ledger entry to storage.
     // "testutils" is not covered by budget metering.
     #[cfg(any(test, feature = "testutils"))]
@@ -1036,6 +1005,62 @@ impl Host {
         self.record_contract_event(ContractEventType::System, topics, data)?;
         Ok(Status::OK.into())
     }
+
+    fn create_contract(&self, args: CreateContractArgs) -> Result<Object, HostError> {
+        let id_preimage = match args.contract_id {
+            ContractId::Asset(asset) => self.id_preimage_from_asset(asset)?,
+            ContractId::SourceAccount(salt) => self.id_preimage_from_source_account(salt)?,
+            ContractId::Ed25519PublicKey(key_with_signature) => {
+                let signature_payload_preimage = self.create_contract_args_hash_preimage(
+                    args.source.metered_clone(&self.budget_ref())?,
+                    key_with_signature.salt.metered_clone(self.budget_ref())?,
+                )?;
+                let signature_payload = self.metered_hash_xdr(&signature_payload_preimage)?;
+                self.verify_sig_ed25519_internal(
+                    &signature_payload,
+                    &self.ed25519_pub_key_from_bytes(&key_with_signature.key.0)?,
+                    &self.signature_from_bytes(
+                        "create_contract_sig",
+                        &key_with_signature.signature.0,
+                    )?,
+                )?;
+                self.id_preimage_from_ed25519(key_with_signature.key, key_with_signature.salt)?
+            }
+        };
+        self.create_contract_with_id_preimage(args.source, id_preimage)
+    }
+
+    fn install_contract(&self, args: InstallContractCodeArgs) -> Result<Object, HostError> {
+        let hash_bytes = self.metered_hash_xdr(&args)?;
+        let hash_obj = self.add_host_object(hash_bytes.to_vec())?.to_object();
+        let code_key = LedgerKey::ContractCode(LedgerKeyContractCode {
+            hash: Hash(hash_bytes.metered_clone(self.budget_ref())?),
+        });
+        if !self.0.storage.borrow_mut().has(&code_key)? {
+            self.with_mut_storage(|storage| {
+                let data = LedgerEntryData::ContractCode(ContractCodeEntry {
+                    hash: Hash(hash_bytes),
+                    code: args.code,
+                    ext: ExtensionPoint::V0,
+                });
+                storage.put(&code_key, &Host::ledger_entry_from_data(data))
+            })?;
+        }
+        Ok(hash_obj)
+    }
+
+    pub(crate) fn verify_sig_ed25519_internal(
+        &self,
+        payload: &[u8],
+        public_key: &ed25519_dalek::PublicKey,
+        sig: &ed25519_dalek::Signature,
+    ) -> Result<(), HostError> {
+        use ed25519_dalek::Verifier;
+        self.charge_budget(CostType::VerifyEd25519Sig, payload.len() as u64)?;
+        public_key
+            .verify(payload, &sig)
+            .map_err(|_| self.err_general("Failed ED25519 verification"))
+    }
 }
 
 // Notes on metering: these are called from the guest and thus charged on the VM instructions.
@@ -1752,12 +1777,10 @@ impl VmCallerCheckedEnv for Host {
             key: self.from_host_val(k)?,
             val: self.from_host_val(v)?,
         });
-        let val = LedgerEntry {
-            last_modified_ledger_seq: 0,
-            data,
-            ext: LedgerEntryExt::V0,
-        };
-        self.0.storage.borrow_mut().put(&key, &val)?;
+        self.0
+            .storage
+            .borrow_mut()
+            .put(&key, &Host::ledger_entry_from_data(data))?;
         Ok(().into())
     }
 
@@ -1803,103 +1826,19 @@ impl VmCallerCheckedEnv for Host {
         Ok(().into())
     }
 
-    // Notes on metering: covered by the components.
-    fn create_contract_from_ed25519(
-        &self,
-        vmcaller: &mut VmCaller<Host>,
-        v: Object,
-        salt: Object,
-        key: Object,
-        sig: Object,
-    ) -> Result<Object, HostError> {
-        Err(self.err_status_msg(
-            ScStatus::HostFunctionError(ScHostFnErrorCode::UnknownError),
-            "not yet implemented",
-        ))
-    }
-
     // Notes on metering: covered by the components.
     fn create_contract_from_contract(
         &self,
         _vmcaller: &mut VmCaller<Host>,
-        v: Object,
+        wasm_hash: Object,
         salt: Object,
     ) -> Result<Object, HostError> {
         let contract_id = self.get_current_contract_id()?;
         let salt = self.uint256_from_obj_input("salt", salt)?;
 
-        let wasm = self.visit_obj(v, |b: &Vec<u8>| {
-            Ok(ScContractCode::Wasm(
-                b.try_into()
-                    .map_err(|_| self.err_general("code too large"))?,
-            ))
-        })?;
-        let buf = self.id_preimage_from_contract(contract_id, salt)?;
-        self.create_contract_with_id_preimage(wasm, buf)
-    }
-
-    fn create_contract_from_source_account(
-        &self,
-        _vmcaller: &mut VmCaller<Host>,
-        v: Object,
-        salt: Object,
-    ) -> Result<Object, HostError> {
-        let frames = self.0.context.borrow();
-        if frames.len() > 1 {
-            return Err(self.err_general("cannot be called from a contract"));
-        }
-
-        let salt = self.uint256_from_obj_input("salt", salt)?;
-
-        let wasm = self.visit_obj(v, |b: &Vec<u8>| {
-            Ok(ScContractCode::Wasm(
-                b.try_into()
-                    .map_err(|_| self.err_general("code too large"))?,
-            ))
-        })?;
-        let buf = self.id_preimage_from_source_account(salt)?;
-        self.create_contract_with_id_preimage(wasm, buf)
-    }
-
-    fn create_token_from_source_account(
-        &self,
-        _vmcaller: &mut VmCaller<Host>,
-        salt: Object,
-    ) -> Result<Object, HostError> {
-        let frames = self.0.context.borrow();
-        if frames.len() > 1 {
-            return Err(self.err_general("cannot be called from a contract"));
-        }
-
-        let salt = self.uint256_from_obj_input("salt", salt)?;
-
-        let buf = self.id_preimage_from_source_account(salt)?;
-        self.create_contract_with_id_preimage(ScContractCode::Token, buf)
-    }
-
-    fn create_token_from_ed25519(
-        &self,
-        vmcaller: &mut VmCaller<Host>,
-        salt: Object,
-        key: Object,
-        sig: Object,
-    ) -> Result<Object, HostError> {
-        let salt_val = self.uint256_from_obj_input("salt", salt)?;
-        let key_val = self.uint256_from_obj_input("key", key)?;
-
-        // Verify parameters
-        let params = {
-            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::BytesAppend, params.len() as u64)?;
-        let hash = self.compute_hash_sha256(vmcaller, self.add_host_object(params)?.into())?;
-
-        self.verify_sig_ed25519(vmcaller, hash, key, sig)?;
-
-        let buf = self.id_preimage_from_ed25519(key_val, salt_val)?;
-        self.create_contract_with_id_preimage(ScContractCode::Token, buf)
+        let code = ScContractCode::WasmRef(self.hash_from_obj_input("wasm_hash", wasm_hash)?);
+        let id_preimage = self.id_preimage_from_contract(contract_id, salt)?;
+        self.create_contract_with_id_preimage(code, id_preimage)
     }
 
     fn create_token_from_contract(
@@ -1909,27 +1848,8 @@ impl VmCallerCheckedEnv for Host {
     ) -> Result<Object, HostError> {
         let contract_id = self.get_current_contract_id()?;
         let salt = self.uint256_from_obj_input("salt", salt)?;
-        let buf = self.id_preimage_from_contract(contract_id, salt)?;
-        self.create_contract_with_id_preimage(ScContractCode::Token, buf)
-    }
-
-    //TODO: metering
-    fn create_token_from_asset(
-        &self,
-        vmcaller: &mut VmCaller<Host>,
-        asset_bytes: Object,
-    ) -> Result<Object, HostError> {
-        let asset = self.metered_from_xdr_obj(asset_bytes)?;
-        let id_preimage = self.id_preimage_from_asset(asset)?;
-        let id = self.create_contract_with_id_preimage(ScContractCode::Token, id_preimage)?;
-        self.call_n(
-            id,
-            Symbol::from_str("init_asset"),
-            &[asset_bytes.try_into()?],
-            false,
-        )?;
-
-        Ok(id)
+        let id_preimage = self.id_preimage_from_contract(contract_id, salt)?;
+        self.create_contract_with_id_preimage(ScContractCode::Token, id_preimage)
     }
 
     // Notes on metering: here covers the args unpacking. The actual VM work is changed at lower layers.
@@ -2598,14 +2518,10 @@ impl VmCallerCheckedEnv for Host {
         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, |bytes: &Vec<u8>| {
-            self.charge_budget(CostType::VerifyEd25519Sig, bytes.len() as u64)?;
-            public_key
-                .verify(bytes, &sig)
-                .map_err(|_| self.err_general("Failed ED25519 verification"))
+        let res = self.visit_obj(x, |payload: &Vec<u8>| {
+            self.verify_sig_ed25519_internal(payload, &public_key, &sig)
         });
         Ok(res?.into())
     }
diff --git a/soroban-env-host/src/host/conversion.rs b/soroban-env-host/src/host/conversion.rs
index ff559665..54e751e2 100644
--- a/soroban-env-host/src/host/conversion.rs
+++ b/soroban-env-host/src/host/conversion.rs
@@ -116,6 +116,14 @@ impl Host {
         self.fixed_length_bytes_from_obj_input::<Uint256, 32>(name, u256)
     }
 
+    pub(crate) fn signature_from_bytes(
+        &self,
+        name: &'static str,
+        bytes: &[u8],
+    ) -> Result<Signature, HostError> {
+        self.fixed_length_bytes_from_slice::<Signature, SIGNATURE_LENGTH>(name, bytes)
+    }
+
     pub(crate) fn signature_from_obj_input(
         &self,
         name: &'static str,
@@ -124,6 +132,30 @@ impl Host {
         self.fixed_length_bytes_from_obj_input::<Signature, SIGNATURE_LENGTH>(name, sig)
     }
 
+    fn fixed_length_bytes_from_slice<T, const N: usize>(
+        &self,
+        name: &'static str,
+        bytes_arr: &[u8],
+    ) -> Result<T, HostError>
+    where
+        T: From<[u8; N]>,
+    {
+        match <[u8; N]>::try_from(bytes_arr) {
+            Ok(arr) => {
+                self.charge_budget(CostType::BytesClone, N as u64)?;
+                Ok(arr.into())
+            }
+            Err(cvt) => Err(self.err(
+                // TODO: This is a wrong error code to use here, we should replace
+                // it with a more generic one.
+                DebugError::new(ScHostObjErrorCode::ContractHashWrongLength) // TODO: this should be renamed to be more generic
+                    .msg("{} has wrong length for input '{}'")
+                    .arg(std::any::type_name::<T>())
+                    .arg(name),
+            )),
+        }
+    }
+
     fn fixed_length_bytes_from_obj_input<T, const N: usize>(
         &self,
         name: &'static str,
@@ -133,31 +165,21 @@ impl Host {
         T: From<[u8; N]>,
     {
         self.visit_obj(obj, |bytes: &Vec<u8>| {
-            match <[u8; N]>::try_from(bytes.as_slice()) {
-                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 {}")
-                        .arg(std::any::type_name::<T>())
-                        .arg(obj.to_raw())
-                        .arg(name),
-                )),
-            }
+            self.fixed_length_bytes_from_slice(name, bytes)
         })
     }
 
-    pub fn ed25519_pub_key_from_obj_input(&self, k: Object) -> Result<PublicKey, HostError> {
-        self.visit_obj(k, |bytes: &Vec<u8>| {
-            self.charge_budget(CostType::ComputeEd25519PubKey, bytes.len() as u64)?;
-            PublicKey::from_bytes(bytes).map_err(|_| {
-                self.err_status_msg(ScHostObjErrorCode::UnexpectedType, "invalid public key")
-            })
+    pub(crate) fn ed25519_pub_key_from_bytes(&self, bytes: &[u8]) -> Result<PublicKey, HostError> {
+        self.charge_budget(CostType::ComputeEd25519PubKey, bytes.len() as u64)?;
+        PublicKey::from_bytes(bytes).map_err(|_| {
+            self.err_status_msg(ScHostObjErrorCode::UnexpectedType, "invalid public key")
         })
     }
 
+    pub fn ed25519_pub_key_from_obj_input(&self, k: Object) -> Result<PublicKey, HostError> {
+        self.visit_obj(k, |bytes: &Vec<u8>| self.ed25519_pub_key_from_bytes(bytes))
+    }
+
     pub fn sha256_hash_from_bytes_input(&self, x: Object) -> Result<Vec<u8>, HostError> {
         self.visit_obj(x, |bytes: &Vec<u8>| {
             self.charge_budget(CostType::ComputeSha256Hash, bytes.len() as u64)?;
@@ -300,4 +322,11 @@ impl Host {
             Ok(hv.iter().map(|a| a.to_raw()).collect())
         })
     }
+
+    pub(crate) fn scvals_to_rawvals(&self, sc_vals: &[ScVal]) -> Result<Vec<RawVal>, HostError> {
+        sc_vals
+            .iter()
+            .map(|scv| self.to_host_val(scv).map(|hv| hv.val))
+            .collect::<Result<Vec<RawVal>, HostError>>()
+    }
 }
diff --git a/soroban-env-host/src/host/data_helper.rs b/soroban-env-host/src/host/data_helper.rs
index 8b7744b3..0f24a527 100644
--- a/soroban-env-host/src/host/data_helper.rs
+++ b/soroban-env-host/src/host/data_helper.rs
@@ -1,18 +1,14 @@
 use std::cmp::min;
 
-use soroban_env_common::{
-    xdr::{
-        Asset, HashIdPreimageSourceAccountContractId, PublicKey, Signer, SignerKey,
-        ThresholdIndexes, TrustLineAsset,
-    },
-    CheckedEnv, InvokerType,
-};
+use soroban_env_common::{CheckedEnv, InvokerType};
 
 use crate::xdr::{
-    AccountEntry, AccountId, ContractDataEntry, Hash, HashIdPreimage, HashIdPreimageContractId,
-    HashIdPreimageEd25519ContractId, LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey,
-    LedgerKeyAccount, LedgerKeyContractData, LedgerKeyTrustLine, ScContractCode,
-    ScHostStorageErrorCode, ScHostValErrorCode, ScObject, ScStatic, ScVal, Uint256,
+    AccountEntry, AccountId, Asset, ContractCodeEntry, ContractDataEntry, Hash, HashIdPreimage,
+    HashIdPreimageContractId, HashIdPreimageCreateContractArgs, HashIdPreimageEd25519ContractId,
+    HashIdPreimageFromAsset, HashIdPreimageSourceAccountContractId, LedgerEntry, LedgerEntryData,
+    LedgerEntryExt, LedgerKey, LedgerKeyAccount, LedgerKeyContractCode, LedgerKeyContractData,
+    LedgerKeyTrustLine, PublicKey, ScContractCode, ScHostStorageErrorCode, ScHostValErrorCode,
+    ScObject, ScStatic, ScVal, Signer, SignerKey, ThresholdIndexes, TrustLineAsset, Uint256,
 };
 use crate::{Host, HostError};
 
@@ -20,7 +16,7 @@ use super::metered_clone::MeteredClone;
 
 impl Host {
     // Notes on metering: free
-    pub fn contract_code_ledger_key(&self, contract_id: Hash) -> LedgerKey {
+    pub fn contract_source_ledger_key(&self, contract_id: Hash) -> LedgerKey {
         LedgerKey::ContractData(LedgerKeyContractData {
             contract_id,
             key: ScVal::Static(ScStatic::LedgerKeyContractCode),
@@ -28,7 +24,7 @@ impl Host {
     }
 
     // Notes on metering: retrieving from storage covered. Rest are free.
-    pub fn retrieve_contract_code_from_storage(
+    pub(crate) fn retrieve_contract_source_from_storage(
         &self,
         key: &LedgerKey,
     ) -> Result<ScContractCode, HostError> {
@@ -47,24 +43,37 @@ impl Host {
         }
     }
 
+    pub(crate) fn contract_code_ledger_key(&self, wasm_hash: Hash) -> LedgerKey {
+        LedgerKey::ContractCode(LedgerKeyContractCode { hash: wasm_hash })
+    }
+
+    pub(crate) fn retrieve_contract_code_from_storage(
+        &self,
+        wasm_hash: Hash,
+    ) -> Result<ContractCodeEntry, HostError> {
+        let key = self.contract_code_ledger_key(wasm_hash);
+        match self.0.storage.borrow_mut().get(&key)?.data {
+            LedgerEntryData::ContractCode(e) => Ok(e),
+            _ => Err(self.err_status(ScHostStorageErrorCode::AccessToUnknownEntry)),
+        }
+    }
+
     // Notes on metering: `from_host_obj` and `put` to storage covered, rest are free.
-    pub fn store_contract_code(
+    pub(crate) fn store_contract_source(
         &self,
-        contract: ScContractCode,
+        contract_source: ScContractCode,
         contract_id: Hash,
         key: &LedgerKey,
     ) -> Result<(), HostError> {
         let data = LedgerEntryData::ContractData(ContractDataEntry {
             contract_id,
             key: ScVal::Static(ScStatic::LedgerKeyContractCode),
-            val: ScVal::Object(Some(ScObject::ContractCode(contract))),
+            val: ScVal::Object(Some(ScObject::ContractCode(contract_source))),
         });
-        let val = LedgerEntry {
-            last_modified_ledger_seq: 0,
-            data,
-            ext: LedgerEntryExt::V0,
-        };
-        self.0.storage.borrow_mut().put(&key, &val)?;
+        self.0
+            .storage
+            .borrow_mut()
+            .put(&key, &Host::ledger_entry_from_data(data))?;
         Ok(())
     }
 
@@ -73,14 +82,15 @@ impl Host {
         &self,
         key: Uint256,
         salt: Uint256,
-    ) -> Result<Vec<u8>, HostError> {
-        let pre_image = HashIdPreimage::ContractIdFromEd25519(HashIdPreimageEd25519ContractId {
-            ed25519: key,
-            salt,
-        });
-        let mut buf = Vec::new();
-        self.metered_write_xdr(&pre_image, &mut buf)?;
-        Ok(buf)
+    ) -> Result<HashIdPreimage, HostError> {
+        Ok(HashIdPreimage::ContractIdFromEd25519(
+            HashIdPreimageEd25519ContractId {
+                network_id: self
+                    .hash_from_obj_input("network_id", self.get_ledger_network_id()?)?,
+                ed25519: key,
+                salt,
+            },
+        ))
     }
 
     // metering: covered by components
@@ -88,37 +98,62 @@ impl Host {
         &self,
         contract_id: Hash,
         salt: Uint256,
-    ) -> Result<Vec<u8>, HostError> {
-        let pre_image =
-            HashIdPreimage::ContractIdFromContract(HashIdPreimageContractId { contract_id, salt });
-        let mut buf = Vec::new();
-        self.metered_write_xdr(&pre_image, &mut buf)?;
-        Ok(buf)
+    ) -> Result<HashIdPreimage, HostError> {
+        Ok(HashIdPreimage::ContractIdFromContract(
+            HashIdPreimageContractId {
+                network_id: self
+                    .hash_from_obj_input("network_id", self.get_ledger_network_id()?)?,
+                contract_id,
+                salt,
+            },
+        ))
     }
 
     // metering: covered by components
-    pub fn id_preimage_from_asset(&self, asset: Asset) -> Result<Vec<u8>, HostError> {
-        let pre_image = HashIdPreimage::ContractIdFromAsset(asset);
-        let mut buf = Vec::new();
-        self.metered_write_xdr(&pre_image, &mut buf)?;
-        Ok(buf)
+    pub fn id_preimage_from_asset(&self, asset: Asset) -> Result<HashIdPreimage, HostError> {
+        Ok(HashIdPreimage::ContractIdFromAsset(
+            HashIdPreimageFromAsset {
+                network_id: self
+                    .hash_from_obj_input("network_id", self.get_ledger_network_id()?)?,
+                asset,
+            },
+        ))
     }
 
     // metering: covered by components
-    pub fn id_preimage_from_source_account(&self, salt: Uint256) -> Result<Vec<u8>, HostError> {
+    pub fn id_preimage_from_source_account(
+        &self,
+        salt: Uint256,
+    ) -> Result<HashIdPreimage, HostError> {
         if self.get_invoker_type()? != InvokerType::Account as u64 {
             return Err(self.err_general("invoker is not an account"));
         }
 
         let source_account = self.source_account()?;
-        let pre_image =
-            HashIdPreimage::ContractIdFromSourceAccount(HashIdPreimageSourceAccountContractId {
+        Ok(HashIdPreimage::ContractIdFromSourceAccount(
+            HashIdPreimageSourceAccountContractId {
+                network_id: self
+                    .hash_from_obj_input("network_id", self.get_ledger_network_id()?)?,
                 source_account,
                 salt,
-            });
-        let mut buf = Vec::new();
-        self.metered_write_xdr(&pre_image, &mut buf)?;
-        Ok(buf)
+            },
+        ))
+    }
+
+    // metering: covered by components
+    pub fn create_contract_args_hash_preimage(
+        &self,
+        source: ScContractCode,
+        salt: Uint256,
+    ) -> Result<HashIdPreimage, HostError> {
+        Ok(HashIdPreimage::CreateContractArgs(
+            HashIdPreimageCreateContractArgs {
+                network_id: self
+                    .hash_from_obj_input("network_id", self.get_ledger_network_id()?)?,
+                source,
+                salt,
+            },
+        ))
     }
 
     // notes on metering: `get` from storage is covered. Rest are free.
@@ -203,4 +238,14 @@ impl Host {
             Ok(0u8)
         }
     }
+
+    pub(crate) fn ledger_entry_from_data(data: LedgerEntryData) -> LedgerEntry {
+        LedgerEntry {
+            // This is modified to the appropriate value on the core side during
+            // commiting the ledger transaction.
+            last_modified_ledger_seq: 0,
+            data,
+            ext: LedgerEntryExt::V0,
+        }
+    }
 }
diff --git a/soroban-env-host/src/host/metered_clone.rs b/soroban-env-host/src/host/metered_clone.rs
index 212ad82a..2f0a1c5f 100644
--- a/soroban-env-host/src/host/metered_clone.rs
+++ b/soroban-env-host/src/host/metered_clone.rs
@@ -1,7 +1,7 @@
 use crate::{
     budget::{Budget, CostType},
     host::Events,
-    xdr::{AccountId, Hash, PublicKey, ScContractCode, Uint256},
+    xdr::{AccountId, Hash, PublicKey, ScContractCode, ScVec, Uint256},
     HostError,
 };
 
@@ -23,6 +23,13 @@ impl MeteredClone for Vec<u8> {
     }
 }
 
+impl<const N: usize> MeteredClone for [u8; N] {
+    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, self.0.len() as u64)?;
@@ -39,9 +46,17 @@ impl MeteredClone for Events {
 
 impl MeteredClone for ScContractCode {
     fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
-        if let ScContractCode::Wasm(c) = self {
-            budget.charge(CostType::BytesClone, c.len() as u64)?;
-        }
+        match self {
+            ScContractCode::WasmRef(h) => budget.charge(CostType::BytesClone, h.0.len() as u64)?,
+            ScContractCode::Token => (),
+        };
+        Ok(self.clone())
+    }
+}
+
+impl MeteredClone for ScVec {
+    fn metered_clone(&self, budget: &Budget) -> Result<Self, HostError> {
+        budget.charge(CostType::BytesClone, self.0.len() as u64)?;
         Ok(self.clone())
     }
 }
diff --git a/soroban-env-host/src/host/metered_cmp.rs b/soroban-env-host/src/host/metered_cmp.rs
index 9dabbe2f..d814a765 100644
--- a/soroban-env-host/src/host/metered_cmp.rs
+++ b/soroban-env-host/src/host/metered_cmp.rs
@@ -4,6 +4,7 @@ use soroban_env_common::xdr::{AccountId, PublicKey, ScContractCode, Uint256};
 
 use crate::{
     budget::{Budget, CostType},
+    host::Hash,
     host_object::HostObject,
     HostError,
 };
@@ -56,10 +57,17 @@ impl MeteredCmp for Uint256 {
     }
 }
 
+impl MeteredCmp for Hash {
+    fn metered_cmp(&self, other: &Self, budget: &Budget) -> Result<Ordering, HostError> {
+        budget.charge(CostType::BytesCmp, self.0.len() as u64)?;
+        Ok(self.cmp(other))
+    }
+}
+
 impl MeteredCmp for ScContractCode {
     fn metered_cmp(&self, other: &Self, budget: &Budget) -> Result<Ordering, HostError> {
         match (self, other) {
-            (ScContractCode::Wasm(a), ScContractCode::Wasm(b)) => a.metered_cmp(b, budget),
+            (ScContractCode::WasmRef(a), ScContractCode::WasmRef(b)) => a.metered_cmp(b, budget),
             _ => Ok(self.cmp(other)),
         }
     }
diff --git a/soroban-env-host/src/host/metered_xdr.rs b/soroban-env-host/src/host/metered_xdr.rs
index c81832db..426877ce 100644
--- a/soroban-env-host/src/host/metered_xdr.rs
+++ b/soroban-env-host/src/host/metered_xdr.rs
@@ -8,6 +8,8 @@ use crate::{
     Host, HostError,
 };
 
+use sha2::{Digest, Sha256};
+
 struct MeteredWrite<'a, W: Write> {
     host: &'a Host,
     w: &'a mut W,
@@ -49,6 +51,13 @@ impl Host {
         })
     }
 
+    pub(crate) fn metered_hash_xdr(&self, obj: &impl WriteXdr) -> Result<[u8; 32], HostError> {
+        let mut buf = vec![];
+        self.metered_write_xdr(obj, &mut buf)?;
+        self.charge_budget(CostType::ComputeSha256Hash, buf.len() as u64)?;
+        Ok(Sha256::digest(buf).try_into()?)
+    }
+
     pub(crate) fn metered_from_xdr<T: ReadXdr>(&self, bytes: &[u8]) -> Result<T, HostError> {
         self.charge_budget(CostType::ValDeser, bytes.len() as u64)?;
         T::from_xdr(bytes).map_err(|_| self.err_general("failed to read from xdr"))


Test patch:
diff --git a/soroban-env-host/src/native_contract/testutils.rs b/soroban-env-host/src/native_contract/testutils.rs
index f3081346..5c3c1009 100644
--- a/soroban-env-host/src/native_contract/testutils.rs
+++ b/soroban-env-host/src/native_contract/testutils.rs
@@ -2,10 +2,11 @@ use crate::{
     native_contract::token::public_types::{
         AccountSignatures, Ed25519Signature, Signature, SignaturePayload, SignaturePayloadV0,
     },
+    test::util::generate_bytes_array,
     Host, HostError,
 };
 use ed25519_dalek::{Keypair, Signer};
-use rand::{thread_rng, RngCore};
+use rand::thread_rng;
 use soroban_env_common::{
     xdr::{AccountId, PublicKey, Uint256},
     CheckedEnv,
@@ -42,9 +43,11 @@ pub(crate) fn generate_keypair() -> Keypair {
 }
 
 pub(crate) fn generate_bytes(host: &Host) -> BytesN<32> {
-    let mut bytes: [u8; 32] = Default::default();
-    thread_rng().fill_bytes(&mut bytes);
-    BytesN::<32>::try_from_val(host, host.bytes_new_from_slice(&bytes).unwrap()).unwrap()
+    BytesN::<32>::try_from_val(
+        host,
+        host.bytes_new_from_slice(&generate_bytes_array()).unwrap(),
+    )
+    .unwrap()
 }
 
 pub(crate) fn signer_to_id_bytes(host: &Host, key: &Keypair) -> BytesN<32> {
diff --git a/soroban-env-host/src/native_contract/token/test_token.rs b/soroban-env-host/src/native_contract/token/test_token.rs
index 82895988..09c88b1c 100644
--- a/soroban-env-host/src/native_contract/token/test_token.rs
+++ b/soroban-env-host/src/native_contract/token/test_token.rs
@@ -5,20 +5,19 @@ use crate::{
         testutils::{sign_args, HostVec, TestSigner},
         token::public_types::TokenMetadata,
     },
+    test::util::{generate_account_id, generate_bytes_array},
     Host, HostError,
 };
 use soroban_env_common::{
-    xdr::{Asset, ScContractCode, WriteXdr},
-    CheckedEnv,
+    xdr::{Asset, ContractId, CreateContractArgs, HostFunction, ScContractCode, Uint256},
+    CheckedEnv, RawVal,
 };
-use soroban_env_common::{EnvBase, Symbol, TryFromVal, TryIntoVal};
+use soroban_env_common::{Symbol, TryFromVal, TryIntoVal};
 
 use crate::native_contract::base_types::{Bytes, BytesN};
 
 use crate::native_contract::token::public_types::Identifier;
 
-use crate::native_contract::testutils::generate_bytes;
-
 pub(crate) struct TestToken<'a> {
     pub(crate) id: BytesN<32>,
     host: &'a Host,
@@ -26,22 +25,35 @@ pub(crate) struct TestToken<'a> {
 
 impl<'a> TestToken<'a> {
     pub(crate) fn new(host: &'a Host) -> Self {
-        let id = generate_bytes(host);
-        host.create_contract_with_id(ScContractCode::Token, id.clone().into())
+        host.set_source_account(generate_account_id());
+        let id_obj: RawVal = host
+            .invoke_function(HostFunction::CreateContract(CreateContractArgs {
+                contract_id: ContractId::SourceAccount(Uint256(generate_bytes_array())),
+                source: ScContractCode::Token,
+            }))
+            .unwrap()
+            .try_into_val(host)
             .unwrap();
-        Self { id, host }
+        host.remove_source_account();
+        Self {
+            id: BytesN::<32>::try_from_val(host, id_obj).unwrap(),
+            host,
+        }
     }
 
     pub(crate) fn new_from_asset(host: &'a Host, asset: Asset) -> Self {
-        let id = BytesN::<32>::try_from_val(
+        let id_obj: RawVal = host
+            .invoke_function(HostFunction::CreateContract(CreateContractArgs {
+                contract_id: ContractId::Asset(asset),
+                source: ScContractCode::Token,
+            }))
+            .unwrap()
+            .try_into_val(host)
+            .unwrap();
+        Self {
+            id: BytesN::<32>::try_from_val(host, id_obj).unwrap(),
             host,
-            host.create_token_from_asset(
-                host.bytes_new_from_slice(&asset.to_xdr().unwrap()).unwrap(),
-            )
-            .unwrap(),
-        )
-        .unwrap();
-        Self { id, host }
+        }
     }
 
     pub(crate) fn init(&self, admin: Identifier, metadata: TokenMetadata) -> Result<(), HostError> {
diff --git a/soroban-env-host/src/test.rs b/soroban-env-host/src/test.rs
index 94f7c2da..c75c8db4 100644
--- a/soroban-env-host/src/test.rs
+++ b/soroban-env-host/src/test.rs
@@ -14,15 +14,12 @@ mod vec;
 mod budget_metering;
 #[cfg(feature = "vm")]
 mod complex;
-#[cfg(any(test, feature = "testutils"))]
 mod contract_event;
 #[cfg(feature = "vm")]
 mod hostile;
 #[cfg(feature = "vm")]
 mod invocation;
-#[cfg(all(feature = "vm", feature = "testutils"))]
+#[cfg(feature = "vm")]
 mod lifecycle;
-mod tuple;
-
-#[cfg(any(test, feature = "testutils"))]
 mod token;
+mod tuple;
diff --git a/soroban-env-host/src/test/budget_metering.rs b/soroban-env-host/src/test/budget_metering.rs
index 4f9d50ea..47e4c709 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::{Budget, CostType},
+    budget::CostType,
     xdr::{ScMap, ScMapEntry, ScObject, ScVal, ScVmErrorCode},
     CheckedEnv, Host, HostError, RawVal, Symbol,
 };
@@ -36,23 +36,20 @@ fn xdr_object_conversion() -> Result<(), HostError> {
 
 #[test]
 fn vm_hostfn_invocation() -> Result<(), HostError> {
-    let dummy_id = [0; 32];
-    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 host = Host::test_host_with_recording_footprint();
+    let id_obj = host.register_test_contract_wasm(VEC)?;
+    let host = host
         .test_budget(100_000, 100_000)
         .enable_model(CostType::InvokeVmFunction)
         .enable_model(CostType::InvokeHostFunction);
 
-    let obj = host.test_bin_obj(&dummy_id)?;
     // `vec_err` is a test contract function which calls `vec_new` (1 call)
     // and `vec_put` (1 call) so total input of 2 to the budget from `CostType::InvokeHostFunction`.
     let sym = Symbol::from_str("vec_err");
     let args = host.test_vec_obj::<u32>(&[1])?;
 
     // try_call
-    host.try_call(obj.to_object(), sym.into(), args.clone().into())?;
+    host.try_call(id_obj, sym.into(), args.clone().into())?;
     host.with_budget(|budget| {
         assert_eq!(budget.get_input(CostType::InvokeVmFunction), 1);
         assert_eq!(budget.get_input(CostType::InvokeHostFunction), 2);
diff --git a/soroban-env-host/src/test/bytes.rs b/soroban-env-host/src/test/bytes.rs
index 69403f1c..91527d23 100644
--- a/soroban-env-host/src/test/bytes.rs
+++ b/soroban-env-host/src/test/bytes.rs
@@ -6,15 +6,7 @@ use crate::{
 use soroban_env_common::EnvBase;
 
 #[cfg(feature = "vm")]
-use crate::{
-    im_rc::OrdMap,
-    storage::{AccessType, Footprint, Storage},
-    xdr::{
-        ContractDataEntry, Hash, LedgerEntry, LedgerEntryData, LedgerEntryExt, LedgerKey,
-        LedgerKeyContractData,
-    },
-    Object, Symbol,
-};
+use crate::{Object, Symbol};
 #[cfg(feature = "vm")]
 use soroban_test_wasms::LINEAR_MEMORY;
 
@@ -167,50 +159,12 @@ fn bytes_xdr_roundtrip() -> Result<(), HostError> {
 #[cfg(feature = "vm")]
 #[test]
 fn linear_memory_operations() -> 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 {
-        contract_id: contract_id.clone(),
-        key: key.clone(),
-    });
-    let val = ScVal::Object(Some(ScObject::ContractCode(ScContractCode::Wasm(
-        LINEAR_MEMORY.try_into().unwrap(),
-    ))));
-    let le = LedgerEntry {
-        last_modified_ledger_seq: 0,
-        data: LedgerEntryData::ContractData(ContractDataEntry {
-            contract_id,
-            key,
-            val,
-        }),
-        ext: LedgerEntryExt::V0,
-    };
-    let map = OrdMap::unit(Box::new(storage_key.clone()), Some(Box::new(le)));
-    let mut footprint = Footprint::default();
-    footprint.record_access(&storage_key, AccessType::ReadOnly)?;
-
-    // initialize storage and host
-    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])?;
+    let host = Host::test_host_with_recording_footprint();
+    let id_obj = host.register_test_contract_wasm(LINEAR_MEMORY)?;
     // tests bytes_new_from_linear_memory
     {
         let args = host.test_vec_obj::<u32>(&[0xaabbccdd])?;
-        let obj = host.call(
-            id_obj.to_object(),
-            Symbol::from_str("bin_word").into(),
-            args.into(),
-        )?;
+        let obj = host.call(id_obj, Symbol::from_str("bin_word").into(), args.into())?;
         let obj_ref = host.test_bin_obj(&[0xaa, 0xbb, 0xcc, 0xdd])?;
         assert_eq!(host.obj_cmp(obj.into(), obj_ref.into())?, 0);
     }
@@ -220,11 +174,7 @@ fn linear_memory_operations() -> Result<(), HostError> {
         let mut args = host.vec_new(RawVal::from_void())?;
         args = host.vec_push_back(args, obj0.to_raw())?;
         let obj: Object = host
-            .call(
-                id_obj.to_object(),
-                Symbol::from_str("bin_inc").into(),
-                args.into(),
-            )?
+            .call(id_obj, Symbol::from_str("bin_inc").into(), args.into())?
             .try_into()?;
         let obj_ref = host.test_bin_obj(&[2, 3, 4, 5])?;
         assert_eq!(host.obj_cmp(obj.into(), obj_ref.into())?, 0);
diff --git a/soroban-env-host/src/test/invocation.rs b/soroban-env-host/src/test/invocation.rs
index ece0a906..f8fd00d3 100644
--- a/soroban-env-host/src/test/invocation.rs
+++ b/soroban-env-host/src/test/invocation.rs
@@ -4,7 +4,6 @@ use soroban_env_common::{
 };
 
 use crate::{
-    budget::Budget,
     events::{DebugArg, HostEvent},
     vm::Vm,
     xdr::{Hash, ScHostObjErrorCode, ScStatusType, ScVal, ScVec},
@@ -38,16 +37,12 @@ fn invoke_single_contract_function() -> Result<(), HostError> {
 
 #[test]
 fn invoke_cross_contract() -> Result<(), HostError> {
-    let dummy_id = [0; 32];
-    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)?;
+    let host = Host::test_host_with_recording_footprint();
+    let id_obj = host.register_test_contract_wasm(ADD_I32)?;
     // prepare arguments
     let sym = Symbol::from_str("add");
     let args = host.test_vec_obj::<i32>(&[1, 2])?;
-    let res = host.call(obj.to_object(), sym.into(), args.into())?;
+    let res = host.call(id_obj, sym.into(), args.into())?;
     assert!(res.is::<i32>());
     assert!(res.get_tag() == Tag::I32);
     let i: i32 = res.try_into()?;
@@ -57,19 +52,14 @@ fn invoke_cross_contract() -> Result<(), HostError> {
 
 #[test]
 fn invoke_cross_contract_with_err() -> Result<(), HostError> {
-    let dummy_id = [0; 32];
-    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)?;
+    let host = Host::test_host_with_recording_footprint();
+    let id_obj = host.register_test_contract_wasm(VEC)?;
     // prepare arguments
     let sym = Symbol::from_str("vec_err");
     let args = host.test_vec_obj::<u32>(&[1])?;
 
     // try_call
-    let sv = host.try_call(obj.to_object(), sym.into(), args.clone().into())?;
+    let sv = host.try_call(id_obj, sym.into(), args.clone().into())?;
     let code = ScHostObjErrorCode::VecIndexOutOfBound;
     let exp_st: Status = code.into();
     assert_eq!(sv.get_payload(), exp_st.to_raw().get_payload());
@@ -103,7 +93,7 @@ fn invoke_cross_contract_with_err() -> Result<(), HostError> {
     };
 
     // call
-    let res = host.call(obj.to_object(), sym.into(), args.into());
+    let res = host.call(id_obj, sym.into(), args.into());
     assert!(HostError::result_matches_err_status(res, code));
 
     let events = host.get_events()?;
@@ -139,23 +129,15 @@ fn invoke_cross_contract_with_err() -> Result<(), HostError> {
 
 #[test]
 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_and_budget(storage, budget);
+    let host = Host::test_host_with_recording_footprint();
+    let id0_obj = host.register_test_contract_wasm(INVOKE_CONTRACT)?;
+    let id1_obj = host.register_test_contract_wasm(ADD_I32)?;
     // prepare arguments
-    let id0_obj = host.test_bin_obj(&dummy_id0)?;
-    let id1_obj = host.test_bin_obj(&dummy_id1)?;
     let sym = Symbol::from_str("add_with");
     let args = host.test_vec_obj::<i32>(&[5, 6])?;
-    let args = host.vec_push_back(args.val, id1_obj.into())?;
+    let args = host.vec_push_back(args.val, id1_obj.to_raw())?;
     // try call
-    let val = host.call(id0_obj.to_object(), sym.into(), args.clone().into())?;
+    let val = host.call(id0_obj, sym.into(), args.clone().into())?;
     let exp: RawVal = 11i32.into();
     assert_eq!(val.get_payload(), exp.get_payload());
     Ok(())
@@ -163,24 +145,15 @@ 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 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_and_budget(storage, budget);
-    // prepare arguments
-    let id0_obj = host.test_bin_obj(&dummy_id0)?;
-    let id1_obj = host.test_bin_obj(&dummy_id1)?;
+    let host = Host::test_host_with_recording_footprint();
+    let id0_obj = host.register_test_contract_wasm(INVOKE_CONTRACT)?;
+    let id1_obj = host.register_test_contract_wasm(ADD_I32)?;
     let sym = Symbol::from_str("add_with");
     let args = host.test_vec_obj::<i32>(&[i32::MAX, 1])?;
     let args = host.vec_push_back(args.val, id1_obj.into())?;
 
     // try call -- add will trap, and add_with will trap, but we will get a status
-    let status = host.try_call(id0_obj.to_object(), sym.into(), args.clone().into())?;
+    let status = host.try_call(id0_obj, sym.into(), args.clone().into())?;
     let code = ScVmErrorCode::TrapUnreachable;
     let exp: Status = code.into();
     assert_eq!(status.get_payload(), exp.to_raw().get_payload());
@@ -214,7 +187,7 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {
     };
 
     // call
-    let res = host.call(id0_obj.to_object(), sym.into(), args.clone().into());
+    let res = host.call(id0_obj, sym.into(), args.clone().into());
     assert!(HostError::result_matches_err_status(res, code));
 
     let events = host.get_events()?;
@@ -250,23 +223,16 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {
 
 #[test]
 fn invoke_contract_with_reentry() -> Result<(), HostError> {
-    let dummy_id0 = [0; 32]; // the calling contract
-    let budget = Budget::default();
-    let storage = Host::test_storage_with_contracts(
-        vec![dummy_id0.into()],
-        vec![INVOKE_CONTRACT],
-        budget.clone(),
-    );
-    let host = Host::with_storage_and_budget(storage, budget);
+    let host = Host::test_host_with_recording_footprint();
+    let id0_obj = host.register_test_contract_wasm(INVOKE_CONTRACT)?;
     // prepare arguments
-    let id0_obj = host.test_bin_obj(&dummy_id0)?;
     let sym = Symbol::from_str("add_with");
     let args = host.test_vec_obj::<i32>(&[i32::MAX, 1])?;
     let args = host.vec_push_back(args.val, id0_obj.clone().into())?; // trying to call its own `add` function
 
     // try call -- add will trap, and add_with will trap, but we will get a status
-    let res = host.call(id0_obj.clone().to_object(), sym.into(), args.clone().into());
-    let status = host.try_call(id0_obj.clone().to_object(), sym.into(), args.clone().into())?;
+    let res = host.call(id0_obj.clone(), sym.into(), args.clone().into());
+    let status = host.try_call(id0_obj, sym.into(), args.clone().into())?;
     let code = ScHostContextErrorCode::UnknownError;
     let exp: Status = code.into();
     assert!(HostError::result_matches_err_status(res, code));
diff --git a/soroban-env-host/src/test/lifecycle.rs b/soroban-env-host/src/test/lifecycle.rs
index 89db88df..49384b9b 100644
--- a/soroban-env-host/src/test/lifecycle.rs
+++ b/soroban-env-host/src/test/lifecycle.rs
@@ -1,258 +1,244 @@
 use crate::{
     budget::Budget,
     host::metered_map::MeteredOrdMap,
-    host::Frame,
     storage::{AccessType, Footprint, Storage},
     xdr::{
-        self, AccountId, HostFunction, LedgerEntryData, LedgerKey, LedgerKeyContractData,
-        ScContractCode, ScObject, ScStatic, ScVal, ScVec,
+        self, ContractId, CreateContractArgs, Hash, HashIdPreimage, HashIdPreimageContractId,
+        HashIdPreimageSourceAccountContractId, HostFunction, InstallContractCodeArgs,
+        LedgerEntryData, ScContractCode, ScObject, ScVal, ScVec, Uint256,
     },
-    CheckedEnv, Host, HostError, RawVal, Symbol,
+    CheckedEnv, Host, LedgerInfo, Symbol,
 };
-use hex::FromHex;
+use soroban_env_common::{RawVal, TryIntoVal};
 use soroban_test_wasms::CREATE_CONTRACT;
 
 use im_rc::OrdMap;
 use sha2::{Digest, Sha256};
 
-pub(crate) fn check_new_code(host: &Host, storage_key: LedgerKey, code: ScVal) {
+use super::util::{generate_account_id, generate_bytes_array};
+
+fn get_contract_wasm_ref(host: &Host, contract_id: Hash) -> Hash {
+    let storage_key = host.contract_source_ledger_key(contract_id);
     host.with_mut_storage(|s: &mut Storage| {
-        assert!(s.has(&storage_key)?);
+        assert!(s.has(&storage_key).unwrap());
 
-        match s.get(&storage_key)?.data {
-            LedgerEntryData::ContractData(cde) => assert_eq!(cde.val, code),
+        match s.get(&storage_key).unwrap().data {
+            LedgerEntryData::ContractData(cde) => match cde.val {
+                ScVal::Object(Some(ScObject::ContractCode(ScContractCode::WasmRef(h)))) => Ok(h),
+                _ => panic!("expected ScContractCode"),
+            },
             _ => panic!("expected contract data"),
-        };
-        Ok(())
+        }
     })
-    .unwrap();
+    .unwrap()
 }
 
-/// create contract tests
-fn create_contract_test_helper(salt: &[u8], code: &[u8]) -> Result<Host, HostError> {
-    let source_account = AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256([0; 32])));
-    let salt_bytes: Vec<u8> = FromHex::from_hex(salt).unwrap();
-
-    // Make contractID so we can include it in the footprint
-    let pre_image = xdr::HashIdPreimage::ContractIdFromSourceAccount(
-        xdr::HashIdPreimageSourceAccountContractId {
-            source_account: source_account.clone(),
-            salt: xdr::Uint256(salt_bytes.as_slice().try_into().unwrap()),
-        },
-    );
-
-    let hash = sha256_hash_id_preimage(pre_image);
+fn get_contract_wasm(host: &Host, wasm_hash: Hash) -> Vec<u8> {
+    let storage_key = host.contract_code_ledger_key(wasm_hash);
+    host.with_mut_storage(|s: &mut Storage| {
+        assert!(s.has(&storage_key).unwrap());
 
-    let hash_copy = hash.clone();
-    let key = ScVal::Static(ScStatic::LedgerKeyContractCode);
-    let storage_key = LedgerKey::ContractData(LedgerKeyContractData {
-        contract_id: hash,
-        key,
-    });
+        match s.get(&storage_key).unwrap().data {
+            LedgerEntryData::ContractCode(code_entry) => Ok(code_entry.code.to_vec()),
+            _ => panic!("expected contract WASM code"),
+        }
+    })
+    .unwrap()
+}
 
-    let mut footprint = Footprint::default();
-    footprint.record_access(&storage_key, AccessType::ReadWrite)?;
+fn get_bytes_from_sc_val(val: ScVal) -> Vec<u8> {
+    match val {
+        ScVal::Object(Some(scobj)) => match scobj {
+            ScObject::Bytes(bytes) => bytes.to_vec(),
+            _ => panic!("Wrong type"),
+        },
+        _ => panic!("Wrong type"),
+    }
+}
 
-    // Initialize storage and host
+fn test_host() -> Host {
+    let network_passphrase = generate_bytes_array().to_vec();
     let budget = Budget::default();
     let storage = Storage::with_enforcing_footprint_and_map(
-        footprint,
+        Footprint::default(),
         MeteredOrdMap {
             map: OrdMap::new(),
             budget: budget.clone(),
         },
     );
     let host = Host::with_storage_and_budget(storage, budget);
+    host.set_ledger_info(LedgerInfo {
+        network_passphrase,
+        ..Default::default()
+    });
 
-    host.set_source_account(source_account);
+    host
+}
 
-    // Create contract
-    let obj_code = host.test_bin_obj(&code)?;
-    let obj_salt = host.test_bin_obj(&salt_bytes)?;
-    let contract_id = host
-        .with_frame(
-            Frame::HostFunction(HostFunction::CreateContractWithSourceAccount),
-            || {
-                host.create_contract_from_source_account(obj_code.to_object(), obj_salt.to_object())
-                    .map(|o| o.to_raw())
-            },
-        )
-        .unwrap();
+fn test_create_contract_from_source_account(host: &Host, code: &[u8]) -> Hash {
+    let source_account = generate_account_id();
+    let salt = generate_bytes_array();
+    host.set_source_account(source_account.clone());
+    // Make contractID so we can include it in the footprint
+    let id_pre_image =
+        HashIdPreimage::ContractIdFromSourceAccount(HashIdPreimageSourceAccountContractId {
+            source_account: source_account,
+            salt: Uint256(salt.to_vec().try_into().unwrap()),
+            network_id: host
+                .hash_from_obj_input("network_id", host.get_ledger_network_id().unwrap())
+                .unwrap(),
+        });
 
-    let v = host.from_host_val(contract_id)?;
-    let bytes = match v {
-        ScVal::Object(Some(scobj)) => match scobj {
-            ScObject::Bytes(bytes) => bytes,
-            _ => panic!("Wrong type"),
-        },
-        _ => panic!("Wrong type"),
+    let contract_id = sha256_hash_id_preimage(id_pre_image);
+
+    let install_args = xdr::InstallContractCodeArgs {
+        code: code.to_vec().try_into().unwrap(),
     };
 
-    if bytes.as_slice() != hash_copy.0.as_slice() {
-        panic!("return value doesn't match")
-    }
+    let wasm_hash = sha256_hash_id_preimage(install_args.clone());
+
+    host.with_mut_storage(|s: &mut Storage| {
+        s.footprint
+            .record_access(
+                &host.contract_source_ledger_key(contract_id.clone()),
+                AccessType::ReadWrite,
+            )
+            .unwrap();
+        s.footprint
+            .record_access(
+                &host.contract_code_ledger_key(wasm_hash.clone()),
+                AccessType::ReadWrite,
+            )
+            .unwrap();
+        Ok(())
+    })
+    .unwrap();
+
+    // Create contract
+    let wasm_id: RawVal = host
+        .invoke_function(HostFunction::InstallContractCode(install_args.clone()))
+        .unwrap()
+        .try_into_val(host)
+        .unwrap();
+    let wasm_id = host
+        .hash_from_obj_input("wasm_hash", wasm_id.try_into().unwrap())
+        .unwrap();
+    let created_id_sc_val = host
+        .invoke_function(HostFunction::CreateContract(CreateContractArgs {
+            contract_id: ContractId::SourceAccount(Uint256(salt.to_vec().try_into().unwrap())),
+            source: ScContractCode::WasmRef(wasm_id),
+        }))
+        .unwrap();
 
-    check_new_code(
-        &host,
-        storage_key,
-        ScVal::Object(Some(ScObject::ContractCode(ScContractCode::Wasm(
-            code.try_into().unwrap(),
-        )))),
+    assert_eq!(
+        contract_id.as_slice(),
+        get_bytes_from_sc_val(created_id_sc_val).as_slice()
     );
+    assert_eq!(
+        wasm_hash.as_slice(),
+        get_contract_wasm_ref(&host, contract_id.clone()).as_slice()
+    );
+    assert_eq!(code, get_contract_wasm(&host, wasm_hash));
 
-    Ok(host)
+    contract_id
 }
 
-/// VM tests
+// VM tests
 #[test]
 fn create_contract_using_parent_id_test() {
-    let salt: &[u8] = b"3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c";
-    let salt_bytes: Vec<u8> = FromHex::from_hex(salt).unwrap();
-
-    let host = create_contract_test_helper(salt, CREATE_CONTRACT).unwrap();
-
-    // Get parent contractID
-    let pre_image = xdr::HashIdPreimage::ContractIdFromSourceAccount(
-        xdr::HashIdPreimageSourceAccountContractId {
-            source_account: host.source_account().unwrap(),
-            salt: xdr::Uint256(salt_bytes.as_slice().try_into().unwrap()),
-        },
-    );
-
-    let parent_id = sha256_hash_id_preimage(pre_image);
-
-    //Put child contract that will be created into the footprint
-    //Use the same salt
-    let child_pre_image =
-        xdr::HashIdPreimage::ContractIdFromContract(xdr::HashIdPreimageContractId {
-            contract_id: parent_id.clone(),
-            salt: xdr::Uint256(salt_bytes.as_slice().try_into().unwrap()),
-        });
+    let host = test_host();
+    let parent_contract_id = test_create_contract_from_source_account(&host, CREATE_CONTRACT);
+    let salt = generate_bytes_array();
+    let child_pre_image = HashIdPreimage::ContractIdFromContract(HashIdPreimageContractId {
+        contract_id: parent_contract_id.clone(),
+        salt: Uint256(salt.clone()),
+        network_id: host
+            .hash_from_obj_input("network_id", host.get_ledger_network_id().unwrap())
+            .unwrap(),
+    });
 
     let child_id = sha256_hash_id_preimage(child_pre_image);
+    let child_wasm: &[u8] = b"70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4";
+    let install_args = xdr::InstallContractCodeArgs {
+        code: child_wasm.to_vec().try_into().unwrap(),
+    };
 
-    let child_storage_key = LedgerKey::ContractData(LedgerKeyContractData {
-        contract_id: child_id,
-        key: ScVal::Static(ScStatic::LedgerKeyContractCode),
-    });
-
+    // Install the code for the child contract.
+    let wasm_hash = sha256_hash_id_preimage(install_args.clone());
+    // Add the contract code and code reference access to the footprint.
     host.with_mut_storage(|s: &mut Storage| {
         s.footprint
-            .record_access(&child_storage_key, AccessType::ReadWrite)
+            .record_access(
+                &host.contract_source_ledger_key(child_id.clone()),
+                AccessType::ReadWrite,
+            )
+            .unwrap();
+        s.footprint
+            .record_access(
+                &host.contract_code_ledger_key(wasm_hash.clone()),
+                AccessType::ReadWrite,
+            )
             .unwrap();
         Ok(())
     })
     .unwrap();
 
-    // prepare arguments
-    let child_code: &[u8] = b"70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4";
-    let code_val = ScVal::Object(Some(ScObject::Bytes(child_code.try_into().unwrap())));
-
-    let sym = Symbol::from_str("create");
-    let salt_bytes: Vec<u8> = FromHex::from_hex(salt).unwrap();
-    let scvec0: ScVec = vec![
-        code_val.clone(),
-        ScVal::Object(Some(ScObject::Bytes(salt_bytes.try_into().unwrap()))),
+    // Prepare arguments for the factory contract call.
+    let args_scvec: ScVec = vec![
+        ScVal::Object(Some(ScObject::Bytes(wasm_hash.0.try_into().unwrap()))),
+        ScVal::Object(Some(ScObject::Bytes(salt.try_into().unwrap()))),
     ]
     .try_into()
     .unwrap();
-    let args = host.to_host_obj(&ScObject::Vec(scvec0)).unwrap();
+    let args = host.to_host_obj(&ScObject::Vec(args_scvec)).unwrap();
+    // Can't create the contract yet, as the code hasn't been installed yet.
+    assert!(host
+        .call(
+            host.test_bin_obj(&parent_contract_id.0)
+                .unwrap()
+                .try_into()
+                .unwrap(),
+            Symbol::from_str("create").into(),
+            args.clone().into(),
+        )
+        .is_err());
 
-    let p_id_sobj = ScObject::Bytes(parent_id.0.try_into().unwrap());
-    let p_id_obj = host.to_host_obj(&p_id_sobj).unwrap();
-    host.call(p_id_obj.to_object(), sym.into(), args.into())
+    // Install the code of the child contract.
+    let wasm_hash_sc_val = host
+        .invoke_function(HostFunction::InstallContractCode(InstallContractCodeArgs {
+            code: child_wasm.try_into().unwrap(),
+        }))
         .unwrap();
+    assert_eq!(
+        wasm_hash.as_slice(),
+        get_bytes_from_sc_val(wasm_hash_sc_val).as_slice()
+    );
+    assert_eq!(child_wasm, get_contract_wasm(&host, wasm_hash.clone()));
+
+    // Now successfully create the child contract itself.
+    host.call(
+        host.test_bin_obj(&parent_contract_id.0)
+            .unwrap()
+            .try_into()
+            .unwrap(),
+        Symbol::from_str("create").into(),
+        args.into(),
+    )
+    .unwrap();
 
-    //Validate child contract exists and code is what we expected
-    check_new_code(
-        &host,
-        child_storage_key,
-        ScVal::Object(Some(ScObject::ContractCode(ScContractCode::Wasm(
-            child_code.try_into().unwrap(),
-        )))),
+    assert_eq!(
+        wasm_hash.as_slice(),
+        get_contract_wasm_ref(&host, child_id).as_slice()
     );
 }
 
 #[test]
-fn create_contract_from_source_account() -> Result<(), HostError> {
+fn create_contract_from_source_account() {
     let code: &[u8] = b"70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4";
-    let public_key: &[u8] = b"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a";
-    let salt: &[u8] = b"3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c";
-    let pub_bytes: Vec<u8> = FromHex::from_hex(public_key).unwrap();
-    let salt_bytes: Vec<u8> = FromHex::from_hex(salt).unwrap();
-    let source_account = AccountId(xdr::PublicKey::PublicKeyTypeEd25519(xdr::Uint256(
-        pub_bytes.as_slice().try_into().unwrap(),
-    )));
-
-    let pre_image = xdr::HashIdPreimage::ContractIdFromSourceAccount(
-        xdr::HashIdPreimageSourceAccountContractId {
-            source_account: source_account.clone(),
-            salt: xdr::Uint256(salt_bytes.as_slice().try_into().unwrap()),
-        },
-    );
-
-    let hash = sha256_hash_id_preimage(pre_image);
-
-    let hash_copy = hash.clone();
-    let key = ScVal::Static(ScStatic::LedgerKeyContractCode);
-    let storage_key = LedgerKey::ContractData(LedgerKeyContractData {
-        contract_id: hash,
-        key,
-    });
-
-    let mut footprint = Footprint::default();
-    footprint.record_access(&storage_key, AccessType::ReadWrite)?;
-
-    // Initialize storage and host
-    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);
-    host.set_source_account(source_account);
-
-    let obj_code = host.test_bin_obj(&code)?;
-    let obj_salt = host.test_bin_obj(&salt_bytes)?;
-
-    host.with_frame(
-        Frame::HostFunction(HostFunction::CreateContractWithSourceAccount),
-        || {
-            let contract_id = host
-                .create_contract_from_source_account(obj_code.to_object(), obj_salt.to_object())?;
-
-            let v = host.from_host_val(contract_id.to_raw())?;
-            let bytes = match v {
-                ScVal::Object(Some(scobj)) => match scobj {
-                    ScObject::Bytes(bytes) => bytes,
-                    _ => panic!("Wrong type"),
-                },
-                _ => panic!("Wrong type"),
-            };
-
-            if bytes.as_slice() != hash_copy.0.as_slice() {
-                panic!("return value doesn't match")
-            }
-
-            check_new_code(
-                &host,
-                storage_key,
-                ScVal::Object(Some(ScObject::ContractCode(ScContractCode::Wasm(
-                    code.try_into().unwrap(),
-                )))),
-            );
-            Ok(RawVal::from_void())
-        },
-    )?;
-    Ok(())
+    test_create_contract_from_source_account(&test_host(), code);
 }
 
-pub(crate) fn sha256_hash_id_preimage(pre_image: xdr::HashIdPreimage) -> xdr::Hash {
-    use xdr::WriteXdr;
-
+pub(crate) fn sha256_hash_id_preimage<T: xdr::WriteXdr>(pre_image: T) -> xdr::Hash {
     let mut buf = Vec::new();
     pre_image
         .write_xdr(&mut buf)
diff --git a/soroban-env-host/src/test/token.rs b/soroban-env-host/src/test/token.rs
index 9cd06042..c3d1d116 100644
--- a/soroban-env-host/src/test/token.rs
+++ b/soroban-env-host/src/test/token.rs
@@ -24,10 +24,9 @@ use soroban_env_common::{
     xdr::{
         AccountEntry, AccountEntryExt, AccountEntryExtensionV1, AccountEntryExtensionV1Ext,
         AccountEntryExtensionV2, AccountEntryExtensionV2Ext, AccountId, AlphaNum12, AlphaNum4,
-        Asset, AssetCode12, AssetCode4, Hash, HostFunction, LedgerEntry, LedgerEntryData,
-        LedgerEntryExt, LedgerKey, Liabilities, PublicKey, ScStatusType, SequenceNumber, SignerKey,
-        Thresholds, TrustLineEntry, TrustLineEntryExt, TrustLineEntryV1, TrustLineEntryV1Ext,
-        TrustLineFlags,
+        Asset, AssetCode12, AssetCode4, Hash, HostFunctionType, LedgerEntryData, LedgerKey,
+        Liabilities, PublicKey, ScStatusType, SequenceNumber, SignerKey, Thresholds,
+        TrustLineEntry, TrustLineEntryExt, TrustLineEntryV1, TrustLineEntryV1Ext, TrustLineFlags,
     },
     RawVal,
 };
@@ -167,11 +166,7 @@ impl TokenTest {
         self.host
             .add_ledger_entry(
                 key,
-                LedgerEntry {
-                    last_modified_ledger_seq: 0,
-                    data: LedgerEntryData::Account(acc_entry),
-                    ext: LedgerEntryExt::V0,
-                },
+                Host::ledger_entry_from_data(LedgerEntryData::Account(acc_entry)),
             )
             .unwrap();
     }
@@ -229,11 +224,7 @@ impl TokenTest {
         self.host
             .add_ledger_entry(
                 key.clone(),
-                LedgerEntry {
-                    last_modified_ledger_seq: 0,
-                    data: LedgerEntryData::Trustline(trustline_entry),
-                    ext: LedgerEntryExt::V0,
-                },
+                Host::ledger_entry_from_data(LedgerEntryData::Trustline(trustline_entry)),
             )
             .unwrap();
 
@@ -274,14 +265,16 @@ impl TokenTest {
         F: FnOnce() -> Result<T, HostError>,
     {
         self.host.set_source_account(account_id);
-        self.host
-            .with_frame(Frame::HostFunction(HostFunction::InvokeContract), || {
+        self.host.with_frame(
+            Frame::HostFunction(HostFunctionType::InvokeContract),
+            || {
                 let res = f();
                 match res {
                     Ok(v) => Ok(v.into()),
                     Err(e) => Err(e),
                 }
-            })
+            },
+        )
     }
 }
 
diff --git a/soroban-env-host/src/test/util.rs b/soroban-env-host/src/test/util.rs
index d156ec46..ed581452 100644
--- a/soroban-env-host/src/test/util.rs
+++ b/soroban-env-host/src/test/util.rs
@@ -1,16 +1,20 @@
+use std::rc::Rc;
+
+use rand::{thread_rng, RngCore};
+use soroban_env_common::{
+    xdr::{
+        AccountEntry, AccountId, ContractId, CreateContractArgs, HostFunction,
+        InstallContractCodeArgs, LedgerEntry, LedgerEntryData, LedgerKey, PublicKey,
+        ScContractCode, ScObject, ScVal, ScVec, Uint256,
+    },
+    Object, RawVal, TryIntoVal,
+};
+
 use crate::{
     budget::{Budget, CostType},
-    host::metered_map::MeteredOrdMap,
     host_object::{HostObj, HostVal},
-    im_rc::OrdMap,
-    storage::{AccessType, Footprint, Storage},
-    xdr,
-    xdr::{
-        AccountEntry, AccountId, ContractDataEntry, Hash, LedgerEntry, LedgerEntryData,
-        LedgerEntryExt, LedgerKey, LedgerKeyContractData, ScContractCode, ScObject, ScStatic,
-        ScVal, ScVec,
-    },
-    Host, HostError,
+    storage::{test_storage::MockSnapshotSource, Storage},
+    xdr, Host, HostError, LedgerInfo,
 };
 
 // Test utilities for the host, used in various tests in sub-modules.
@@ -30,12 +34,32 @@ impl AsScVal for i32 {
     }
 }
 
+pub(crate) fn generate_account_id() -> AccountId {
+    AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(
+        generate_bytes_array(),
+    )))
+}
+
+pub(crate) fn generate_bytes_array() -> [u8; 32] {
+    let mut bytes: [u8; 32] = Default::default();
+    thread_rng().fill_bytes(&mut bytes);
+    bytes
+}
+
 #[allow(dead_code)]
 impl Host {
     pub(crate) fn test_host() -> Self {
         Host::default()
     }
 
+    pub(crate) fn test_host_with_recording_footprint() -> Self {
+        let snapshot_source = Rc::<MockSnapshotSource>::new(MockSnapshotSource::new());
+        let storage = Storage::with_recording_footprint(snapshot_source);
+        let host = Host::with_storage_and_budget(storage, Budget::default());
+        host.set_ledger_info(Default::default());
+        host
+    }
+
     pub(crate) fn test_budget(self, cpu: u64, mem: u64) -> Self {
         self.with_budget(|budget| {
             budget.reset_limits(cpu, mem); // something big but finite that we may exceed
@@ -62,41 +86,6 @@ impl Host {
         self
     }
 
-    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();
-        let mut map = OrdMap::default();
-        for (id, contract) in it {
-            let key = ScVal::Static(ScStatic::LedgerKeyContractCode);
-            let storage_key = LedgerKey::ContractData(LedgerKeyContractData {
-                contract_id: id.clone(),
-                key: key.clone(),
-            });
-            // We unwrap here rather than host.map_err because the host doesn't exist yet.
-            let val = ScVal::Object(Some(ScObject::ContractCode(ScContractCode::Wasm(
-                (*contract).try_into().unwrap(),
-            ))));
-            let le = LedgerEntry {
-                last_modified_ledger_seq: 0,
-                data: LedgerEntryData::ContractData(ContractDataEntry {
-                    contract_id: id.clone(),
-                    key,
-                    val,
-                }),
-                ext: LedgerEntryExt::V0,
-            };
-            map.insert(Box::new(storage_key.clone()), Some(Box::new(le)));
-            footprint
-                .record_access(&storage_key, AccessType::ReadOnly)
-                .unwrap();
-        }
-        Storage::with_enforcing_footprint_and_map(footprint, MeteredOrdMap { budget, map })
-    }
-
     pub(crate) fn test_account_ledger_key_entry_pair(
         account_id: AccountId,
     ) -> (LedgerKey, LedgerEntry) {
@@ -115,12 +104,10 @@ impl Host {
             signers: Default::default(),
             ext: xdr::AccountEntryExt::V0,
         };
-        let le = LedgerEntry {
-            last_modified_ledger_seq: 0,
-            data: LedgerEntryData::Account(account_entry),
-            ext: LedgerEntryExt::V0,
-        };
-        (lk, le)
+        (
+            lk,
+            Host::ledger_entry_from_data(LedgerEntryData::Account(account_entry)),
+        )
     }
 
     pub(crate) fn test_scvec<T: AsScVal>(&self, vals: &[T]) -> Result<ScVec, HostError> {
@@ -145,4 +132,51 @@ impl Host {
     pub(crate) fn test_bin_obj(&self, vals: &[u8]) -> Result<HostObj, HostError> {
         self.to_host_obj(&self.test_bin_scobj(vals)?)
     }
+
+    pub(crate) fn raw_val_vec_to_sc_vec(&self, v: Vec<RawVal>) -> ScVec {
+        let mut res = Vec::<ScVal>::new();
+        for val in v {
+            res.push(val.try_into_val(self).unwrap());
+        }
+        res.try_into().unwrap()
+    }
+
+    // Registers a contract with provided WASM source and returns the registered
+    // contract ID.
+    // This relies on the host to have no footprint enforcement.
+    pub(crate) fn register_test_contract_wasm(
+        &self,
+        contract_wasm: &[u8],
+    ) -> Result<Object, HostError> {
+        self.set_source_account(generate_account_id());
+        let wasm_id: RawVal = self
+            .invoke_function(HostFunction::InstallContractCode(InstallContractCodeArgs {
+                code: contract_wasm
+                    .to_vec()
+                    .try_into()
+                    .map_err(|_| self.err_general("too large wasm"))?,
+            }))?
+            .try_into_val(self)?;
+        let wasm_id = self.hash_from_obj_input("wasm_hash", wasm_id.try_into()?)?;
+        let id_obj: RawVal = self
+            .invoke_function(HostFunction::CreateContract(CreateContractArgs {
+                contract_id: ContractId::SourceAccount(Uint256(generate_bytes_array())),
+                source: ScContractCode::WasmRef(wasm_id),
+            }))?
+            .try_into_val(self)?;
+        self.remove_source_account();
+        Ok(id_obj.try_into()?)
+    }
+}
+
+impl Default for LedgerInfo {
+    fn default() -> Self {
+        Self {
+            protocol_version: Default::default(),
+            sequence_number: Default::default(),
+            timestamp: Default::default(),
+            network_passphrase: vec![0; 32],
+            base_reserve: Default::default(),
+        }
+    }
 }
diff --git a/soroban-test-wasms/wasm-workspace/Cargo.lock b/soroban-test-wasms/wasm-workspace/Cargo.lock
index aeec6247..da5ebdf4 100644
--- a/soroban-test-wasms/wasm-workspace/Cargo.lock
+++ b/soroban-test-wasms/wasm-workspace/Cargo.lock
@@ -55,9 +55,9 @@ checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
 
 [[package]]
 name = "base64"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "bitmaps"
@@ -79,9 +79,9 @@ dependencies = [
 
 [[package]]
 name = "block-buffer"
-version = "0.10.2"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
+checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
 dependencies = [
  "generic-array",
 ]
@@ -112,9 +112,9 @@ dependencies = [
 
 [[package]]
 name = "cc"
-version = "1.0.73"
+version = "1.0.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f"
 
 [[package]]
 name = "cfg-if"
@@ -153,9 +153,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
 
 [[package]]
 name = "cpufeatures"
-version = "0.2.3"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1079fb8528d9f9c888b1e8aa651e6e079ade467323d58f75faf1d30b1808f540"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
 dependencies = [
  "libc",
 ]
@@ -229,9 +229,9 @@ dependencies = [
 
 [[package]]
 name = "darling"
-version = "0.14.1"
+version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
+checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
 dependencies = [
  "darling_core",
  "darling_macro",
@@ -239,9 +239,9 @@ dependencies = [
 
 [[package]]
 name = "darling_core"
-version = "0.14.1"
+version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
+checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
 dependencies = [
  "fnv",
  "ident_case",
@@ -253,9 +253,9 @@ dependencies = [
 
 [[package]]
 name = "darling_macro"
-version = "0.14.1"
+version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
+checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
 dependencies = [
  "darling_core",
  "quote",
@@ -273,11 +273,11 @@ dependencies = [
 
 [[package]]
 name = "digest"
-version = "0.10.3"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
+checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c"
 dependencies = [
- "block-buffer 0.10.2",
+ "block-buffer 0.10.3",
  "crypto-common",
 ]
 
@@ -421,9 +421,9 @@ dependencies = [
 
 [[package]]
 name = "getrandom"
-version = "0.2.7"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
 dependencies = [
  "cfg-if",
  "libc",
@@ -488,7 +488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
 dependencies = [
  "bitmaps",
- "rand_core 0.6.3",
+ "rand_core 0.6.4",
  "rand_xoshiro",
  "sized-chunks",
  "typenum",
@@ -514,18 +514,18 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590"
 
 [[package]]
 name = "itertools"
-version = "0.10.3"
+version = "0.10.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
 dependencies = [
  "either",
 ]
 
 [[package]]
 name = "itoa"
-version = "1.0.3"
+version = "1.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
+checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
 
 [[package]]
 name = "js-sys"
@@ -538,15 +538,15 @@ dependencies = [
 
 [[package]]
 name = "libc"
-version = "0.2.132"
+version = "0.2.137"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
+checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
 
 [[package]]
 name = "libm"
-version = "0.2.5"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565"
+checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
 
 [[package]]
 name = "link-cplusplus"
@@ -580,9 +580,9 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
 
 [[package]]
 name = "miniz_oxide"
-version = "0.5.3"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
+checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
 dependencies = [
  "adler",
 ]
@@ -663,15 +663,15 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
 [[package]]
 name = "ppv-lite86"
-version = "0.2.16"
+version = "0.2.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
 name = "prettyplease"
-version = "0.1.19"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a49e86d2c26a24059894a3afa13fd17d063419b05dfb83f06d9c3566060c3f5a"
+checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51"
 dependencies = [
  "proc-macro2",
  "syn",
@@ -679,9 +679,9 @@ dependencies = [
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.43"
+version = "1.0.47"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
+checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
 dependencies = [
  "unicode-ident",
 ]
@@ -716,7 +716,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
  "libc",
  "rand_chacha 0.3.1",
- "rand_core 0.6.3",
+ "rand_core 0.6.4",
 ]
 
 [[package]]
@@ -736,7 +736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
  "ppv-lite86",
- "rand_core 0.6.3",
+ "rand_core 0.6.4",
 ]
 
 [[package]]
@@ -750,11 +750,11 @@ dependencies = [
 
 [[package]]
 name = "rand_core"
-version = "0.6.3"
+version = "0.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
 dependencies = [
- "getrandom 0.2.7",
+ "getrandom 0.2.8",
 ]
 
 [[package]]
@@ -772,7 +772,7 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
 dependencies = [
- "rand_core 0.6.3",
+ "rand_core 0.6.4",
 ]
 
 [[package]]
@@ -795,18 +795,18 @@ checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
 
 [[package]]
 name = "serde"
-version = "1.0.143"
+version = "1.0.147"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
+checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
 dependencies = [
  "serde_derive",
 ]
 
 [[package]]
 name = "serde_derive"
-version = "1.0.143"
+version = "1.0.147"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
+checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -815,9 +815,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.85"
+version = "1.0.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
+checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
 dependencies = [
  "itoa",
  "ryu",
@@ -867,20 +867,20 @@ dependencies = [
 
 [[package]]
 name = "sha2"
-version = "0.10.2"
+version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
 dependencies = [
  "cfg-if",
  "cpufeatures",
- "digest 0.10.3",
+ "digest 0.10.5",
 ]
 
 [[package]]
 name = "signature"
-version = "1.6.0"
+version = "1.6.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a"
+checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
 
 [[package]]
 name = "sized-chunks"
@@ -925,7 +925,7 @@ dependencies = [
  "num-derive",
  "num-integer",
  "num-traits",
- "sha2 0.10.2",
+ "sha2 0.10.6",
  "soroban-env-common",
  "soroban-native-sdk-macros",
  "soroban-wasmi",
@@ -957,7 +957,6 @@ dependencies = [
 [[package]]
 name = "soroban-sdk"
 version = "0.2.1"
-source = "git+https://github.com/stellar/rs-soroban-sdk?rev=63bef69c#63bef69c72f897b1d28beeaf6923afb5293f26f1"
 dependencies = [
  "bytes-lit",
  "ed25519-dalek",
@@ -971,13 +970,12 @@ dependencies = [
 [[package]]
 name = "soroban-sdk-macros"
 version = "0.2.1"
-source = "git+https://github.com/stellar/rs-soroban-sdk?rev=63bef69c#63bef69c72f897b1d28beeaf6923afb5293f26f1"
 dependencies = [
  "darling",
  "itertools",
  "proc-macro2",
  "quote",
- "sha2 0.10.2",
+ "sha2 0.10.6",
  "soroban-env-common",
  "soroban-spec",
  "stellar-xdr",
@@ -987,7 +985,6 @@ dependencies = [
 [[package]]
 name = "soroban-spec"
 version = "0.2.1"
-source = "git+https://github.com/stellar/rs-soroban-sdk?rev=63bef69c#63bef69c72f897b1d28beeaf6923afb5293f26f1"
 dependencies = [
  "base64",
  "darling",
@@ -998,7 +995,7 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "sha2 0.10.2",
+ "sha2 0.10.6",
  "soroban-env-host",
  "stellar-xdr",
  "syn",
@@ -1052,7 +1049,7 @@ dependencies = [
 [[package]]
 name = "stellar-xdr"
 version = "0.0.7"
-source = "git+https://github.com/stellar/rs-stellar-xdr?rev=e88f9fa7#e88f9fa7ab3d0f54efb5cee8499ef9080f4d41f4"
+source = "git+https://github.com/stellar/rs-stellar-xdr?rev=5fd7c7d4#5fd7c7d44d66452209a951be89c0c3602ab9f302"
 dependencies = [
  "base64",
  "hex",
@@ -1074,9 +1071,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 
 [[package]]
 name = "syn"
-version = "1.0.99"
+version = "1.0.103"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
+checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1174,9 +1171,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
 [[package]]
 name = "unicode-ident"
-version = "1.0.3"
+version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
+checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
 
 [[package]]
 name = "unicode-width"
@@ -1186,9 +1183,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
 [[package]]
 name = "unicode-xid"
-version = "0.2.3"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
 
 [[package]]
 name = "version_check"
diff --git a/soroban-test-wasms/wasm-workspace/Cargo.toml b/soroban-test-wasms/wasm-workspace/Cargo.toml
index 313b4448..2629da74 100644
--- a/soroban-test-wasms/wasm-workspace/Cargo.toml
+++ b/soroban-test-wasms/wasm-workspace/Cargo.toml
@@ -52,6 +52,6 @@ rev = "63bef69c"
 
 # Uncomment the following to build against local SDK. This is useful when env
 # changes are breaking for SDK and require the respective SDK change
-# [patch."https://github.com/stellar/rs-soroban-sdk"]
-# soroban-sdk = { path = "../../../rs-soroban-sdk/soroban-sdk" }
+[patch."https://github.com/stellar/rs-soroban-sdk"]
+soroban-sdk = { path = "../../../rs-soroban-sdk/soroban-sdk" }
 
diff --git a/soroban-test-wasms/wasm-workspace/create_contract/src/lib.rs b/soroban-test-wasms/wasm-workspace/create_contract/src/lib.rs
index 5ce915e2..d6ccf994 100644
--- a/soroban-test-wasms/wasm-workspace/create_contract/src/lib.rs
+++ b/soroban-test-wasms/wasm-workspace/create_contract/src/lib.rs
@@ -1,5 +1,5 @@
 #![no_std]
-use soroban_sdk::{contractimpl, Bytes, Env};
+use soroban_sdk::{contractimpl, Bytes, BytesN, Env};
 
 pub struct Contract;
 
@@ -7,7 +7,7 @@ pub struct Contract;
 impl Contract {
     // Note that anyone can create a contract here with any salt, so a users call to
     // this could be frontrun and the same salt taken.
-    pub fn create(e: Env, wasm: Bytes, salt: Bytes) {
-        e.deployer().with_current_contract(&salt).deploy(&wasm);
+    pub fn create(e: Env, wasm_hash: BytesN<32>, salt: Bytes) {
+        e.deployer().with_current_contract(&salt).deploy(&wasm_hash);
     }
 }