soroban_env_host/host/
data_helper.rs

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, ContractIdPreimage, ExtensionPoint, Hash,
13        HashIdPreimage, HashIdPreimageContractId, LedgerEntry, LedgerEntryData, LedgerEntryExt,
14        LedgerKey, LedgerKeyAccount, LedgerKeyContractCode, LedgerKeyContractData,
15        LedgerKeyTrustLine, PublicKey, ScAddress, ScContractInstance, ScErrorCode, ScErrorType,
16        ScMap, ScVal, Signer, SignerKey, ThresholdIndexes, TrustLineAsset, Uint256,
17    },
18    AddressObject, Env, Host, HostError, StorageType, U32Val, Val,
19};
20
21impl Host {
22    pub 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    /// Immutable accessor to the instance storage of the currently running
30    /// contract.
31    /// Performs lazy initialization of instance storage on access.
32    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    /// Mutable accessor to the instance storage of the currently running
50    /// contract.
51    /// Performs lazy initialization of instance storage on access.
52    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            // Consider any mutable access to be modifying the instance storage.
67            // This way we would provide consistent footprint (RO for read-only
68            // ops using `with_instance_storage` and RW for potentially
69            // mutating ops using `with_mut_instance_storage`).
70            storage.is_modified = true;
71            f(storage)
72        })
73    }
74
75    pub(crate) fn contract_instance_ledger_key(
76        &self,
77        contract_id: &Hash,
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    // Notes on metering: retrieving from storage covered. Rest are free.
91    pub(crate) fn retrieve_contract_instance_from_storage(
92        &self,
93        key: &Rc<LedgerKey>,
94    ) -> Result<ScContractInstance, HostError> {
95        let entry = self
96            .try_borrow_storage_mut()?
97            .get_with_host(key, self, None)?;
98        match &entry.data {
99            LedgerEntryData::ContractData(e) => match &e.val {
100                ScVal::ContractInstance(instance) => instance.metered_clone(self),
101                _ => Err(self.err(
102                    ScErrorType::Storage,
103                    ScErrorCode::InternalError,
104                    "ledger entry for contract instance does not contain contract instance",
105                    &[],
106                )),
107            },
108            _ => Err(self.err(
109                ScErrorType::Storage,
110                ScErrorCode::InternalError,
111                "expected ContractData ledger entry",
112                &[],
113            )),
114        }
115    }
116
117    pub(crate) fn contract_code_ledger_key(
118        &self,
119        wasm_hash: &Hash,
120    ) -> Result<Rc<LedgerKey>, HostError> {
121        let wasm_hash = wasm_hash.metered_clone(self)?;
122        Rc::metered_new(
123            LedgerKey::ContractCode(LedgerKeyContractCode { hash: wasm_hash }),
124            self,
125        )
126    }
127
128    pub(crate) fn retrieve_wasm_from_storage(
129        &self,
130        wasm_hash: &Hash,
131    ) -> Result<(BytesM, VersionedContractCodeCostInputs), HostError> {
132        let key = self.contract_code_ledger_key(wasm_hash)?;
133        match &self
134            .try_borrow_storage_mut()?
135            .get_with_host(&key, self, None)?
136            .data
137        {
138            LedgerEntryData::ContractCode(e) => {
139                let code = e.code.metered_clone(self)?;
140                let costs = match &e.ext {
141                    crate::xdr::ContractCodeEntryExt::V0 => VersionedContractCodeCostInputs::V0 {
142                        wasm_bytes: code.len(),
143                    },
144                    crate::xdr::ContractCodeEntryExt::V1(v1) => {
145                        VersionedContractCodeCostInputs::V1(
146                            v1.cost_inputs.metered_clone(self.as_budget())?,
147                        )
148                    }
149                };
150                Ok((code, costs))
151            }
152            _ => Err(err!(
153                self,
154                (ScErrorType::Storage, ScErrorCode::InternalError),
155                "expected ContractCode ledger entry",
156                *wasm_hash
157            )),
158        }
159    }
160
161    pub(crate) fn wasm_exists(&self, wasm_hash: &Hash) -> Result<bool, HostError> {
162        let key = self.contract_code_ledger_key(wasm_hash)?;
163        self.try_borrow_storage_mut()?
164            .has_with_host(&key, self, None)
165    }
166
167    // Stores the contract instance specified with its parts (executable and
168    // storage).
169    // When either of parts is `None`, the old value is preserved (when
170    // existent).
171    // `executable` has to be present for newly created contract instances.
172    // Notes on metering: `from_host_obj` and `put` to storage covered, rest are free.
173    pub(crate) fn store_contract_instance(
174        &self,
175        executable: Option<ContractExecutable>,
176        instance_storage: Option<ScMap>,
177        contract_id: Hash,
178        key: &Rc<LedgerKey>,
179    ) -> Result<(), HostError> {
180        if self
181            .try_borrow_storage_mut()?
182            .has_with_host(key, self, None)?
183        {
184            let (current, live_until_ledger) = self
185                .try_borrow_storage_mut()?
186                .get_with_live_until_ledger(key, self, None)?;
187            let mut current = (*current).metered_clone(self)?;
188
189            if let LedgerEntryData::ContractData(ref mut entry) = current.data {
190                if let ScVal::ContractInstance(ref mut instance) = entry.val {
191                    if let Some(executable) = executable {
192                        instance.executable = executable;
193                    }
194                    if let Some(storage) = instance_storage {
195                        instance.storage = Some(storage);
196                    }
197                } else {
198                    return Err(self.err(
199                        ScErrorType::Storage,
200                        ScErrorCode::InternalError,
201                        "expected ScVal::ContractInstance for contract instance",
202                        &[],
203                    ));
204                }
205            } else {
206                return Err(self.err(
207                    ScErrorType::Storage,
208                    ScErrorCode::InternalError,
209                    "expected DataEntry for contract instance",
210                    &[],
211                ));
212            }
213
214            self.try_borrow_storage_mut()?.put_with_host(
215                &key,
216                &Rc::metered_new(current, self)?,
217                live_until_ledger,
218                self,
219                None,
220            )?;
221        } else {
222            let data = ContractDataEntry {
223                contract: ScAddress::Contract(contract_id.metered_clone(self)?),
224                key: ScVal::LedgerKeyContractInstance,
225                val: ScVal::ContractInstance(ScContractInstance {
226                    executable: executable.ok_or_else(|| {
227                        self.err(
228                            ScErrorType::Context,
229                            ScErrorCode::InternalError,
230                            "can't initialize contract without executable",
231                            &[],
232                        )
233                    })?,
234                    storage: instance_storage,
235                }),
236                durability: ContractDataDurability::Persistent,
237                ext: ExtensionPoint::V0,
238            };
239            self.try_borrow_storage_mut()?.put_with_host(
240                key,
241                &Host::new_contract_data(self, data)?,
242                Some(self.get_min_live_until_ledger(ContractDataDurability::Persistent)?),
243                self,
244                None,
245            )?;
246        }
247        Ok(())
248    }
249
250    pub(crate) fn extend_contract_code_ttl_from_contract_id(
251        &self,
252        instance_key: Rc<LedgerKey>,
253        threshold: u32,
254        extend_to: u32,
255    ) -> Result<(), HostError> {
256        match self
257            .retrieve_contract_instance_from_storage(&instance_key)?
258            .executable
259        {
260            ContractExecutable::Wasm(wasm_hash) => {
261                let key = self.contract_code_ledger_key(&wasm_hash)?;
262                self.try_borrow_storage_mut()?
263                    .extend_ttl(self, key, threshold, extend_to, None)?;
264            }
265            ContractExecutable::StellarAsset => {}
266        }
267        Ok(())
268    }
269
270    pub(crate) fn extend_contract_instance_ttl_from_contract_id(
271        &self,
272        instance_key: Rc<LedgerKey>,
273        threshold: u32,
274        extend_to: u32,
275    ) -> Result<(), HostError> {
276        self.try_borrow_storage_mut()?.extend_ttl(
277            self,
278            instance_key.metered_clone(self)?,
279            threshold,
280            extend_to,
281            None,
282        )?;
283        Ok(())
284    }
285
286    // metering: covered by components
287    pub(crate) fn get_full_contract_id_preimage(
288        &self,
289        init_preimage: ContractIdPreimage,
290    ) -> Result<HashIdPreimage, HostError> {
291        Ok(HashIdPreimage::ContractId(HashIdPreimageContractId {
292            network_id: self
293                .hash_from_bytesobj_input("network_id", self.get_ledger_network_id()?)?,
294            contract_id_preimage: init_preimage,
295        }))
296    }
297
298    // notes on metering: `get` from storage is covered. Rest are free.
299    pub(crate) fn load_account(&self, account_id: AccountId) -> Result<AccountEntry, HostError> {
300        let acc = self.to_account_key(account_id)?;
301        self.with_mut_storage(
302            |storage| match &storage.get_with_host(&acc, self, None)?.data {
303                LedgerEntryData::Account(ae) => ae.metered_clone(self),
304                e => Err(err!(
305                    self,
306                    (ScErrorType::Storage, ScErrorCode::InternalError),
307                    "ledger entry is not account",
308                    e.name()
309                )),
310            },
311        )
312    }
313
314    pub(crate) fn to_account_key(&self, account_id: AccountId) -> Result<Rc<LedgerKey>, HostError> {
315        Rc::metered_new(LedgerKey::Account(LedgerKeyAccount { account_id }), self)
316    }
317
318    pub(crate) fn create_asset_4(&self, asset_code: [u8; 4], issuer: AccountId) -> Asset {
319        use crate::xdr::{AlphaNum4, AssetCode4};
320        Asset::CreditAlphanum4(AlphaNum4 {
321            asset_code: AssetCode4(asset_code),
322            issuer,
323        })
324    }
325
326    pub(crate) fn create_asset_12(&self, asset_code: [u8; 12], issuer: AccountId) -> Asset {
327        use crate::xdr::{AlphaNum12, AssetCode12};
328        Asset::CreditAlphanum12(AlphaNum12 {
329            asset_code: AssetCode12(asset_code),
330            issuer,
331        })
332    }
333
334    pub(crate) fn to_trustline_key(
335        &self,
336        account_id: AccountId,
337        asset: TrustLineAsset,
338    ) -> Result<Rc<LedgerKey>, HostError> {
339        Rc::metered_new(
340            LedgerKey::Trustline(LedgerKeyTrustLine { account_id, asset }),
341            self,
342        )
343    }
344
345    pub(crate) fn get_signer_weight_from_account(
346        &self,
347        target_signer: Uint256,
348        account: &AccountEntry,
349    ) -> Result<u8, HostError> {
350        if account.account_id
351            == AccountId(PublicKey::PublicKeyTypeEd25519(
352                target_signer.metered_clone(self)?,
353            ))
354        {
355            // Target signer is the master key, so return the master weight
356            let Some(threshold) = account
357                .thresholds
358                .0
359                .get(ThresholdIndexes::MasterWeight as usize)
360            else {
361                return Err(self.error(
362                    (ScErrorType::Value, ScErrorCode::InternalError).into(),
363                    "unexpected thresholds-array size",
364                    &[],
365                ));
366            };
367            Ok(*threshold)
368        } else {
369            // Target signer is not the master key, so search the account signers
370            let signers: &Vec<Signer> = account.signers.as_ref();
371            for signer in signers {
372                if let SignerKey::Ed25519(ref this_signer) = signer.key {
373                    if &target_signer == this_signer {
374                        // Clamp the weight at 255. Stellar protocol before v10
375                        // allowed weights to exceed 255, but the max threshold
376                        // is 255, hence there is no point in having a larger
377                        // weight.
378                        let weight = min(signer.weight, u8::MAX as u32);
379                        // We've found the target signer in the account signers, so return the weight
380                        return weight.try_into().map_err(|_| {
381                            self.err(
382                                ScErrorType::Auth,
383                                ScErrorCode::ArithDomain,
384                                "signer weight does not fit in u8",
385                                &[U32Val::from(weight).to_val()],
386                            )
387                        });
388                    }
389                }
390            }
391            // We didn't find the target signer, return 0 weight to indicate that.
392            Ok(0u8)
393        }
394    }
395
396    pub(crate) fn new_contract_data(
397        &self,
398        data: ContractDataEntry,
399    ) -> Result<Rc<LedgerEntry>, HostError> {
400        Rc::metered_new(
401            LedgerEntry {
402                // This is modified to the appropriate value on the core side during
403                // commiting the ledger transaction.
404                last_modified_ledger_seq: 0,
405                data: LedgerEntryData::ContractData(data),
406                ext: LedgerEntryExt::V0,
407            },
408            self,
409        )
410    }
411
412    pub(crate) fn new_contract_code(
413        &self,
414        data: ContractCodeEntry,
415    ) -> Result<Rc<LedgerEntry>, HostError> {
416        Rc::metered_new(
417            LedgerEntry {
418                // This is modified to the appropriate value on the core side during
419                // commiting the ledger transaction.
420                last_modified_ledger_seq: 0,
421                data: LedgerEntryData::ContractCode(data),
422                ext: LedgerEntryExt::V0,
423            },
424            self,
425        )
426    }
427
428    pub(crate) fn modify_ledger_entry_data(
429        &self,
430        original_entry: &LedgerEntry,
431        new_data: LedgerEntryData,
432    ) -> Result<Rc<LedgerEntry>, HostError> {
433        Rc::metered_new(
434            LedgerEntry {
435                // This is modified to the appropriate value on the core side during
436                // commiting the ledger transaction.
437                last_modified_ledger_seq: 0,
438                data: new_data,
439                ext: original_entry.ext.metered_clone(self)?,
440            },
441            self,
442        )
443    }
444
445    pub(crate) fn contract_id_from_scaddress(&self, address: ScAddress) -> Result<Hash, HostError> {
446        match address {
447            ScAddress::Account(_) => Err(self.err(
448                ScErrorType::Object,
449                ScErrorCode::InvalidInput,
450                "not a contract address",
451                &[],
452            )),
453            ScAddress::Contract(contract_id) => Ok(contract_id),
454        }
455    }
456
457    pub(crate) fn contract_id_from_address(
458        &self,
459        address: AddressObject,
460    ) -> Result<Hash, HostError> {
461        self.visit_obj(address, |addr: &ScAddress| {
462            self.contract_id_from_scaddress(addr.metered_clone(self)?)
463        })
464    }
465
466    pub(super) fn put_contract_data_into_ledger(
467        &self,
468        k: Val,
469        v: Val,
470        t: StorageType,
471    ) -> Result<(), HostError> {
472        let durability: ContractDataDurability = t.try_into()?;
473        let key = self.storage_key_from_val(k, durability)?;
474        // Currently the storage stores the whole ledger entries, while this
475        // operation might only modify the internal `ScVal` value. Thus we
476        // need to only overwrite the value in case if there is already an
477        // existing ledger entry value for the key in the storage.
478        if self
479            .try_borrow_storage_mut()?
480            .has_with_host(&key, self, Some(k))?
481        {
482            let (current, live_until_ledger) = self
483                .try_borrow_storage_mut()?
484                .get_with_live_until_ledger(&key, self, Some(k))?;
485            let mut current = (*current).metered_clone(self)?;
486            match current.data {
487                LedgerEntryData::ContractData(ref mut entry) => {
488                    entry.val = self.from_host_val(v)?;
489                }
490                _ => {
491                    return Err(self.err(
492                        ScErrorType::Storage,
493                        ScErrorCode::InternalError,
494                        "expected DataEntry",
495                        &[],
496                    ));
497                }
498            }
499            self.try_borrow_storage_mut()?.put_with_host(
500                &key,
501                &Rc::metered_new(current, self)?,
502                live_until_ledger,
503                self,
504                Some(k),
505            )?;
506        } else {
507            let data = ContractDataEntry {
508                contract: ScAddress::Contract(self.get_current_contract_id_internal()?),
509                key: self.from_host_val(k)?,
510                val: self.from_host_val(v)?,
511                durability,
512                ext: ExtensionPoint::V0,
513            };
514            self.try_borrow_storage_mut()?.put_with_host(
515                &key,
516                &Host::new_contract_data(self, data)?,
517                Some(self.get_min_live_until_ledger(durability)?),
518                self,
519                Some(k),
520            )?;
521        }
522
523        Ok(())
524    }
525}
526
527#[cfg(any(test, feature = "testutils"))]
528use crate::crypto;
529#[cfg(any(test, feature = "testutils"))]
530use crate::storage::{AccessType, Footprint};
531
532#[cfg(any(test, feature = "testutils"))]
533impl Host {
534    // Writes an arbitrary ledger entry to storage.
535    pub fn add_ledger_entry(
536        &self,
537        key: &Rc<LedgerKey>,
538        val: &Rc<soroban_env_common::xdr::LedgerEntry>,
539        live_until_ledger: Option<u32>,
540    ) -> Result<(), HostError> {
541        self.with_mut_storage(|storage| {
542            storage.put_with_host(key, val, live_until_ledger, self, None)
543        })
544    }
545
546    // Performs the necessary setup to access the provided ledger key/entry in
547    // enforcing storage mode.
548    pub fn setup_storage_entry(
549        &self,
550        key: Rc<LedgerKey>,
551        val: Option<(Rc<soroban_env_common::xdr::LedgerEntry>, Option<u32>)>,
552        access_type: AccessType,
553    ) -> Result<(), HostError> {
554        self.with_mut_storage(|storage| {
555            storage
556                .footprint
557                .record_access(&key, access_type, self.as_budget())?;
558            storage.map = storage.map.insert(key, val, self.as_budget())?;
559            Ok(())
560        })
561    }
562
563    // Performs the necessary setup to access all the entries in provided
564    // footprint in enforcing mode.
565    // "testutils" are not covered by budget metering.
566    pub fn setup_storage_footprint(&self, footprint: Footprint) -> Result<(), HostError> {
567        for (key, access_type) in footprint.0.map {
568            self.setup_storage_entry(key, None, access_type)?;
569        }
570        Ok(())
571    }
572
573    // Checks whether the given contract has a special 'dummy' executable
574    // that marks contracts created with `register_test_contract`.
575    pub(crate) fn is_test_contract_executable(
576        &self,
577        contract_id: &Hash,
578    ) -> Result<bool, HostError> {
579        let key = self.contract_instance_ledger_key(contract_id)?;
580        let instance = self.retrieve_contract_instance_from_storage(&key)?;
581        let test_contract_executable = ContractExecutable::Wasm(
582            crypto::sha256_hash_from_bytes(&[], self)?
583                .try_into()
584                .map_err(|_| {
585                    self.err(
586                        ScErrorType::Value,
587                        ScErrorCode::InternalError,
588                        "unexpected hash length",
589                        &[],
590                    )
591                })?,
592        );
593        Ok(test_contract_executable == instance.executable)
594    }
595
596    #[cfg(test)]
597    pub(crate) fn create_tl_asset_4(
598        &self,
599        asset_code: [u8; 4],
600        issuer: AccountId,
601    ) -> TrustLineAsset {
602        use crate::xdr::{AlphaNum4, AssetCode4};
603        TrustLineAsset::CreditAlphanum4(AlphaNum4 {
604            asset_code: AssetCode4(asset_code),
605            issuer,
606        })
607    }
608
609    #[cfg(test)]
610    pub(crate) fn create_tl_asset_12(
611        &self,
612        asset_code: [u8; 12],
613        issuer: AccountId,
614    ) -> TrustLineAsset {
615        use crate::xdr::{AlphaNum12, AssetCode12};
616        TrustLineAsset::CreditAlphanum12(AlphaNum12 {
617            asset_code: AssetCode12(asset_code),
618            issuer,
619        })
620    }
621}