soroban_env_host_zephyr/host/
lifecycle.rs

1use crate::{
2    err,
3    host::{
4        metered_clone::{MeteredAlloc, MeteredClone},
5        metered_write_xdr, ContractReentryMode, CreateContractArgs,
6    },
7    vm::Vm,
8    xdr::{
9        Asset, ContractCodeEntry, ContractDataDurability, ContractExecutable, ContractIdPreimage,
10        ContractIdPreimageFromAddress, ExtensionPoint, Hash, LedgerKey, LedgerKeyContractCode,
11        ScAddress, ScErrorCode, ScErrorType,
12    },
13    AddressObject, BytesObject, Host, HostError, Symbol, TryFromVal,
14};
15use std::rc::Rc;
16
17impl Host {
18    // Notes on metering: this is covered by the called components.
19    fn create_contract_with_id(
20        &self,
21        contract_id: Hash,
22        contract_executable: ContractExecutable,
23    ) -> Result<(), HostError> {
24        let storage_key = self.contract_instance_ledger_key(&contract_id)?;
25        if self
26            .try_borrow_storage_mut()?
27            .has_with_host(&storage_key, self, None)?
28        {
29            return Err(self.err(
30                ScErrorType::Storage,
31                ScErrorCode::ExistingValue,
32                "contract already exists",
33                &[self
34                    .add_host_object(self.scbytes_from_hash(&contract_id)?)?
35                    .into()],
36            ));
37        }
38        // Make sure the contract code exists. Without this check it would be
39        // possible to accidentally create a contract that never may be invoked
40        // (just by providing a bad hash).
41        if let ContractExecutable::Wasm(wasm_hash) = &contract_executable {
42            if !self.wasm_exists(wasm_hash)? {
43                return Err(err!(
44                    self,
45                    (ScErrorType::Storage, ScErrorCode::MissingValue),
46                    "Wasm does not exist",
47                    *wasm_hash
48                ));
49            }
50        }
51        self.store_contract_instance(Some(contract_executable), None, contract_id, &storage_key)?;
52        Ok(())
53    }
54
55    fn maybe_initialize_stellar_asset_contract(
56        &self,
57        contract_id: &Hash,
58        id_preimage: &ContractIdPreimage,
59    ) -> Result<(), HostError> {
60        if let ContractIdPreimage::Asset(asset) = id_preimage {
61            let mut asset_bytes: Vec<u8> = Default::default();
62            metered_write_xdr(self.budget_ref(), asset, &mut asset_bytes)?;
63            self.call_n_internal(
64                contract_id,
65                Symbol::try_from_val(self, &"init_asset")?,
66                &[self
67                    .add_host_object(self.scbytes_from_vec(asset_bytes)?)?
68                    .into()],
69                ContractReentryMode::Prohibited,
70                false,
71            )?;
72            Ok(())
73        } else {
74            Ok(())
75        }
76    }
77
78    pub(crate) fn create_contract_internal(
79        &self,
80        deployer: Option<AddressObject>,
81        args: CreateContractArgs,
82    ) -> Result<AddressObject, HostError> {
83        let has_deployer = deployer.is_some();
84        if has_deployer {
85            self.try_borrow_authorization_manager()?
86                .push_create_contract_host_fn_frame(self, args.metered_clone(self)?)?;
87        }
88        // Make sure that even in case of operation failure we still pop the
89        // stack frame.
90        // This is hacky, but currently this is the only instance where we need
91        // to manually manage auth manager frames (we don't need to authorize
92        // any other host fns and it doesn't seem useful to create extra frames
93        // for them just to make auth work in a single case).
94        let res = self.create_contract_with_optional_auth(deployer, args);
95        if has_deployer {
96            self.try_borrow_authorization_manager()?
97                .pop_frame(self, None)?;
98        }
99        res
100    }
101
102    fn create_contract_with_optional_auth(
103        &self,
104        deployer: Option<AddressObject>,
105        args: CreateContractArgs,
106    ) -> Result<AddressObject, HostError> {
107        if let Some(deployer_address) = deployer {
108            self.try_borrow_authorization_manager()?.require_auth(
109                self,
110                deployer_address,
111                Default::default(),
112            )?;
113        }
114
115        let id_preimage =
116            self.get_full_contract_id_preimage(args.contract_id_preimage.metered_clone(self)?)?;
117        let hash_id = Hash(self.metered_hash_xdr(&id_preimage)?);
118        self.create_contract_with_id(hash_id.metered_clone(self)?, args.executable)?;
119        self.maybe_initialize_stellar_asset_contract(&hash_id, &args.contract_id_preimage)?;
120        self.add_host_object(ScAddress::Contract(hash_id))
121    }
122
123    pub(crate) fn get_contract_id_hash(
124        &self,
125        deployer: AddressObject,
126        salt: BytesObject,
127    ) -> Result<Hash, HostError> {
128        let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress {
129            address: self.visit_obj(deployer, |addr: &ScAddress| addr.metered_clone(self))?,
130            salt: self.u256_from_bytesobj_input("contract_id_salt", salt)?,
131        });
132
133        let id_preimage =
134            self.get_full_contract_id_preimage(contract_id_preimage.metered_clone(self)?)?;
135        Ok(Hash(self.metered_hash_xdr(&id_preimage)?))
136    }
137
138    pub(crate) fn get_asset_contract_id_hash(&self, asset: Asset) -> Result<Hash, HostError> {
139        let id_preimage = self.get_full_contract_id_preimage(ContractIdPreimage::Asset(asset))?;
140        let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?;
141        Ok(Hash(id_arr))
142    }
143
144    pub(crate) fn upload_contract_wasm(&self, wasm: Vec<u8>) -> Result<BytesObject, HostError> {
145        let hash_bytes: [u8; 32] = crypto::sha256_hash_from_bytes(wasm.as_slice(), self)?
146            .try_into()
147            .map_err(|_| {
148                self.err(
149                    ScErrorType::Value,
150                    ScErrorCode::InternalError,
151                    "unexpected hash length",
152                    &[],
153                )
154            })?;
155
156        // Check size before instantiation.
157        let wasm_bytes_m: crate::xdr::BytesM = wasm.try_into().map_err(|_| {
158            self.err(
159                ScErrorType::Value,
160                ScErrorCode::ExceededLimit,
161                "Wasm code is too large",
162                &[],
163            )
164        })?;
165
166        let mut ext = crate::xdr::ContractCodeEntryExt::V0;
167
168        // Instantiate a temporary / throwaway VM using this wasm. This will do
169        // both quick checks like "does this wasm have the right protocol number
170        // to run on this network" and also a full parse-and-link pass to check
171        // that the wasm is basically not garbage. It might still fail to run
172        // but it will at least instantiate. This might seem a bit heavyweight
173        // but really "instantiating a VM" is mostly just "parsing the module
174        // and doing those checks" anyway. Revisit in the future if you want to
175        // try to split these costs up some.
176        if cfg!(any(test, feature = "testutils")) && wasm_bytes_m.as_slice().is_empty() {
177            // Allow a zero-byte contract when testing, as this is used to make
178            // native test contracts behave like wasm. They will never be
179            // instantiated, this is just to exercise their storage logic.
180        } else {
181            let _check_vm = Vm::new(
182                self,
183                Hash(hash_bytes.metered_clone(self)?),
184                wasm_bytes_m.as_slice(),
185            )?;
186            if self.get_ledger_protocol_version()? >= super::ModuleCache::MIN_LEDGER_VERSION {
187                // At this point we do a secondary parse on what we've checked to be a valid
188                // module in order to extract a refined cost model, which we'll store in the
189                // code entry's ext field, for future parsing and instantiations.
190                _check_vm.module.cost_inputs.charge_for_parsing(self)?;
191                ext = crate::xdr::ContractCodeEntryExt::V1(crate::xdr::ContractCodeEntryV1 {
192                    ext: ExtensionPoint::V0,
193                    cost_inputs: crate::vm::ParsedModule::extract_refined_contract_cost_inputs(
194                        self,
195                        wasm_bytes_m.as_slice(),
196                    )?,
197                });
198            }
199        }
200
201        let hash_obj = self.add_host_object(self.scbytes_from_slice(hash_bytes.as_slice())?)?;
202        let code_key = Rc::metered_new(
203            LedgerKey::ContractCode(LedgerKeyContractCode {
204                hash: Hash(hash_bytes.metered_clone(self)?),
205            }),
206            self,
207        )?;
208
209        let mut storage = self.try_borrow_storage_mut()?;
210
211        // We will definitely put the contract in the ledger if it isn't there yet.
212        #[allow(unused_mut)]
213        let mut should_put_contract = !storage.has_with_host(&code_key, self, None)?;
214
215        // We may also, in the cache-supporting protocol, overwrite the contract if its ext field changed.
216        if !should_put_contract
217            && self.get_ledger_protocol_version()? >= super::ModuleCache::MIN_LEDGER_VERSION
218        {
219            let entry = storage.get_with_host(&code_key, self, None)?;
220            if let crate::xdr::LedgerEntryData::ContractCode(ContractCodeEntry {
221                ext: old_ext,
222                ..
223            }) = &entry.data
224            {
225                should_put_contract = *old_ext != ext;
226            }
227        }
228
229        if should_put_contract {
230            let data = ContractCodeEntry {
231                hash: Hash(hash_bytes),
232                ext,
233                code: wasm_bytes_m,
234            };
235            storage.put_with_host(
236                &code_key,
237                &Host::new_contract_code(self, data)?,
238                Some(self.get_min_live_until_ledger(ContractDataDurability::Persistent)?),
239                self,
240                None,
241            )?;
242        }
243        Ok(hash_obj)
244    }
245}
246
247use super::crypto;
248#[cfg(any(test, feature = "testutils"))]
249use super::ContractFunctionSet;
250
251// "testutils" is not covered by budget metering.
252#[cfg(any(test, feature = "testutils"))]
253impl Host {
254    pub fn register_test_contract(
255        &self,
256        contract_address: AddressObject,
257        contract_fns: Rc<dyn ContractFunctionSet>,
258    ) -> Result<(), HostError> {
259        let contract_id = self.contract_id_from_address(contract_address)?;
260        let instance_key = self.contract_instance_ledger_key(&contract_id)?;
261        let wasm_hash_obj = self.upload_contract_wasm(vec![])?;
262        let wasm_hash = self.hash_from_bytesobj_input("wasm_hash", wasm_hash_obj)?;
263        // Use the empty Wasm as an executable to a) mark that the contract
264        // calls should be dispatched via provided `contract_fns` and b) have
265        // the same ledger entries as for 'real' contracts that consist of Wasm
266        // entry and the instance entry, so that instance-related host functions
267        // work properly.
268        self.store_contract_instance(
269            Some(ContractExecutable::Wasm(wasm_hash)),
270            None,
271            contract_id.clone(),
272            &instance_key,
273        )?;
274        let mut contracts = self.try_borrow_contracts_mut()?;
275        contracts.insert(contract_id, contract_fns);
276        Ok(())
277    }
278}