1use crate::{
2    crypto, err,
3    host::{
4        metered_clone::{MeteredAlloc, MeteredClone},
5        metered_write_xdr, ContractReentryMode,
6    },
7    vm::Vm,
8    xdr::{
9        Asset, ContractCodeEntry, ContractDataDurability, ContractExecutable, ContractId,
10        ContractIdPreimage, ContractIdPreimageFromAddress, CreateContractArgsV2, ExtensionPoint,
11        Hash, LedgerKey, LedgerKeyContractCode, ScAddress, ScErrorCode, ScErrorType,
12    },
13    AddressObject, BytesObject, Host, HostError, Symbol, TryFromVal, TryIntoVal, Val,
14};
15use std::rc::Rc;
16
17const CONSTRUCTOR_FUNCTION_NAME: &str = "__constructor";
18const CONSTRUCTOR_SUPPORT_PROTOCOL: u32 = 22;
19
20impl Host {
21    fn create_contract_with_id(
23        &self,
24        contract_id: ContractId,
25        contract_executable: ContractExecutable,
26    ) -> Result<(), HostError> {
27        let storage_key = self.contract_instance_ledger_key(&contract_id)?;
28        if self
29            .try_borrow_storage_mut()?
30            .has(&storage_key, self, None)?
31        {
32            return Err(self.err(
33                ScErrorType::Storage,
34                ScErrorCode::ExistingValue,
35                "contract already exists",
36                &[self
37                    .add_host_object(self.scbytes_from_hash(&contract_id.0)?)?
38                    .into()],
39            ));
40        }
41        if let ContractExecutable::Wasm(wasm_hash) = &contract_executable {
45            if !self.wasm_exists(wasm_hash)? {
46                return Err(err!(
47                    self,
48                    (ScErrorType::Storage, ScErrorCode::MissingValue),
49                    "Wasm does not exist",
50                    *wasm_hash
51                ));
52            }
53        }
54        self.store_contract_instance(Some(contract_executable), None, contract_id, &storage_key)?;
55        Ok(())
56    }
57
58    fn call_constructor(
59        &self,
60        contract_id: &ContractId,
61        constructor_args: Vec<Val>,
62    ) -> Result<(), HostError> {
63        let contract_protocol = self.get_contract_protocol_version(&contract_id)?;
67        if contract_protocol < CONSTRUCTOR_SUPPORT_PROTOCOL {
68            if constructor_args.is_empty() {
69                return Ok(());
70            }
71            return Err(self.err(
72                ScErrorType::Context,
73                ScErrorCode::InvalidAction,
74                "trying to call non-default constructor on a contract that doesn't support constructors (built prior to protocol 22)",
75                &[],
76            ));
77        }
78        let res = self
79            .call_n_internal(
80                contract_id,
81                CONSTRUCTOR_FUNCTION_NAME.try_into_val(self)?,
82                constructor_args.as_slice(),
83                CallParams {
84                    reentry_mode: ContractReentryMode::Prohibited,
85                    internal_host_call: true,
86                    treat_missing_function_as_noop: constructor_args.is_empty(),
89                },
90            )
91            .map_err(|err| {
92                if err.is_recoverable() {
96                    self.err(
98                        ScErrorType::Context,
99                        ScErrorCode::InvalidAction,
100                        "constructor invocation has failed with error",
101                        &[err.error.to_val()],
102                    )
103                } else {
104                    err
105                }
106            })?;
107        if !res.is_void() {
108            return Err(self.err(
109                ScErrorType::Value,
110                ScErrorCode::UnexpectedType,
111                "constructor returned non-void value",
112                &[res],
113            ));
114        }
115        Ok(())
116    }
117
118    fn maybe_initialize_stellar_asset_contract(
119        &self,
120        contract_id: &ContractId,
121        id_preimage: &ContractIdPreimage,
122    ) -> Result<(), HostError> {
123        if let ContractIdPreimage::Asset(asset) = id_preimage {
124            let mut asset_bytes: Vec<u8> = Default::default();
125            metered_write_xdr(self.budget_ref(), asset, &mut asset_bytes)?;
126            self.call_n_internal(
127                contract_id,
128                Symbol::try_from_val(self, &"init_asset")?,
129                &[self
130                    .add_host_object(self.scbytes_from_vec(asset_bytes)?)?
131                    .into()],
132                CallParams::default_external_call(),
133            )?;
134            Ok(())
135        } else {
136            Ok(())
137        }
138    }
139
140    pub(crate) fn create_contract_internal(
141        &self,
142        deployer: Option<AddressObject>,
143        args: CreateContractArgsV2,
144        constructor_args: Vec<Val>,
145    ) -> Result<AddressObject, HostError> {
146        let has_deployer = deployer.is_some();
147        if has_deployer {
148            self.try_borrow_authorization_manager()?
149                .push_create_contract_host_fn_frame(self, args.metered_clone(self)?)?;
150        }
151        let res = self.create_contract_with_optional_auth(deployer, args, constructor_args);
158        if has_deployer {
159            self.try_borrow_authorization_manager()?
160                .pop_frame(self, None)?;
161        }
162        res
163    }
164
165    fn create_contract_with_optional_auth(
166        &self,
167        deployer: Option<AddressObject>,
168        args: CreateContractArgsV2,
169        constructor_args: Vec<Val>,
170    ) -> Result<AddressObject, HostError> {
171        if let Some(deployer_address) = deployer {
172            self.try_borrow_authorization_manager()?.require_auth(
173                self,
174                deployer_address,
175                Default::default(),
176            )?;
177        }
178
179        let id_preimage =
180            self.get_full_contract_id_preimage(args.contract_id_preimage.metered_clone(self)?)?;
181        let contract_id = ContractId(Hash(self.metered_hash_xdr(&id_preimage)?));
182        self.create_contract_with_id(contract_id.metered_clone(self)?, args.executable.clone())?;
183        self.maybe_initialize_stellar_asset_contract(&contract_id, &args.contract_id_preimage)?;
184        if matches!(args.executable, ContractExecutable::Wasm(_)) {
185            self.call_constructor(&contract_id, constructor_args)?;
186        }
187        self.add_host_object(ScAddress::Contract(contract_id))
188    }
189
190    pub(crate) fn get_contract_id_hash(
191        &self,
192        deployer: AddressObject,
193        salt: BytesObject,
194    ) -> Result<ContractId, HostError> {
195        let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress {
196            address: self.visit_obj(deployer, |addr: &ScAddress| addr.metered_clone(self))?,
197            salt: self.u256_from_bytesobj_input("contract_id_salt", salt)?,
198        });
199
200        let id_preimage =
201            self.get_full_contract_id_preimage(contract_id_preimage.metered_clone(self)?)?;
202        Ok(ContractId(Hash(self.metered_hash_xdr(&id_preimage)?)))
203    }
204
205    pub(crate) fn get_asset_contract_id_hash(&self, asset: Asset) -> Result<ContractId, HostError> {
206        let id_preimage = self.get_full_contract_id_preimage(ContractIdPreimage::Asset(asset))?;
207        let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?;
208        Ok(ContractId(Hash(id_arr)))
209    }
210
211    pub(crate) fn upload_contract_wasm(&self, wasm: Vec<u8>) -> Result<BytesObject, HostError> {
212        let hash_bytes: [u8; 32] = crypto::sha256_hash_from_bytes(wasm.as_slice(), self)?
213            .try_into()
214            .map_err(|_| {
215                self.err(
216                    ScErrorType::Value,
217                    ScErrorCode::InternalError,
218                    "unexpected hash length",
219                    &[],
220                )
221            })?;
222
223        let wasm_bytes_m: crate::xdr::BytesM = wasm.try_into().map_err(|_| {
225            self.err(
226                ScErrorType::Value,
227                ScErrorCode::ExceededLimit,
228                "Wasm code is too large",
229                &[],
230            )
231        })?;
232
233        let mut ext = crate::xdr::ContractCodeEntryExt::V0;
234
235        if cfg!(any(test, feature = "testutils")) && wasm_bytes_m.as_slice().is_empty() {
244            } else {
248            let _check_vm = Vm::new(
249                self,
250                ContractId(Hash(hash_bytes.metered_clone(self)?)),
251                wasm_bytes_m.as_slice(),
252            )?;
253            _check_vm.module.cost_inputs.charge_for_parsing(self)?;
257            ext = crate::xdr::ContractCodeEntryExt::V1(crate::xdr::ContractCodeEntryV1 {
258                ext: ExtensionPoint::V0,
259                cost_inputs: crate::vm::ParsedModule::extract_refined_contract_cost_inputs(
260                    self,
261                    wasm_bytes_m.as_slice(),
262                )?,
263            });
264        }
265
266        let hash_obj = self.add_host_object(self.scbytes_from_slice(hash_bytes.as_slice())?)?;
267        let code_key = Rc::metered_new(
268            LedgerKey::ContractCode(LedgerKeyContractCode {
269                hash: Hash(hash_bytes.metered_clone(self)?),
270            }),
271            self,
272        )?;
273
274        let mut storage = self.try_borrow_storage_mut()?;
275
276        #[allow(unused_mut)]
278        let mut should_put_contract = !storage.has(&code_key, self, None)?;
279
280        if !should_put_contract {
282            let entry = storage.get(&code_key, self, None)?;
283            if let crate::xdr::LedgerEntryData::ContractCode(ContractCodeEntry {
284                ext: old_ext,
285                ..
286            }) = &entry.data
287            {
288                should_put_contract = *old_ext != ext;
289            }
290        }
291
292        if should_put_contract {
293            let data = ContractCodeEntry {
294                hash: Hash(hash_bytes),
295                ext,
296                code: wasm_bytes_m,
297            };
298            storage.put(
299                &code_key,
300                &Host::new_contract_code(self, data)?,
301                Some(self.get_min_live_until_ledger(ContractDataDurability::Persistent)?),
302                self,
303                None,
304            )?;
305        }
306        Ok(hash_obj)
307    }
308}
309
310use super::frame::CallParams;
311#[cfg(any(test, feature = "testutils"))]
312use super::ContractFunctionSet;
313
314#[cfg(any(test, feature = "testutils"))]
316impl Host {
317    pub fn register_test_contract(
318        &self,
319        contract_address: AddressObject,
320        contract_fns: Rc<dyn ContractFunctionSet>,
321    ) -> Result<(), HostError> {
322        #[cfg(any(test, feature = "testutils"))]
323        let _invocation_meter_scope = self.maybe_meter_invocation(
324            crate::host::invocation_metering::MeteringInvocation::CreateContractEntryPoint,
325        );
326
327        use crate::Env;
328        self.register_test_contract_with_constructor(
329            contract_address,
330            contract_fns,
331            self.vec_new()?,
332        )
333    }
334
335    pub fn register_test_contract_with_constructor(
336        &self,
337        contract_address: AddressObject,
338        contract_fns: Rc<dyn ContractFunctionSet>,
339        constructor_args: crate::VecObject,
340    ) -> Result<(), HostError> {
341        #[cfg(any(test, feature = "testutils"))]
342        let _invocation_meter_scope = self.maybe_meter_invocation(
343            crate::host::invocation_metering::MeteringInvocation::CreateContractEntryPoint,
344        );
345
346        let contract_id = self.contract_id_from_address(contract_address)?;
347        let instance_key = self.contract_instance_ledger_key(&contract_id)?;
348        let wasm_hash_obj = self.upload_contract_wasm(vec![])?;
349        let wasm_hash = self.hash_from_bytesobj_input("wasm_hash", wasm_hash_obj)?;
350        self.store_contract_instance(
356            Some(ContractExecutable::Wasm(wasm_hash)),
357            None,
358            contract_id.clone(),
359            &instance_key,
360        )?;
361        self.try_borrow_contracts_mut()?
362            .insert(contract_id.clone(), contract_fns);
363
364        self.call_constructor(&contract_id, self.call_args_from_obj(constructor_args)?)
365    }
366
367    pub fn call_constructor_for_stored_contract_unsafe(
373        &self,
374        contract_id: &ContractId,
375        constructor_args: crate::VecObject,
376    ) -> Result<(), HostError> {
377        self.call_constructor(&contract_id, self.call_args_from_obj(constructor_args)?)
378    }
379}