1use core::cmp::min;
2use std::rc::Rc;
3
4use crate::{
5    budget::AsBudget,
6    err,
7    host::metered_clone::{MeteredAlloc, MeteredClone},
8    storage::{InstanceStorageMap, Storage},
9    vm::VersionedContractCodeCostInputs,
10    xdr::{
11        AccountEntry, AccountId, Asset, BytesM, ContractCodeEntry, ContractDataDurability,
12        ContractDataEntry, ContractExecutable, ContractId, ContractIdPreimage, ExtensionPoint,
13        Hash, HashIdPreimage, HashIdPreimageContractId, LedgerEntry, LedgerEntryData,
14        LedgerEntryExt, LedgerKey, LedgerKeyAccount, LedgerKeyContractCode, LedgerKeyContractData,
15        LedgerKeyTrustLine, PublicKey, ScAddress, ScContractInstance, ScErrorCode, ScErrorType,
16        ScMap, ScVal, Signer, SignerKey, ThresholdIndexes, TrustLineAsset, Uint256,
17    },
18    AddressObject, Env, ErrorHandler, Host, HostError, StorageType, U32Val, Val,
19};
20
21impl Host {
22    pub(crate) fn with_mut_storage<F, U>(&self, f: F) -> Result<U, HostError>
23    where
24        F: FnOnce(&mut Storage) -> Result<U, HostError>,
25    {
26        f(&mut *self.try_borrow_storage_mut()?)
27    }
28
29    pub(crate) fn with_instance_storage<F, U>(&self, f: F) -> Result<U, HostError>
33    where
34        F: FnOnce(&InstanceStorageMap) -> Result<U, HostError>,
35    {
36        self.with_current_context_mut(|ctx| {
37            self.maybe_init_instance_storage(ctx)?;
38            f(ctx.storage.as_ref().ok_or_else(|| {
39                self.err(
40                    ScErrorType::Context,
41                    ScErrorCode::InternalError,
42                    "missing instance storage",
43                    &[],
44                )
45            })?)
46        })
47    }
48
49    pub(crate) fn with_mut_instance_storage<F, U>(&self, f: F) -> Result<U, HostError>
53    where
54        F: FnOnce(&mut InstanceStorageMap) -> Result<U, HostError>,
55    {
56        self.with_current_context_mut(|ctx| {
57            self.maybe_init_instance_storage(ctx)?;
58            let storage = ctx.storage.as_mut().ok_or_else(|| {
59                self.err(
60                    ScErrorType::Context,
61                    ScErrorCode::InternalError,
62                    "missing instance storage",
63                    &[],
64                )
65            })?;
66            storage.is_modified = true;
71            f(storage)
72        })
73    }
74
75    pub(crate) fn contract_instance_ledger_key(
76        &self,
77        contract_id: &ContractId,
78    ) -> Result<Rc<LedgerKey>, HostError> {
79        let contract_id = contract_id.metered_clone(self)?;
80        Rc::metered_new(
81            LedgerKey::ContractData(LedgerKeyContractData {
82                key: ScVal::LedgerKeyContractInstance,
83                durability: ContractDataDurability::Persistent,
84                contract: ScAddress::Contract(contract_id),
85            }),
86            self,
87        )
88    }
89
90    pub(crate) fn extract_contract_instance_from_ledger_entry(
91        &self,
92        entry: &LedgerEntry,
93    ) -> Result<ScContractInstance, HostError> {
94        match &entry.data {
95            LedgerEntryData::ContractData(e) => match &e.val {
96                ScVal::ContractInstance(instance) => instance.metered_clone(self),
97                _ => Err(self.err(
98                    ScErrorType::Storage,
99                    ScErrorCode::InternalError,
100                    "ledger entry for contract instance does not contain contract instance",
101                    &[],
102                )),
103            },
104            _ => Err(self.err(
105                ScErrorType::Storage,
106                ScErrorCode::InternalError,
107                "expected ContractData ledger entry",
108                &[],
109            )),
110        }
111    }
112
113    pub(crate) fn retrieve_contract_instance_from_storage(
115        &self,
116        key: &Rc<LedgerKey>,
117    ) -> Result<ScContractInstance, HostError> {
118        let entry = self.try_borrow_storage_mut()?.get(key, self, None)?;
119        self.extract_contract_instance_from_ledger_entry(&entry)
120    }
121
122    pub(crate) fn contract_code_ledger_key(
123        &self,
124        wasm_hash: &Hash,
125    ) -> Result<Rc<LedgerKey>, HostError> {
126        let wasm_hash = wasm_hash.metered_clone(self)?;
127        Rc::metered_new(
128            LedgerKey::ContractCode(LedgerKeyContractCode { hash: wasm_hash }),
129            self,
130        )
131    }
132
133    pub(crate) fn retrieve_wasm_from_storage(
134        &self,
135        wasm_hash: &Hash,
136    ) -> Result<(BytesM, VersionedContractCodeCostInputs), HostError> {
137        let key = self.contract_code_ledger_key(wasm_hash)?;
138        match &self.try_borrow_storage_mut()?.get(&key, self, None)?.data {
139            LedgerEntryData::ContractCode(e) => {
140                let code = e.code.metered_clone(self)?;
141                let costs = match &e.ext {
142                    crate::xdr::ContractCodeEntryExt::V0 => VersionedContractCodeCostInputs::V0 {
143                        wasm_bytes: code.len(),
144                    },
145                    crate::xdr::ContractCodeEntryExt::V1(v1) => {
146                        VersionedContractCodeCostInputs::V1(
147                            v1.cost_inputs.metered_clone(self.as_budget())?,
148                        )
149                    }
150                };
151                Ok((code, costs))
152            }
153            _ => Err(err!(
154                self,
155                (ScErrorType::Storage, ScErrorCode::InternalError),
156                "expected ContractCode ledger entry",
157                *wasm_hash
158            )),
159        }
160    }
161
162    pub(crate) fn wasm_exists(&self, wasm_hash: &Hash) -> Result<bool, HostError> {
163        let key = self.contract_code_ledger_key(wasm_hash)?;
164        self.try_borrow_storage_mut()?.has(&key, self, None)
165    }
166
167    pub(crate) fn store_contract_instance(
174        &self,
175        executable: Option<ContractExecutable>,
176        instance_storage: Option<ScMap>,
177        contract_id: ContractId,
178        key: &Rc<LedgerKey>,
179    ) -> Result<(), HostError> {
180        if self.try_borrow_storage_mut()?.has(key, self, None)? {
181            let (current, live_until_ledger) = self
182                .try_borrow_storage_mut()?
183                .get_with_live_until_ledger(key, self, None)?;
184            let mut current = (*current).metered_clone(self)?;
185
186            if let LedgerEntryData::ContractData(ref mut entry) = current.data {
187                if let ScVal::ContractInstance(ref mut instance) = entry.val {
188                    if let Some(executable) = executable {
189                        instance.executable = executable;
190                    }
191                    if let Some(storage) = instance_storage {
192                        instance.storage = Some(storage);
193                    }
194                } else {
195                    return Err(self.err(
196                        ScErrorType::Storage,
197                        ScErrorCode::InternalError,
198                        "expected ScVal::ContractInstance for contract instance",
199                        &[],
200                    ));
201                }
202            } else {
203                return Err(self.err(
204                    ScErrorType::Storage,
205                    ScErrorCode::InternalError,
206                    "expected DataEntry for contract instance",
207                    &[],
208                ));
209            }
210
211            self.try_borrow_storage_mut()?.put(
212                &key,
213                &Rc::metered_new(current, self)?,
214                live_until_ledger,
215                self,
216                None,
217            )?;
218        } else {
219            let data = ContractDataEntry {
220                contract: ScAddress::Contract(contract_id.metered_clone(self)?),
221                key: ScVal::LedgerKeyContractInstance,
222                val: ScVal::ContractInstance(ScContractInstance {
223                    executable: executable.ok_or_else(|| {
224                        self.err(
225                            ScErrorType::Context,
226                            ScErrorCode::InternalError,
227                            "can't initialize contract without executable",
228                            &[],
229                        )
230                    })?,
231                    storage: instance_storage,
232                }),
233                durability: ContractDataDurability::Persistent,
234                ext: ExtensionPoint::V0,
235            };
236            self.try_borrow_storage_mut()?.put(
237                key,
238                &Host::new_contract_data(self, data)?,
239                Some(self.get_min_live_until_ledger(ContractDataDurability::Persistent)?),
240                self,
241                None,
242            )?;
243        }
244        Ok(())
245    }
246
247    pub(crate) fn extend_contract_code_ttl_from_contract_id(
248        &self,
249        instance_key: Rc<LedgerKey>,
250        threshold: u32,
251        extend_to: u32,
252    ) -> Result<(), HostError> {
253        match self
254            .retrieve_contract_instance_from_storage(&instance_key)?
255            .executable
256        {
257            ContractExecutable::Wasm(wasm_hash) => {
258                let key = self.contract_code_ledger_key(&wasm_hash)?;
259                self.try_borrow_storage_mut()?
260                    .extend_ttl(self, key, threshold, extend_to, None)?;
261            }
262            ContractExecutable::StellarAsset => {}
263        }
264        Ok(())
265    }
266
267    pub(crate) fn extend_contract_instance_ttl_from_contract_id(
268        &self,
269        instance_key: Rc<LedgerKey>,
270        threshold: u32,
271        extend_to: u32,
272    ) -> Result<(), HostError> {
273        self.try_borrow_storage_mut()?.extend_ttl(
274            self,
275            instance_key.metered_clone(self)?,
276            threshold,
277            extend_to,
278            None,
279        )?;
280        Ok(())
281    }
282
283    pub(crate) fn get_full_contract_id_preimage(
285        &self,
286        init_preimage: ContractIdPreimage,
287    ) -> Result<HashIdPreimage, HostError> {
288        Ok(HashIdPreimage::ContractId(HashIdPreimageContractId {
289            network_id: self
290                .hash_from_bytesobj_input("network_id", self.get_ledger_network_id()?)?,
291            contract_id_preimage: init_preimage,
292        }))
293    }
294
295    pub(crate) fn load_account(&self, account_id: AccountId) -> Result<AccountEntry, HostError> {
297        let acc = self.to_account_key(account_id)?;
298        self.with_mut_storage(|storage| match &storage.get(&acc, self, None)?.data {
299            LedgerEntryData::Account(ae) => ae.metered_clone(self),
300            e => Err(err!(
301                self,
302                (ScErrorType::Storage, ScErrorCode::InternalError),
303                "ledger entry is not account",
304                e.name()
305            )),
306        })
307    }
308
309    pub(crate) fn to_account_key(&self, account_id: AccountId) -> Result<Rc<LedgerKey>, HostError> {
310        Rc::metered_new(LedgerKey::Account(LedgerKeyAccount { account_id }), self)
311    }
312
313    pub(crate) fn create_asset_4(&self, asset_code: [u8; 4], issuer: AccountId) -> Asset {
314        use crate::xdr::{AlphaNum4, AssetCode4};
315        Asset::CreditAlphanum4(AlphaNum4 {
316            asset_code: AssetCode4(asset_code),
317            issuer,
318        })
319    }
320
321    pub(crate) fn create_asset_12(&self, asset_code: [u8; 12], issuer: AccountId) -> Asset {
322        use crate::xdr::{AlphaNum12, AssetCode12};
323        Asset::CreditAlphanum12(AlphaNum12 {
324            asset_code: AssetCode12(asset_code),
325            issuer,
326        })
327    }
328
329    pub(crate) fn to_trustline_key(
330        &self,
331        account_id: AccountId,
332        asset: TrustLineAsset,
333    ) -> Result<Rc<LedgerKey>, HostError> {
334        Rc::metered_new(
335            LedgerKey::Trustline(LedgerKeyTrustLine { account_id, asset }),
336            self,
337        )
338    }
339
340    pub(crate) fn get_signer_weight_from_account(
341        &self,
342        target_signer: Uint256,
343        account: &AccountEntry,
344    ) -> Result<u8, HostError> {
345        if account.account_id
346            == AccountId(PublicKey::PublicKeyTypeEd25519(
347                target_signer.metered_clone(self)?,
348            ))
349        {
350            let Some(threshold) = account
352                .thresholds
353                .0
354                .get(ThresholdIndexes::MasterWeight as usize)
355            else {
356                return Err(self.error(
357                    (ScErrorType::Value, ScErrorCode::InternalError).into(),
358                    "unexpected thresholds-array size",
359                    &[],
360                ));
361            };
362            Ok(*threshold)
363        } else {
364            let signers: &Vec<Signer> = account.signers.as_ref();
366            for signer in signers {
367                if let SignerKey::Ed25519(ref this_signer) = signer.key {
368                    if &target_signer == this_signer {
369                        let weight = min(signer.weight, u8::MAX as u32);
374                        return weight.try_into().map_err(|_| {
376                            self.err(
377                                ScErrorType::Auth,
378                                ScErrorCode::ArithDomain,
379                                "signer weight does not fit in u8",
380                                &[U32Val::from(weight).to_val()],
381                            )
382                        });
383                    }
384                }
385            }
386            Ok(0u8)
388        }
389    }
390
391    pub(crate) fn new_contract_data(
392        &self,
393        data: ContractDataEntry,
394    ) -> Result<Rc<LedgerEntry>, HostError> {
395        Rc::metered_new(
396            LedgerEntry {
397                last_modified_ledger_seq: 0,
400                data: LedgerEntryData::ContractData(data),
401                ext: LedgerEntryExt::V0,
402            },
403            self,
404        )
405    }
406
407    pub(crate) fn new_contract_code(
408        &self,
409        data: ContractCodeEntry,
410    ) -> Result<Rc<LedgerEntry>, HostError> {
411        Rc::metered_new(
412            LedgerEntry {
413                last_modified_ledger_seq: 0,
416                data: LedgerEntryData::ContractCode(data),
417                ext: LedgerEntryExt::V0,
418            },
419            self,
420        )
421    }
422
423    pub(crate) fn modify_ledger_entry_data(
424        &self,
425        original_entry: &LedgerEntry,
426        new_data: LedgerEntryData,
427    ) -> Result<Rc<LedgerEntry>, HostError> {
428        Rc::metered_new(
429            LedgerEntry {
430                last_modified_ledger_seq: 0,
433                data: new_data,
434                ext: original_entry.ext.metered_clone(self)?,
435            },
436            self,
437        )
438    }
439
440    pub(crate) fn contract_id_from_scaddress(
441        &self,
442        address: ScAddress,
443    ) -> Result<ContractId, HostError> {
444        match address {
445            ScAddress::Contract(contract_id) => Ok(contract_id),
446            _ => Err(self.err(
447                ScErrorType::Object,
448                ScErrorCode::InvalidInput,
449                "not a contract address",
450                &[],
451            )),
452        }
453    }
454
455    pub(crate) fn contract_id_from_address(
456        &self,
457        address: AddressObject,
458    ) -> Result<ContractId, HostError> {
459        self.visit_obj(address, |addr: &ScAddress| {
460            self.contract_id_from_scaddress(addr.metered_clone(self)?)
461        })
462    }
463
464    pub(super) fn put_contract_data_into_ledger(
465        &self,
466        k: Val,
467        v: Val,
468        t: StorageType,
469    ) -> Result<(), HostError> {
470        let durability: ContractDataDurability = t.try_into()?;
471        let key = self.storage_key_from_val(k, durability)?;
472        if self.try_borrow_storage_mut()?.has(&key, self, Some(k))? {
477            let (current, live_until_ledger) = self
478                .try_borrow_storage_mut()?
479                .get_with_live_until_ledger(&key, self, Some(k))?;
480            let mut current = (*current).metered_clone(self)?;
481            match current.data {
482                LedgerEntryData::ContractData(ref mut entry) => {
483                    entry.val = self.from_host_val(v)?;
484                }
485                _ => {
486                    return Err(self.err(
487                        ScErrorType::Storage,
488                        ScErrorCode::InternalError,
489                        "expected DataEntry",
490                        &[],
491                    ));
492                }
493            }
494            self.try_borrow_storage_mut()?.put(
495                &key,
496                &Rc::metered_new(current, self)?,
497                live_until_ledger,
498                self,
499                Some(k),
500            )?;
501        } else {
502            let data = ContractDataEntry {
503                contract: ScAddress::Contract(self.get_current_contract_id_internal()?),
504                key: self.from_host_val(k)?,
505                val: self.from_host_val(v)?,
506                durability,
507                ext: ExtensionPoint::V0,
508            };
509            self.try_borrow_storage_mut()?.put(
510                &key,
511                &Host::new_contract_data(self, data)?,
512                Some(self.get_min_live_until_ledger(durability)?),
513                self,
514                Some(k),
515            )?;
516        }
517
518        Ok(())
519    }
520}
521
522#[cfg(any(test, feature = "testutils"))]
523use crate::crypto;
524#[cfg(any(test, feature = "testutils"))]
525use crate::storage::{AccessType, EntryWithLiveUntil, Footprint};
526
527#[cfg(any(test, feature = "testutils"))]
528impl Host {
529    pub fn add_ledger_entry(
531        &self,
532        key: &Rc<LedgerKey>,
533        val: &Rc<soroban_env_common::xdr::LedgerEntry>,
534        live_until_ledger: Option<u32>,
535    ) -> Result<(), HostError> {
536        self.with_mut_storage(|storage| storage.put(key, val, live_until_ledger, self, None))
537    }
538
539    pub fn get_ledger_entry(
543        &self,
544        key: &Rc<LedgerKey>,
545    ) -> Result<Option<EntryWithLiveUntil>, HostError> {
546        self.with_mut_storage(|storage| storage.try_get_full(key, self, None))
547    }
548
549    #[allow(clippy::type_complexity)]
551    pub fn get_stored_entries(
552        &self,
553    ) -> Result<Vec<(Rc<LedgerKey>, Option<EntryWithLiveUntil>)>, HostError> {
554        self.with_mut_storage(|storage| Ok(storage.map.map.clone()))
555    }
556
557    pub fn setup_storage_entry(
560        &self,
561        key: Rc<LedgerKey>,
562        val: Option<(Rc<soroban_env_common::xdr::LedgerEntry>, Option<u32>)>,
563        access_type: AccessType,
564    ) -> Result<(), HostError> {
565        self.with_mut_storage(|storage| {
566            storage
567                .footprint
568                .record_access(&key, access_type, self.as_budget())?;
569            storage.map = storage.map.insert(key, val, self.as_budget())?;
570            Ok(())
571        })
572    }
573
574    pub fn setup_storage_footprint(&self, footprint: Footprint) -> Result<(), HostError> {
578        for (key, access_type) in footprint.0.map {
579            self.setup_storage_entry(key, None, access_type)?;
580        }
581        Ok(())
582    }
583
584    pub(crate) fn is_test_contract_executable(
587        &self,
588        contract_id: &ContractId,
589    ) -> Result<bool, HostError> {
590        let key = self.contract_instance_ledger_key(contract_id)?;
591        let instance = self.retrieve_contract_instance_from_storage(&key)?;
592        let test_contract_executable = ContractExecutable::Wasm(
593            crypto::sha256_hash_from_bytes(&[], self)?
594                .try_into()
595                .map_err(|_| {
596                    self.err(
597                        ScErrorType::Value,
598                        ScErrorCode::InternalError,
599                        "unexpected hash length",
600                        &[],
601                    )
602                })?,
603        );
604        Ok(test_contract_executable == instance.executable)
605    }
606
607    #[cfg(test)]
608    pub(crate) fn create_tl_asset_4(
609        &self,
610        asset_code: [u8; 4],
611        issuer: AccountId,
612    ) -> TrustLineAsset {
613        use crate::xdr::{AlphaNum4, AssetCode4};
614        TrustLineAsset::CreditAlphanum4(AlphaNum4 {
615            asset_code: AssetCode4(asset_code),
616            issuer,
617        })
618    }
619
620    #[cfg(test)]
621    pub(crate) fn create_tl_asset_12(
622        &self,
623        asset_code: [u8; 12],
624        issuer: AccountId,
625    ) -> TrustLineAsset {
626        use crate::xdr::{AlphaNum12, AssetCode12};
627        TrustLineAsset::CreditAlphanum12(AlphaNum12 {
628            asset_code: AssetCode12(asset_code),
629            issuer,
630        })
631    }
632}