1use std::rc::Rc;
11
12use crate::budget::AsBudget;
13use crate::host::metered_clone::MeteredClone;
14use crate::{
15 budget::Budget,
16 host::metered_map::MeteredOrdMap,
17 ledger_info::get_key_durability,
18 xdr::{ContractDataDurability, LedgerEntry, LedgerKey, ScErrorCode, ScErrorType, ScVal},
19 Env, Error, Host, HostError, Val,
20};
21
22pub type FootprintMap = MeteredOrdMap<Rc<LedgerKey>, AccessType, Budget>;
23pub type EntryWithLiveUntil = (Rc<LedgerEntry>, Option<u32>);
24pub type StorageMap = MeteredOrdMap<Rc<LedgerKey>, Option<EntryWithLiveUntil>, Budget>;
25
26#[derive(Clone, Hash)]
30pub(crate) struct InstanceStorageMap {
31 pub(crate) map: MeteredOrdMap<Val, Val, Host>,
32 pub(crate) is_modified: bool,
33}
34
35impl InstanceStorageMap {
36 pub(crate) fn from_map(map: Vec<(Val, Val)>, host: &Host) -> Result<Self, HostError> {
37 Ok(Self {
38 map: MeteredOrdMap::from_map(map, host)?,
39 is_modified: false,
40 })
41 }
42}
43
44#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
48pub enum AccessType {
49 ReadOnly,
52 ReadWrite,
55}
56
57pub trait SnapshotSource {
61 fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError>;
64}
65
66#[derive(Clone, Default, Hash)]
76pub struct Footprint(pub FootprintMap);
77
78impl Footprint {
79 pub fn record_access(
80 &mut self,
81 key: &Rc<LedgerKey>,
82 ty: AccessType,
83 budget: &Budget,
84 ) -> Result<(), HostError> {
85 if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
86 match (existing, ty) {
87 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
88 (AccessType::ReadOnly, AccessType::ReadWrite) => {
89 self.0 = self.0.insert(Rc::clone(key), ty, budget)?;
92 Ok(())
93 }
94 (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
95 (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
96 }
97 } else {
98 self.0 = self.0.insert(Rc::clone(key), ty, budget)?;
99 Ok(())
100 }
101 }
102
103 pub fn enforce_access(
104 &mut self,
105 key: &Rc<LedgerKey>,
106 ty: AccessType,
107 budget: &Budget,
108 ) -> Result<(), HostError> {
109 if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
116 match (existing, ty) {
117 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
118 (AccessType::ReadOnly, AccessType::ReadWrite) => {
119 Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
120 }
121 (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
122 (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
123 }
124 } else {
125 Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
126 }
127 }
128}
129
130#[derive(Clone, Default)]
131pub enum FootprintMode {
132 Recording(Rc<dyn SnapshotSource>),
133 #[default]
134 Enforcing,
135}
136
137#[derive(Clone, Default)]
151pub struct Storage {
152 pub footprint: Footprint,
153 pub mode: FootprintMode,
154 pub map: StorageMap,
155}
156
157impl Storage {
160 pub fn check_supported_ledger_entry_type(le: &LedgerEntry) -> Result<(), HostError> {
166 use crate::xdr::LedgerEntryData::*;
167 match le.data {
168 Account(_) | Trustline(_) | ContractData(_) | ContractCode(_) => Ok(()),
169 Offer(_) | Data(_) | ClaimableBalance(_) | LiquidityPool(_) | ConfigSetting(_)
170 | Ttl(_) => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
171 }
172 }
173
174 pub fn check_supported_ledger_key_type(lk: &LedgerKey) -> Result<(), HostError> {
180 use LedgerKey::*;
181 match lk {
182 Account(_) | Trustline(_) | ContractData(_) | ContractCode(_) => Ok(()),
183 Offer(_) | Data(_) | ClaimableBalance(_) | LiquidityPool(_) | ConfigSetting(_)
184 | Ttl(_) => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
185 }
186 }
187
188 pub fn with_enforcing_footprint_and_map(footprint: Footprint, map: StorageMap) -> Self {
192 Self {
193 mode: FootprintMode::Enforcing,
194 footprint,
195 map,
196 }
197 }
198
199 pub fn with_recording_footprint(src: Rc<dyn SnapshotSource>) -> Self {
202 Self {
203 mode: FootprintMode::Recording(src),
204 footprint: Footprint::default(),
205 map: Default::default(),
206 }
207 }
208
209 fn try_get_full(
211 &mut self,
212 key: &Rc<LedgerKey>,
213 budget: &Budget,
214 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
215 let _span = tracy_span!("storage get");
216 Self::check_supported_ledger_key_type(key)?;
217 self.prepare_read_only_access(key, budget)?;
218 match self.map.get::<Rc<LedgerKey>>(key, budget)? {
219 None => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
222 Some(pair_option) => Ok(pair_option.clone()),
223 }
224 }
225
226 pub(crate) fn try_get_full_with_host(
227 &mut self,
228 key: &Rc<LedgerKey>,
229 host: &Host,
230 key_val: Option<Val>,
231 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
232 let res = self
233 .try_get_full(key, host.as_budget())
234 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))?;
235
236 #[cfg(any(test, feature = "testutils"))]
237 if !host.check_if_entry_is_live(key.as_ref(), &res, key_val)? {
238 return Ok(None);
239 }
240
241 Ok(res)
242 }
243
244 pub fn get(
256 &mut self,
257 key: &Rc<LedgerKey>,
258 budget: &Budget,
259 ) -> Result<Rc<LedgerEntry>, HostError> {
260 self.try_get_full(key, budget)?
261 .ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
262 .map(|e| e.0)
263 }
264 pub(crate) fn get_with_host(
265 &mut self,
266 key: &Rc<LedgerKey>,
267 host: &Host,
268 key_val: Option<Val>,
269 ) -> Result<Rc<LedgerEntry>, HostError> {
270 self.try_get_full_with_host(key, host, key_val)?
271 .ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
272 .map(|e| e.0)
273 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
274 }
275
276 pub(crate) fn try_get(
279 &mut self,
280 key: &Rc<LedgerKey>,
281 host: &Host,
282 key_val: Option<Val>,
283 ) -> Result<Option<Rc<LedgerEntry>>, HostError> {
284 self.try_get_full_with_host(key, host, key_val)
285 .map(|ok| ok.map(|pair| pair.0))
286 }
287
288 pub(crate) fn get_with_live_until_ledger(
303 &mut self,
304 key: &Rc<LedgerKey>,
305 host: &Host,
306 key_val: Option<Val>,
307 ) -> Result<EntryWithLiveUntil, HostError> {
308 self.try_get_full_with_host(key, host, key_val)
309 .and_then(|maybe_entry| {
310 maybe_entry.ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
311 })
312 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
313 }
314
315 fn put_opt(
317 &mut self,
318 key: &Rc<LedgerKey>,
319 val: Option<EntryWithLiveUntil>,
320 budget: &Budget,
321 ) -> Result<(), HostError> {
322 Self::check_supported_ledger_key_type(key)?;
323 if let Some(le) = &val {
324 Self::check_supported_ledger_entry_type(&le.0)?;
325 }
326 let ty = AccessType::ReadWrite;
327 match self.mode {
328 FootprintMode::Recording(_) => {
329 self.footprint.record_access(key, ty, budget)?;
330 }
331 FootprintMode::Enforcing => {
332 self.footprint.enforce_access(key, ty, budget)?;
333 }
334 };
335 self.map = self.map.insert(Rc::clone(key), val, budget)?;
336 Ok(())
337 }
338
339 fn put_opt_with_host(
340 &mut self,
341 key: &Rc<LedgerKey>,
342 val: Option<EntryWithLiveUntil>,
343 host: &Host,
344 key_val: Option<Val>,
345 ) -> Result<(), HostError> {
346 let prev = host.as_budget().get_cpu_insns_consumed().unwrap();
347 #[cfg(any(test, feature = "testutils"))]
348 let _ = host.as_budget().with_observable_shadow_mode(|| {
349 let Ok(Some(entry_with_live_until)) =
350 self.map.get::<Rc<LedgerKey>>(key, host.as_budget())
351 else {
352 return Ok(());
353 };
354 let _ = host.check_if_entry_is_live(key.as_ref(), &entry_with_live_until, key_val)?;
355 Ok(())
356 })?;
357 let curr = host.as_budget().get_cpu_insns_consumed().unwrap();
358 assert_eq!(prev, curr);
359 self.put_opt(key, val, host.as_budget())
360 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
361 }
362
363 pub fn put(
373 &mut self,
374 key: &Rc<LedgerKey>,
375 val: &Rc<LedgerEntry>,
376 live_until_ledger: Option<u32>,
377 budget: &Budget,
378 ) -> Result<(), HostError> {
379 let _span = tracy_span!("storage put");
380 self.put_opt(key, Some((val.clone(), live_until_ledger)), budget)
381 }
382
383 pub(crate) fn put_with_host(
384 &mut self,
385 key: &Rc<LedgerKey>,
386 val: &Rc<LedgerEntry>,
387 live_until_ledger: Option<u32>,
388 host: &Host,
389 key_val: Option<Val>,
390 ) -> Result<(), HostError> {
391 let _span = tracy_span!("storage put");
392 self.put_opt_with_host(key, Some((val.clone(), live_until_ledger)), host, key_val)
393 }
394
395 pub fn del(&mut self, key: &Rc<LedgerKey>, budget: &Budget) -> Result<(), HostError> {
405 let _span = tracy_span!("storage del");
406 self.put_opt(key, None, budget)
407 }
408
409 pub(crate) fn del_with_host(
410 &mut self,
411 key: &Rc<LedgerKey>,
412 host: &Host,
413 key_val: Option<Val>,
414 ) -> Result<(), HostError> {
415 let _span = tracy_span!("storage del");
416 self.put_opt_with_host(key, None, host, key_val)
417 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
418 }
419
420 pub fn has(&mut self, key: &Rc<LedgerKey>, budget: &Budget) -> Result<bool, HostError> {
430 let _span = tracy_span!("storage has");
431 Self::check_supported_ledger_key_type(key)?;
432 self.prepare_read_only_access(key, budget)?;
433 Ok(self
434 .map
435 .get::<Rc<LedgerKey>>(key, budget)?
436 .ok_or_else(|| HostError::from((ScErrorType::Storage, ScErrorCode::InternalError)))?
439 .is_some())
440 }
441
442 pub(crate) fn has_with_host(
443 &mut self,
444 key: &Rc<LedgerKey>,
445 host: &Host,
446 key_val: Option<Val>,
447 ) -> Result<bool, HostError> {
448 let _span = tracy_span!("storage has");
449 Ok(self.try_get_full_with_host(key, host, key_val)?.is_some())
450 }
451
452 pub(crate) fn extend_ttl(
467 &mut self,
468 host: &Host,
469 key: Rc<LedgerKey>,
470 threshold: u32,
471 extend_to: u32,
472 key_val: Option<Val>,
473 ) -> Result<(), HostError> {
474 let _span = tracy_span!("extend key");
475 Self::check_supported_ledger_key_type(&key)?;
476
477 if threshold > extend_to {
478 return Err(host.err(
479 ScErrorType::Storage,
480 ScErrorCode::InvalidInput,
481 "threshold must be <= extend_to",
482 &[threshold.into(), extend_to.into()],
483 ));
484 }
485
486 let (entry, old_live_until) = self.get_with_live_until_ledger(&key, &host, key_val)?;
489 let old_live_until = old_live_until.ok_or_else(|| {
490 host.err(
491 ScErrorType::Storage,
492 ScErrorCode::InternalError,
493 "trying to extend invalid entry",
494 &[],
495 )
496 })?;
497
498 let ledger_seq: u32 = host.get_ledger_sequence()?.into();
499 if old_live_until < ledger_seq {
500 return Err(host.err(
501 ScErrorType::Storage,
502 ScErrorCode::InternalError,
503 "accessing no-longer-live entry",
504 &[old_live_until.into(), ledger_seq.into()],
505 ));
506 }
507
508 let mut new_live_until = host.with_ledger_info(|li| {
509 li.sequence_number.checked_add(extend_to).ok_or_else(|| {
510 HostError::from(Error::from_type_and_code(
515 ScErrorType::Context,
516 ScErrorCode::InternalError,
517 ))
518 })
519 })?;
520
521 if new_live_until > host.max_live_until_ledger()? {
522 if let Some(durability) = get_key_durability(&key) {
523 if matches!(durability, ContractDataDurability::Persistent) {
524 new_live_until = host.max_live_until_ledger()?;
525 } else {
526 return Err(host.err(
532 ScErrorType::Storage,
533 ScErrorCode::InvalidAction,
534 "trying to extend past max live_until ledger",
535 &[new_live_until.into()],
536 ));
537 }
538 } else {
539 return Err(host.err(
540 ScErrorType::Storage,
541 ScErrorCode::InternalError,
542 "durability is missing",
543 &[],
544 ));
545 }
546 }
547
548 if new_live_until > old_live_until && old_live_until.saturating_sub(ledger_seq) <= threshold
549 {
550 self.map = self.map.insert(
551 key,
552 Some((entry.clone(), Some(new_live_until))),
553 host.budget_ref(),
554 )?;
555 }
556 Ok(())
557 }
558
559 #[cfg(any(test, feature = "recording_mode"))]
560 pub(crate) fn get_snapshot_value(
561 &self,
562 host: &Host,
563 key: &Rc<LedgerKey>,
564 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
565 match &self.mode {
566 FootprintMode::Recording(snapshot) => snapshot.get(key),
567 FootprintMode::Enforcing => Err(host.err(
568 ScErrorType::Storage,
569 ScErrorCode::InternalError,
570 "trying to get snapshot value in enforcing mode",
571 &[],
572 )),
573 }
574 }
575
576 fn prepare_read_only_access(
577 &mut self,
578 key: &Rc<LedgerKey>,
579 budget: &Budget,
580 ) -> Result<(), HostError> {
581 let ty = AccessType::ReadOnly;
582 match self.mode {
583 FootprintMode::Recording(ref src) => {
584 self.footprint.record_access(key, ty, budget)?;
585 if !self.map.contains_key::<Rc<LedgerKey>>(key, budget)? {
588 let value = src.get(&key)?;
589 self.map = self.map.insert(key.clone(), value, budget)?;
590 }
591 }
592 FootprintMode::Enforcing => {
593 self.footprint.enforce_access(key, ty, budget)?;
594 }
595 };
596 Ok(())
597 }
598
599 #[cfg(any(test, feature = "testutils"))]
600 pub(crate) fn reset_footprint(&mut self) {
601 self.footprint = Footprint::default();
602 }
603}
604
605fn get_key_type_string_for_error(lk: &LedgerKey) -> &str {
606 match lk {
607 LedgerKey::ContractData(cd) => match cd.key {
608 ScVal::LedgerKeyContractInstance => "contract instance",
609 ScVal::LedgerKeyNonce(_) => "nonce",
610 _ => "contract data key",
611 },
612 LedgerKey::ContractCode(_) => "contract code",
613 LedgerKey::Account(_) => "account",
614 LedgerKey::Trustline(_) => "account trustline",
615 _ => "ledger key",
619 }
620}
621
622impl Host {
623 fn decorate_storage_error(
624 &self,
625 err: HostError,
626 lk: &LedgerKey,
627 key_val: Option<Val>,
628 ) -> HostError {
629 let mut err = err;
630 self.with_debug_mode(|| {
631 if !err.error.is_type(ScErrorType::Storage) {
632 return Ok(());
633 }
634 if !err.error.is_code(ScErrorCode::ExceededLimit)
635 && !err.error.is_code(ScErrorCode::MissingValue)
636 {
637 return Ok(());
638 }
639
640 let key_type_str = get_key_type_string_for_error(lk);
641 let can_create_new_objects = err.error.is_code(ScErrorCode::ExceededLimit);
648 let args = self
649 .get_args_for_error(lk, key_val, can_create_new_objects)
650 .unwrap_or_else(|_| vec![]);
651 if err.error.is_code(ScErrorCode::ExceededLimit) {
652 err = self.err(
653 ScErrorType::Storage,
654 ScErrorCode::ExceededLimit,
655 format!("trying to access {} outside of the footprint", key_type_str).as_str(),
656 args.as_slice(),
657 );
658 } else if err.error.is_code(ScErrorCode::MissingValue) {
659 err = self.err(
660 ScErrorType::Storage,
661 ScErrorCode::MissingValue,
662 format!("trying to get non-existing value for {}", key_type_str).as_str(),
663 args.as_slice(),
664 );
665 }
666
667 Ok(())
668 });
669 err
670 }
671
672 fn get_args_for_error(
673 &self,
674 lk: &LedgerKey,
675 key_val: Option<Val>,
676 can_create_new_objects: bool,
677 ) -> Result<Vec<Val>, HostError> {
678 let mut res = vec![];
679 match lk {
680 LedgerKey::ContractData(cd) => {
681 if can_create_new_objects {
682 let address_val = self
683 .add_host_object(cd.contract.metered_clone(self.as_budget())?)?
684 .into();
685 res.push(address_val);
686 }
687 match &cd.key {
688 ScVal::LedgerKeyContractInstance => (),
689 ScVal::LedgerKeyNonce(n) => {
690 if can_create_new_objects {
691 res.push(self.add_host_object(n.nonce)?.into());
692 }
693 }
694 _ => {
695 if let Some(key) = key_val {
696 res.push(key);
697 }
698 }
699 }
700 }
701 LedgerKey::ContractCode(c) => {
702 if can_create_new_objects {
703 res.push(
704 self.add_host_object(self.scbytes_from_hash(&c.hash)?)?
705 .into(),
706 );
707 }
708 }
709 LedgerKey::Account(_) | LedgerKey::Trustline(_) => {
710 if can_create_new_objects {
711 res.push(self.account_address_from_key(lk)?)
712 }
713 }
714 _ => (),
718 };
719 Ok(res)
720 }
721
722 #[cfg(any(test, feature = "testutils"))]
723 fn check_if_entry_is_live(
724 &self,
725 key: &LedgerKey,
726 entry_with_live_until: &Option<EntryWithLiveUntil>,
727 key_val: Option<Val>,
728 ) -> Result<bool, HostError> {
729 let Some((_, Some(live_until_ledger))) = &entry_with_live_until else {
730 return Ok(true);
731 };
732 let ledger_seq = self
733 .with_ledger_info(|li| Ok(li.sequence_number))
734 .unwrap_or_default();
735 if *live_until_ledger >= ledger_seq {
736 return Ok(true);
737 }
738 match get_key_durability(key) {
739 Some(ContractDataDurability::Temporary) => Ok(false),
740 Some(ContractDataDurability::Persistent) => {
741 let key_type_str = get_key_type_string_for_error(key);
742 let args = self
743 .as_budget()
744 .with_observable_shadow_mode(|| self.get_args_for_error(key, key_val, true))
745 .unwrap_or_else(|_| vec![]);
746 let msg = format!("[testing-only] Accessed {} key that has been archived. Important: this error may only appear in tests; in the real network contracts aren't called at all if any archived entry is accessed.", key_type_str);
747 Err(self.err(
748 ScErrorType::Storage,
749 ScErrorCode::InternalError,
750 msg.as_str(),
751 args.as_slice(),
752 ))
753 }
754 None => Ok(true),
755 }
756 }
757}