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}