soroban_sdk/
storage.rs

1//! Storage contains types for storing data for the currently executing contract.
2use core::fmt::Debug;
3
4use crate::{
5    env::internal::{self, StorageType, Val},
6    unwrap::{UnwrapInfallible, UnwrapOptimized},
7    Env, IntoVal, TryFromVal,
8};
9
10/// Storage stores and retrieves data for the currently executing contract.
11///
12/// All data stored can only be queried and modified by the contract that stores
13/// it. Contracts cannot query or modify data stored by other contracts.
14///
15/// There are three types of storage - Temporary, Persistent, and Instance.
16///
17/// Temporary entries are the cheaper storage option and are never in the Expired State Stack (ESS). Whenever
18/// a TemporaryEntry expires, the entry is permanently deleted and cannot be recovered.
19/// This storage type is best for entries that are only relevant for short periods of
20/// time or for entries that can be arbitrarily recreated.
21///
22/// Persistent entries are the more expensive storage type. Whenever
23/// a persistent entry expires, it is deleted from the ledger, sent to the ESS
24/// and can be recovered via an operation in Stellar Core. Only a single version of a
25/// persistent entry can exist at a time.
26///
27/// Instance storage is used to store entries within the Persistent contract
28/// instance entry, allowing users to tie that data directly to the TTL
29/// of the instance. Instance storage is good for global contract data like
30/// metadata, admin accounts, or pool reserves.
31///
32/// ### Examples
33///
34/// ```
35/// use soroban_sdk::{Env, Symbol};
36///
37/// # use soroban_sdk::{contract, contractimpl, symbol_short, BytesN};
38/// #
39/// # #[contract]
40/// # pub struct Contract;
41/// #
42/// # #[contractimpl]
43/// # impl Contract {
44/// #     pub fn f(env: Env) {
45/// let storage = env.storage();
46/// let key = symbol_short!("key");
47/// storage.persistent().set(&key, &1);
48/// assert_eq!(storage.persistent().has(&key), true);
49/// assert_eq!(storage.persistent().get::<_, i32>(&key), Some(1));
50/// #     }
51/// # }
52/// #
53/// # #[cfg(feature = "testutils")]
54/// # fn main() {
55/// #     let env = Env::default();
56/// #     let contract_id = env.register(Contract, ());
57/// #     ContractClient::new(&env, &contract_id).f();
58/// # }
59/// # #[cfg(not(feature = "testutils"))]
60/// # fn main() { }
61/// ```
62#[derive(Clone)]
63pub struct Storage {
64    env: Env,
65}
66
67impl Debug for Storage {
68    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
69        write!(f, "Storage")
70    }
71}
72
73impl Storage {
74    #[inline(always)]
75    pub(crate) fn new(env: &Env) -> Storage {
76        Storage { env: env.clone() }
77    }
78
79    /// Storage for data that can stay in the ledger forever until deleted.
80    ///
81    /// Persistent entries might expire and be removed from the ledger if they run out
82    /// of the rent balance. However, expired entries can be restored and
83    /// they cannot be recreated. This means these entries
84    /// behave 'as if' they were stored in the ledger forever.
85    ///
86    /// This should be used for data that requires persistency, such as token
87    /// balances, user properties etc.
88    pub fn persistent(&self) -> Persistent {
89        assert_in_contract!(self.env);
90
91        Persistent {
92            storage: self.clone(),
93        }
94    }
95
96    /// Storage for data that may stay in ledger only for a limited amount of
97    /// time.
98    ///
99    /// Temporary storage is cheaper than Persistent storage.
100    ///
101    /// Temporary entries will be removed from the ledger after their lifetime
102    /// ends. Removed entries can be created again, potentially with different
103    /// values.
104    ///
105    /// This should be used for data that needs to only exist for a limited
106    /// period of time, such as oracle data, claimable balances, offer, etc.
107    pub fn temporary(&self) -> Temporary {
108        assert_in_contract!(self.env);
109
110        Temporary {
111            storage: self.clone(),
112        }
113    }
114
115    /// Storage for a **small amount** of persistent data associated with
116    /// the current contract's instance.
117    ///
118    /// Storing a small amount of frequently used data in instance storage is
119    /// likely cheaper than storing it separately in Persistent storage.
120    ///
121    /// Instance storage is tightly coupled with the contract instance: it will
122    /// be loaded from the ledger every time the contract instance itself is
123    /// loaded. It also won't appear in the ledger footprint. *All*
124    /// the data stored in the instance storage is read from ledger every time
125    /// the contract is used and it doesn't matter whether contract uses the
126    /// storage or not.
127    ///
128    /// This has the same lifetime properties as Persistent storage, i.e.
129    /// the data semantically stays in the ledger forever and can be
130    /// expired/restored.
131    ///
132    /// The amount of data that can be stored in the instance storage is limited
133    /// by the ledger entry size (a network-defined parameter). It is
134    /// in the order of 100 KB serialized.
135    ///
136    /// This should be used for small data directly associated with the current
137    /// contract, such as its admin, configuration settings, tokens the contract
138    /// operates on etc. Do not use this with any data that can scale in
139    /// unbounded fashion (such as user balances).
140    pub fn instance(&self) -> Instance {
141        assert_in_contract!(self.env);
142
143        Instance {
144            storage: self.clone(),
145        }
146    }
147
148    /// Returns the maximum time-to-live (TTL) for all the Soroban ledger entries.
149    ///
150    /// TTL is the number of ledgers left until the instance entry is considered
151    /// expired, excluding the current ledger. Maximum TTL represents the maximum
152    /// possible TTL of an entry and maximum extension via `extend_ttl` methods.
153    pub fn max_ttl(&self) -> u32 {
154        let seq = self.env.ledger().sequence();
155        let max = self.env.ledger().max_live_until_ledger();
156        max - seq
157    }
158
159    /// Returns if there is a value stored for the given key in the currently
160    /// executing contracts storage.
161    #[inline(always)]
162    pub(crate) fn has<K>(&self, key: &K, storage_type: StorageType) -> bool
163    where
164        K: IntoVal<Env, Val>,
165    {
166        self.has_internal(key.into_val(&self.env), storage_type)
167    }
168
169    /// Returns the value stored for the given key in the currently executing
170    /// contract's storage, when present.
171    ///
172    /// Returns `None` when the value is missing.
173    ///
174    /// If the value is present, then the returned value will be a result of
175    /// converting the internal value representation to `V`, or will panic if
176    /// the conversion to `V` fails.
177    #[inline(always)]
178    pub(crate) fn get<K, V>(&self, key: &K, storage_type: StorageType) -> Option<V>
179    where
180        K: IntoVal<Env, Val>,
181        V: TryFromVal<Env, Val>,
182    {
183        let key = key.into_val(&self.env);
184        if self.has_internal(key, storage_type) {
185            let rv = self.get_internal(key, storage_type);
186            Some(V::try_from_val(&self.env, &rv).unwrap_optimized())
187        } else {
188            None
189        }
190    }
191
192    /// Returns the value there is a value stored for the given key in the
193    /// currently executing contract's storage.
194    ///
195    /// The returned value is a result of converting the internal value
196    pub(crate) fn set<K, V>(&self, key: &K, val: &V, storage_type: StorageType)
197    where
198        K: IntoVal<Env, Val>,
199        V: IntoVal<Env, Val>,
200    {
201        let env = &self.env;
202        internal::Env::put_contract_data(env, key.into_val(env), val.into_val(env), storage_type)
203            .unwrap_infallible();
204    }
205
206    /// Update a value stored against a key.
207    ///
208    /// Loads the value, calls the function with it, then sets the value to the
209    /// returned value of the function.  If no value is stored with the key then
210    /// the function is called with None.
211    ///
212    /// The returned value is the value stored after updating.
213    pub(crate) fn update<K, V>(
214        &self,
215        key: &K,
216        storage_type: StorageType,
217        f: impl FnOnce(Option<V>) -> V,
218    ) -> V
219    where
220        K: IntoVal<Env, Val>,
221        V: TryFromVal<Env, Val>,
222        V: IntoVal<Env, Val>,
223    {
224        let key = key.into_val(&self.env);
225        let val = self.get(&key, storage_type);
226        let val = f(val);
227        self.set(&key, &val, storage_type);
228        val
229    }
230
231    /// Update a value stored against a key.
232    ///
233    /// Loads the value, calls the function with it, then sets the value to the
234    /// returned value of the function.  If no value is stored with the key then
235    /// the function is called with None.  If the function returns an error it
236    /// will be passed through.
237    ///
238    /// The returned value is the value stored after updating.
239    pub(crate) fn try_update<K, V, E>(
240        &self,
241        key: &K,
242        storage_type: StorageType,
243        f: impl FnOnce(Option<V>) -> Result<V, E>,
244    ) -> Result<V, E>
245    where
246        K: IntoVal<Env, Val>,
247        V: TryFromVal<Env, Val>,
248        V: IntoVal<Env, Val>,
249    {
250        let key = key.into_val(&self.env);
251        let val = self.get(&key, storage_type);
252        let val = f(val)?;
253        self.set(&key, &val, storage_type);
254        Ok(val)
255    }
256
257    pub(crate) fn extend_ttl<K>(
258        &self,
259        key: &K,
260        storage_type: StorageType,
261        threshold: u32,
262        extend_to: u32,
263    ) where
264        K: IntoVal<Env, Val>,
265    {
266        let env = &self.env;
267        internal::Env::extend_contract_data_ttl(
268            env,
269            key.into_val(env),
270            storage_type,
271            threshold.into(),
272            extend_to.into(),
273        )
274        .unwrap_infallible();
275    }
276
277    /// Removes the key and the corresponding value from the currently executing
278    /// contract's storage.
279    ///
280    /// No-op if the key does not exist.
281    #[inline(always)]
282    pub(crate) fn remove<K>(&self, key: &K, storage_type: StorageType)
283    where
284        K: IntoVal<Env, Val>,
285    {
286        let env = &self.env;
287        internal::Env::del_contract_data(env, key.into_val(env), storage_type).unwrap_infallible();
288    }
289
290    fn has_internal(&self, key: Val, storage_type: StorageType) -> bool {
291        internal::Env::has_contract_data(&self.env, key, storage_type)
292            .unwrap_infallible()
293            .into()
294    }
295
296    fn get_internal(&self, key: Val, storage_type: StorageType) -> Val {
297        internal::Env::get_contract_data(&self.env, key, storage_type).unwrap_infallible()
298    }
299}
300
301pub struct Persistent {
302    storage: Storage,
303}
304
305impl Persistent {
306    pub fn has<K>(&self, key: &K) -> bool
307    where
308        K: IntoVal<Env, Val>,
309    {
310        self.storage.has(key, StorageType::Persistent)
311    }
312
313    pub fn get<K, V>(&self, key: &K) -> Option<V>
314    where
315        V::Error: Debug,
316        K: IntoVal<Env, Val>,
317        V: TryFromVal<Env, Val>,
318    {
319        self.storage.get(key, StorageType::Persistent)
320    }
321
322    pub fn set<K, V>(&self, key: &K, val: &V)
323    where
324        K: IntoVal<Env, Val>,
325        V: IntoVal<Env, Val>,
326    {
327        self.storage.set(key, val, StorageType::Persistent)
328    }
329
330    /// Update a value stored against a key.
331    ///
332    /// Loads the value, calls the function with it, then sets the value to the
333    /// returned value of the function.  If no value is stored with the key then
334    /// the function is called with None.
335    ///
336    /// The returned value is the value stored after updating.
337    pub fn update<K, V>(&self, key: &K, f: impl FnOnce(Option<V>) -> V) -> V
338    where
339        K: IntoVal<Env, Val>,
340        V: IntoVal<Env, Val>,
341        V: TryFromVal<Env, Val>,
342    {
343        self.storage.update(key, StorageType::Persistent, f)
344    }
345
346    /// Update a value stored against a key.
347    ///
348    /// Loads the value, calls the function with it, then sets the value to the
349    /// returned value of the function.  If no value is stored with the key then
350    /// the function is called with None.  If the function returns an error it
351    /// will be passed through.
352    ///
353    /// The returned value is the value stored after updating.
354    pub fn try_update<K, V, E>(
355        &self,
356        key: &K,
357        f: impl FnOnce(Option<V>) -> Result<V, E>,
358    ) -> Result<V, E>
359    where
360        K: IntoVal<Env, Val>,
361        V: IntoVal<Env, Val>,
362        V: TryFromVal<Env, Val>,
363    {
364        self.storage.try_update(key, StorageType::Persistent, f)
365    }
366
367    /// Extend the TTL of the data under the key.
368    ///
369    /// Extends the TTL only if the TTL for the provided data is below `threshold` ledgers.
370    /// The TTL will then become `extend_to`.
371    ///
372    /// The TTL is the number of ledgers between the current ledger and the final ledger the data can still be accessed.
373    pub fn extend_ttl<K>(&self, key: &K, threshold: u32, extend_to: u32)
374    where
375        K: IntoVal<Env, Val>,
376    {
377        self.storage
378            .extend_ttl(key, StorageType::Persistent, threshold, extend_to)
379    }
380
381    #[inline(always)]
382    pub fn remove<K>(&self, key: &K)
383    where
384        K: IntoVal<Env, Val>,
385    {
386        self.storage.remove(key, StorageType::Persistent)
387    }
388}
389
390pub struct Temporary {
391    storage: Storage,
392}
393
394impl Temporary {
395    pub fn has<K>(&self, key: &K) -> bool
396    where
397        K: IntoVal<Env, Val>,
398    {
399        self.storage.has(key, StorageType::Temporary)
400    }
401
402    pub fn get<K, V>(&self, key: &K) -> Option<V>
403    where
404        V::Error: Debug,
405        K: IntoVal<Env, Val>,
406        V: TryFromVal<Env, Val>,
407    {
408        self.storage.get(key, StorageType::Temporary)
409    }
410
411    pub fn set<K, V>(&self, key: &K, val: &V)
412    where
413        K: IntoVal<Env, Val>,
414        V: IntoVal<Env, Val>,
415    {
416        self.storage.set(key, val, StorageType::Temporary)
417    }
418
419    /// Update a value stored against a key.
420    ///
421    /// Loads the value, calls the function with it, then sets the value to the
422    /// returned value of the function.  If no value is stored with the key then
423    /// the function is called with None.
424    ///
425    /// The returned value is the value stored after updating.
426    pub fn update<K, V>(&self, key: &K, f: impl FnOnce(Option<V>) -> V) -> V
427    where
428        K: IntoVal<Env, Val>,
429        V: IntoVal<Env, Val>,
430        V: TryFromVal<Env, Val>,
431    {
432        self.storage.update(key, StorageType::Temporary, f)
433    }
434
435    /// Update a value stored against a key.
436    ///
437    /// Loads the value, calls the function with it, then sets the value to the
438    /// returned value of the function.  If no value is stored with the key then
439    /// the function is called with None.  If the function returns an error it
440    /// will be passed through.
441    ///
442    /// The returned value is the value stored after updating.
443    pub fn try_update<K, V, E>(
444        &self,
445        key: &K,
446        f: impl FnOnce(Option<V>) -> Result<V, E>,
447    ) -> Result<V, E>
448    where
449        K: IntoVal<Env, Val>,
450        V: IntoVal<Env, Val>,
451        V: TryFromVal<Env, Val>,
452    {
453        self.storage.try_update(key, StorageType::Temporary, f)
454    }
455
456    /// Extend the TTL of the data under the key.
457    ///
458    /// Extends the TTL only if the TTL for the provided data is below `threshold` ledgers.
459    /// The TTL will then become `extend_to`.
460    ///
461    /// The TTL is the number of ledgers between the current ledger and the final ledger the data can still be accessed.
462    pub fn extend_ttl<K>(&self, key: &K, threshold: u32, extend_to: u32)
463    where
464        K: IntoVal<Env, Val>,
465    {
466        self.storage
467            .extend_ttl(key, StorageType::Temporary, threshold, extend_to)
468    }
469
470    #[inline(always)]
471    pub fn remove<K>(&self, key: &K)
472    where
473        K: IntoVal<Env, Val>,
474    {
475        self.storage.remove(key, StorageType::Temporary)
476    }
477}
478
479pub struct Instance {
480    storage: Storage,
481}
482
483impl Instance {
484    pub fn has<K>(&self, key: &K) -> bool
485    where
486        K: IntoVal<Env, Val>,
487    {
488        self.storage.has(key, StorageType::Instance)
489    }
490
491    pub fn get<K, V>(&self, key: &K) -> Option<V>
492    where
493        V::Error: Debug,
494        K: IntoVal<Env, Val>,
495        V: TryFromVal<Env, Val>,
496    {
497        self.storage.get(key, StorageType::Instance)
498    }
499
500    pub fn set<K, V>(&self, key: &K, val: &V)
501    where
502        K: IntoVal<Env, Val>,
503        V: IntoVal<Env, Val>,
504    {
505        self.storage.set(key, val, StorageType::Instance)
506    }
507
508    /// Update a value stored against a key.
509    ///
510    /// Loads the value, calls the function with it, then sets the value to the
511    /// returned value of the function.  If no value is stored with the key then
512    /// the function is called with None.
513    ///
514    /// The returned value is the value stored after updating.
515    pub fn update<K, V>(&self, key: &K, f: impl FnOnce(Option<V>) -> V) -> V
516    where
517        K: IntoVal<Env, Val>,
518        V: IntoVal<Env, Val>,
519        V: TryFromVal<Env, Val>,
520    {
521        self.storage.update(key, StorageType::Instance, f)
522    }
523
524    /// Update a value stored against a key.
525    ///
526    /// Loads the value, calls the function with it, then sets the value to the
527    /// returned value of the function.  If no value is stored with the key then
528    /// the function is called with None.  If the function returns an error it
529    /// will be passed through.
530    ///
531    /// The returned value is the value stored after updating.
532    pub fn try_update<K, V, E>(
533        &self,
534        key: &K,
535        f: impl FnOnce(Option<V>) -> Result<V, E>,
536    ) -> Result<V, E>
537    where
538        K: IntoVal<Env, Val>,
539        V: IntoVal<Env, Val>,
540        V: TryFromVal<Env, Val>,
541    {
542        self.storage.try_update(key, StorageType::Instance, f)
543    }
544
545    #[inline(always)]
546    pub fn remove<K>(&self, key: &K)
547    where
548        K: IntoVal<Env, Val>,
549    {
550        self.storage.remove(key, StorageType::Instance)
551    }
552
553    /// Extend the TTL of the contract instance and code.
554    ///
555    /// Extends the TTL of the instance and code only if the TTL for the provided contract is below `threshold` ledgers.
556    /// The TTL will then become `extend_to`. Note that the `threshold` check and TTL extensions are done for both the
557    /// contract code and contract instance, so it's possible that one is bumped but not the other depending on what the
558    /// current TTL's are.
559    ///
560    /// The TTL is the number of ledgers between the current ledger and the final ledger the data can still be accessed.
561    pub fn extend_ttl(&self, threshold: u32, extend_to: u32) {
562        internal::Env::extend_current_contract_instance_and_code_ttl(
563            &self.storage.env,
564            threshold.into(),
565            extend_to.into(),
566        )
567        .unwrap_infallible();
568    }
569}
570
571#[cfg(any(test, feature = "testutils"))]
572#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
573mod testutils {
574    use super::*;
575    use crate::{testutils, xdr, Map, TryIntoVal};
576
577    impl testutils::storage::Instance for Instance {
578        fn all(&self) -> Map<Val, Val> {
579            let env = &self.storage.env;
580            let storage = env.host().with_mut_storage(|s| Ok(s.map.clone())).unwrap();
581            let address: xdr::ScAddress = env.current_contract_address().try_into().unwrap();
582            for entry in storage {
583                let (k, Some((v, _))) = entry else {
584                    continue;
585                };
586                let xdr::LedgerKey::ContractData(xdr::LedgerKeyContractData {
587                    ref contract, ..
588                }) = *k
589                else {
590                    continue;
591                };
592                if contract != &address {
593                    continue;
594                }
595                let xdr::LedgerEntry {
596                    data:
597                        xdr::LedgerEntryData::ContractData(xdr::ContractDataEntry {
598                            key: xdr::ScVal::LedgerKeyContractInstance,
599                            val:
600                                xdr::ScVal::ContractInstance(xdr::ScContractInstance {
601                                    ref storage,
602                                    ..
603                                }),
604                            ..
605                        }),
606                    ..
607                } = *v
608                else {
609                    continue;
610                };
611                return match storage {
612                    Some(map) => {
613                        let map: Val =
614                            Val::try_from_val(env, &xdr::ScVal::Map(Some(map.clone()))).unwrap();
615                        map.try_into_val(env).unwrap()
616                    }
617                    None => Map::new(env),
618                };
619            }
620            panic!("contract instance for current contract address not found");
621        }
622
623        fn get_ttl(&self) -> u32 {
624            let env = &self.storage.env;
625            env.host()
626                .get_contract_instance_live_until_ledger(env.current_contract_address().to_object())
627                .unwrap()
628                .checked_sub(env.ledger().sequence())
629                .unwrap()
630        }
631    }
632
633    impl testutils::storage::Persistent for Persistent {
634        fn all(&self) -> Map<Val, Val> {
635            all(&self.storage.env, xdr::ContractDataDurability::Persistent)
636        }
637
638        fn get_ttl<K: IntoVal<Env, Val>>(&self, key: &K) -> u32 {
639            let env = &self.storage.env;
640            env.host()
641                .get_contract_data_live_until_ledger(key.into_val(env), StorageType::Persistent)
642                .unwrap()
643                .checked_sub(env.ledger().sequence())
644                .unwrap()
645        }
646    }
647
648    impl testutils::storage::Temporary for Temporary {
649        fn all(&self) -> Map<Val, Val> {
650            all(&self.storage.env, xdr::ContractDataDurability::Temporary)
651        }
652
653        fn get_ttl<K: IntoVal<Env, Val>>(&self, key: &K) -> u32 {
654            let env = &self.storage.env;
655            env.host()
656                .get_contract_data_live_until_ledger(key.into_val(env), StorageType::Temporary)
657                .unwrap()
658                .checked_sub(env.ledger().sequence())
659                .unwrap()
660        }
661    }
662
663    fn all(env: &Env, d: xdr::ContractDataDurability) -> Map<Val, Val> {
664        let storage = env.host().with_mut_storage(|s| Ok(s.map.clone())).unwrap();
665        let mut map = Map::<Val, Val>::new(env);
666        for entry in storage {
667            let (_, Some((v, _))) = entry else {
668                continue;
669            };
670            let xdr::LedgerEntry {
671                data:
672                    xdr::LedgerEntryData::ContractData(xdr::ContractDataEntry {
673                        ref key,
674                        ref val,
675                        durability,
676                        ..
677                    }),
678                ..
679            } = *v
680            else {
681                continue;
682            };
683            if d != durability {
684                continue;
685            }
686            let Ok(key) = Val::try_from_val(env, key) else {
687                continue;
688            };
689            let Ok(val) = Val::try_from_val(env, val) else {
690                continue;
691            };
692            map.set(key, val);
693        }
694        map
695    }
696}