1use std::rc::Rc;
11
12use crate::budget::AsBudget;
13use crate::host::metered_clone::{MeteredClone, MeteredIterator};
14use crate::{
15 budget::Budget,
16 host::metered_map::MeteredOrdMap,
17 ledger_info::get_key_durability,
18 xdr::{
19 ContractDataDurability, LedgerEntry, LedgerKey, ScContractInstance, ScErrorCode,
20 ScErrorType, ScVal,
21 },
22 Env, Error, Host, HostError, Val,
23};
24
25pub type FootprintMap = MeteredOrdMap<Rc<LedgerKey>, AccessType, Budget>;
26pub type EntryWithLiveUntil = (Rc<LedgerEntry>, Option<u32>);
27pub type StorageMap = MeteredOrdMap<Rc<LedgerKey>, Option<EntryWithLiveUntil>, Budget>;
28
29#[derive(Clone, Hash)]
33pub(crate) struct InstanceStorageMap {
34 pub(crate) map: MeteredOrdMap<Val, Val, Host>,
35 pub(crate) is_modified: bool,
36}
37
38impl InstanceStorageMap {
39 pub(crate) fn from_map(map: Vec<(Val, Val)>, host: &Host) -> Result<Self, HostError> {
40 Ok(Self {
41 map: MeteredOrdMap::from_map(map, host)?,
42 is_modified: false,
43 })
44 }
45
46 pub(crate) fn from_instance_xdr(
47 instance: &ScContractInstance,
48 host: &Host,
49 ) -> Result<Self, HostError> {
50 Self::from_map(
51 instance.storage.as_ref().map_or_else(
52 || Ok(vec![]),
53 |m| {
54 m.iter()
55 .map(|i| {
56 Ok((
57 host.to_valid_host_val(&i.key)?,
58 host.to_valid_host_val(&i.val)?,
59 ))
60 })
61 .metered_collect::<Result<Vec<(Val, Val)>, HostError>>(host)?
62 },
63 )?,
64 host,
65 )
66 }
67}
68
69#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
73pub enum AccessType {
74 ReadOnly,
77 ReadWrite,
80}
81pub trait SnapshotSource {
87 fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError>;
90}
91
92#[derive(Clone, Default, Hash)]
102pub struct Footprint(pub FootprintMap);
103
104impl Footprint {
105 #[cfg(any(test, feature = "recording_mode"))]
106 pub(crate) fn record_access(
107 &mut self,
108 key: &Rc<LedgerKey>,
109 ty: AccessType,
110 budget: &Budget,
111 ) -> Result<(), HostError> {
112 if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
113 match (existing, ty) {
114 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
115 (AccessType::ReadOnly, AccessType::ReadWrite) => {
116 self.0 = self.0.insert(Rc::clone(key), ty, budget)?;
119 Ok(())
120 }
121 (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
122 (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
123 }
124 } else {
125 self.0 = self.0.insert(Rc::clone(key), ty, budget)?;
126 Ok(())
127 }
128 }
129
130 pub(crate) fn enforce_access(
131 &mut self,
132 key: &Rc<LedgerKey>,
133 ty: AccessType,
134 budget: &Budget,
135 ) -> Result<(), HostError> {
136 if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
143 match (existing, ty) {
144 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
145 (AccessType::ReadOnly, AccessType::ReadWrite) => {
146 Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
147 }
148 (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
149 (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
150 }
151 } else {
152 Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
153 }
154 }
155}
156
157#[derive(Clone, Default)]
158pub(crate) enum FootprintMode {
159 #[cfg(any(test, feature = "recording_mode"))]
160 Recording(Rc<dyn SnapshotSource>),
161 #[default]
162 Enforcing,
163}
164
165#[derive(Clone, Default)]
179pub struct Storage {
180 pub footprint: Footprint,
181 pub(crate) mode: FootprintMode,
182 pub map: StorageMap,
183}
184
185impl Storage {
188 pub fn check_supported_ledger_entry_type(le: &LedgerEntry) -> Result<(), HostError> {
194 use crate::xdr::LedgerEntryData::*;
195 match le.data {
196 Account(_) | Trustline(_) | ContractData(_) | ContractCode(_) => Ok(()),
197 Offer(_) | Data(_) | ClaimableBalance(_) | LiquidityPool(_) | ConfigSetting(_)
198 | Ttl(_) => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
199 }
200 }
201
202 pub fn check_supported_ledger_key_type(lk: &LedgerKey) -> Result<(), HostError> {
208 use LedgerKey::*;
209 match lk {
210 Account(_) | Trustline(_) | ContractData(_) | ContractCode(_) => Ok(()),
211 Offer(_) | Data(_) | ClaimableBalance(_) | LiquidityPool(_) | ConfigSetting(_)
212 | Ttl(_) => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
213 }
214 }
215
216 pub fn with_enforcing_footprint_and_map(footprint: Footprint, map: StorageMap) -> Self {
220 Self {
221 mode: FootprintMode::Enforcing,
222 footprint,
223 map,
224 }
225 }
226
227 #[cfg(any(test, feature = "recording_mode"))]
230 pub fn with_recording_footprint(src: Rc<dyn SnapshotSource>) -> Self {
231 Self {
232 mode: FootprintMode::Recording(src),
233 footprint: Footprint::default(),
234 map: Default::default(),
235 }
236 }
237
238 fn try_get_full_helper(
240 &mut self,
241 key: &Rc<LedgerKey>,
242 host: &Host,
243 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
244 let _span = tracy_span!("storage get");
245 Self::check_supported_ledger_key_type(key)?;
246 self.prepare_read_only_access(key, host)?;
247 match self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())? {
248 None => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
251 Some(pair_option) => Ok(pair_option.clone()),
252 }
253 }
254
255 pub(crate) fn try_get_full(
256 &mut self,
257 key: &Rc<LedgerKey>,
258 host: &Host,
259 key_val: Option<Val>,
260 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
261 let res = self
262 .try_get_full_helper(key, host)
263 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))?;
264 Ok(res)
265 }
266
267 pub(crate) fn get(
268 &mut self,
269 key: &Rc<LedgerKey>,
270 host: &Host,
271 key_val: Option<Val>,
272 ) -> Result<Rc<LedgerEntry>, HostError> {
273 self.try_get_full(key, host, key_val)?
274 .ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
275 .map(|e| e.0)
276 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
277 }
278
279 pub(crate) fn try_get(
282 &mut self,
283 key: &Rc<LedgerKey>,
284 host: &Host,
285 key_val: Option<Val>,
286 ) -> Result<Option<Rc<LedgerEntry>>, HostError> {
287 self.try_get_full(key, host, key_val)
288 .map(|ok| ok.map(|pair| pair.0))
289 }
290
291 pub(crate) fn get_with_live_until_ledger(
306 &mut self,
307 key: &Rc<LedgerKey>,
308 host: &Host,
309 key_val: Option<Val>,
310 ) -> Result<EntryWithLiveUntil, HostError> {
311 self.try_get_full(key, host, key_val)
312 .and_then(|maybe_entry| {
313 maybe_entry.ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
314 })
315 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
316 }
317
318 fn put_opt_helper(
320 &mut self,
321 key: &Rc<LedgerKey>,
322 val: Option<EntryWithLiveUntil>,
323 host: &Host,
324 ) -> Result<(), HostError> {
325 Self::check_supported_ledger_key_type(key)?;
326 if let Some(le) = &val {
327 Self::check_supported_ledger_entry_type(&le.0)?;
328 }
329 #[cfg(any(test, feature = "recording_mode"))]
330 self.handle_maybe_expired_entry(&key, host)?;
331
332 let ty = AccessType::ReadWrite;
333 match &self.mode {
334 #[cfg(any(test, feature = "recording_mode"))]
335 FootprintMode::Recording(_) => {
336 self.footprint.record_access(key, ty, host.budget_ref())?;
337 }
338 FootprintMode::Enforcing => {
339 self.footprint.enforce_access(key, ty, host.budget_ref())?;
340 }
341 };
342 self.map = self.map.insert(Rc::clone(key), val, host.budget_ref())?;
343 Ok(())
344 }
345
346 fn put_opt(
347 &mut self,
348 key: &Rc<LedgerKey>,
349 val: Option<EntryWithLiveUntil>,
350 host: &Host,
351 key_val: Option<Val>,
352 ) -> Result<(), HostError> {
353 self.put_opt_helper(key, val, host)
354 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
355 }
356
357 pub(crate) fn put(
367 &mut self,
368 key: &Rc<LedgerKey>,
369 val: &Rc<LedgerEntry>,
370 live_until_ledger: Option<u32>,
371 host: &Host,
372 key_val: Option<Val>,
373 ) -> Result<(), HostError> {
374 let _span = tracy_span!("storage put");
375 self.put_opt(key, Some((val.clone(), live_until_ledger)), host, key_val)
376 }
377
378 pub(crate) fn del(
388 &mut self,
389 key: &Rc<LedgerKey>,
390 host: &Host,
391 key_val: Option<Val>,
392 ) -> Result<(), HostError> {
393 let _span = tracy_span!("storage del");
394 self.put_opt(key, None, host, key_val)
395 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
396 }
397
398 pub(crate) fn has(
408 &mut self,
409 key: &Rc<LedgerKey>,
410 host: &Host,
411 key_val: Option<Val>,
412 ) -> Result<bool, HostError> {
413 let _span = tracy_span!("storage has");
414 Ok(self.try_get_full(key, host, key_val)?.is_some())
415 }
416
417 pub(crate) fn extend_ttl(
432 &mut self,
433 host: &Host,
434 key: Rc<LedgerKey>,
435 threshold: u32,
436 extend_to: u32,
437 key_val: Option<Val>,
438 ) -> Result<(), HostError> {
439 let _span = tracy_span!("extend key");
440 Self::check_supported_ledger_key_type(&key)?;
441
442 if threshold > extend_to {
443 return Err(host.err(
444 ScErrorType::Storage,
445 ScErrorCode::InvalidInput,
446 "threshold must be <= extend_to",
447 &[threshold.into(), extend_to.into()],
448 ));
449 }
450 #[cfg(any(test, feature = "recording_mode"))]
451 self.handle_maybe_expired_entry(&key, host)?;
452
453 let (entry, old_live_until) = self.get_with_live_until_ledger(&key, &host, key_val)?;
456 let old_live_until = old_live_until.ok_or_else(|| {
457 host.err(
458 ScErrorType::Storage,
459 ScErrorCode::InternalError,
460 "trying to extend invalid entry",
461 &[],
462 )
463 })?;
464
465 let ledger_seq: u32 = host.get_ledger_sequence()?.into();
466 if old_live_until < ledger_seq {
467 return Err(host.err(
468 ScErrorType::Storage,
469 ScErrorCode::InternalError,
470 "accessing no-longer-live entry",
471 &[old_live_until.into(), ledger_seq.into()],
472 ));
473 }
474
475 let mut new_live_until = host.with_ledger_info(|li| {
476 li.sequence_number.checked_add(extend_to).ok_or_else(|| {
477 HostError::from(Error::from_type_and_code(
482 ScErrorType::Context,
483 ScErrorCode::InternalError,
484 ))
485 })
486 })?;
487
488 if new_live_until > host.max_live_until_ledger()? {
489 if let Some(durability) = get_key_durability(&key) {
490 if matches!(durability, ContractDataDurability::Persistent) {
491 new_live_until = host.max_live_until_ledger()?;
492 } else {
493 return Err(host.err(
499 ScErrorType::Storage,
500 ScErrorCode::InvalidAction,
501 "trying to extend past max live_until ledger",
502 &[new_live_until.into()],
503 ));
504 }
505 } else {
506 return Err(host.err(
507 ScErrorType::Storage,
508 ScErrorCode::InternalError,
509 "durability is missing",
510 &[],
511 ));
512 }
513 }
514
515 if new_live_until > old_live_until && old_live_until.saturating_sub(ledger_seq) <= threshold
516 {
517 self.map = self.map.insert(
518 key,
519 Some((entry.clone(), Some(new_live_until))),
520 host.budget_ref(),
521 )?;
522 }
523 Ok(())
524 }
525
526 #[cfg(any(test, feature = "recording_mode"))]
527 pub(crate) fn is_key_live_in_snapshot(
530 &self,
531 host: &Host,
532 key: &Rc<LedgerKey>,
533 ) -> Result<bool, HostError> {
534 match &self.mode {
535 FootprintMode::Recording(snapshot) => {
536 let snapshot_value = snapshot.get(key)?;
537 if let Some((_, live_until_ledger)) = snapshot_value {
538 if let Some(live_until_ledger) = live_until_ledger {
539 let current_ledger_sequence =
540 host.with_ledger_info(|li| Ok(li.sequence_number))?;
541 Ok(live_until_ledger >= current_ledger_sequence)
542 } else {
543 Ok(true)
545 }
546 } else {
547 Ok(false)
549 }
550 }
551 FootprintMode::Enforcing => Err(host.err(
552 ScErrorType::Storage,
553 ScErrorCode::InternalError,
554 "trying to get snapshot value in enforcing mode",
555 &[],
556 )),
557 }
558 }
559
560 #[cfg(any(test, feature = "testutils"))]
563 pub(crate) fn get_from_map(
564 &self,
565 key: &Rc<LedgerKey>,
566 host: &Host,
567 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
568 match self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())? {
569 Some(pair_option) => Ok(pair_option.clone()),
570 None => Ok(None),
571 }
572 }
573
574 fn prepare_read_only_access(
575 &mut self,
576 key: &Rc<LedgerKey>,
577 host: &Host,
578 ) -> Result<(), HostError> {
579 let ty = AccessType::ReadOnly;
580 match self.mode {
581 #[cfg(any(test, feature = "recording_mode"))]
582 FootprintMode::Recording(ref src) => {
583 self.footprint.record_access(key, ty, host.budget_ref())?;
584 if !self
587 .map
588 .contains_key::<Rc<LedgerKey>>(key, host.budget_ref())?
589 {
590 let value = src.get(&key)?;
591 self.map = self.map.insert(key.clone(), value, host.budget_ref())?;
592 }
593 self.footprint.record_access(key, ty, host.budget_ref())?;
594 self.handle_maybe_expired_entry(key, host)?;
595 }
596 FootprintMode::Enforcing => {
597 self.footprint.enforce_access(key, ty, host.budget_ref())?;
598 }
599 };
600 Ok(())
601 }
602
603 #[cfg(any(test, feature = "recording_mode"))]
604 fn handle_maybe_expired_entry(
605 &mut self,
606 key: &Rc<LedgerKey>,
607 host: &Host,
608 ) -> Result<(), HostError> {
609 host.with_ledger_info(|li| {
610 let budget = host.budget_ref();
611 if let Some(Some((entry, live_until))) =
612 self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())?
613 {
614 if let Some(durability) = get_key_durability(key.as_ref()) {
615 let live_until = live_until.ok_or_else(|| {
616 host.err(
617 ScErrorType::Storage,
618 ScErrorCode::InternalError,
619 "unexpected contract entry without TTL",
620 &[],
621 )
622 })?;
623 if live_until < li.sequence_number {
624 match durability {
625 ContractDataDurability::Temporary => {
626 self.map = self.map.insert(key.clone(), None, budget)?;
627 }
628 ContractDataDurability::Persistent => {
629 self.footprint
630 .record_access(key, AccessType::ReadWrite, budget)?;
631 let new_live_until = li
632 .min_live_until_ledger_checked(
633 ContractDataDurability::Persistent,
634 )
635 .ok_or_else(|| {
636 host.err(ScErrorType::Storage,
637 ScErrorCode::InternalError,
638 "persistent entry TTL overflow, ledger is mis-configured",
639 &[],)
640 })?;
641 self.map = self.map.insert(
642 key.clone(),
643 Some((entry.clone(), Some(new_live_until))),
644 budget,
645 )?;
646 }
647 };
648 }
649 }
650 }
651 Ok(())
652 })
653 }
654
655 #[cfg(any(test, feature = "testutils"))]
656 pub(crate) fn reset_footprint(&mut self) {
657 self.footprint = Footprint::default();
658 }
659}
660
661fn get_key_type_string_for_error(lk: &LedgerKey) -> &str {
662 match lk {
663 LedgerKey::ContractData(cd) => match cd.key {
664 ScVal::LedgerKeyContractInstance => "contract instance",
665 ScVal::LedgerKeyNonce(_) => "nonce",
666 _ => "contract data key",
667 },
668 LedgerKey::ContractCode(_) => "contract code",
669 LedgerKey::Account(_) => "account",
670 LedgerKey::Trustline(_) => "account trustline",
671 _ => "ledger key",
675 }
676}
677
678impl Host {
679 fn decorate_storage_error(
680 &self,
681 err: HostError,
682 lk: &LedgerKey,
683 key_val: Option<Val>,
684 ) -> HostError {
685 let mut err = err;
686 self.with_debug_mode_allowing_new_objects(
687 || {
688 if !err.error.is_type(ScErrorType::Storage) {
689 return Ok(());
690 }
691 if !err.error.is_code(ScErrorCode::ExceededLimit)
692 && !err.error.is_code(ScErrorCode::MissingValue)
693 {
694 return Ok(());
695 }
696
697 let key_type_str = get_key_type_string_for_error(lk);
698 let can_create_new_objects = err.error.is_code(ScErrorCode::ExceededLimit);
705 let args = self
706 .get_args_for_error(lk, key_val, can_create_new_objects)
707 .unwrap_or_else(|_| vec![]);
708 if err.error.is_code(ScErrorCode::ExceededLimit) {
709 err = self.err(
710 ScErrorType::Storage,
711 ScErrorCode::ExceededLimit,
712 format!("trying to access {} outside of the footprint", key_type_str)
713 .as_str(),
714 args.as_slice(),
715 );
716 } else if err.error.is_code(ScErrorCode::MissingValue) {
717 err = self.err(
718 ScErrorType::Storage,
719 ScErrorCode::MissingValue,
720 format!("trying to get non-existing value for {}", key_type_str).as_str(),
721 args.as_slice(),
722 );
723 }
724
725 Ok(())
726 },
727 true,
728 );
729 err
730 }
731
732 fn get_args_for_error(
733 &self,
734 lk: &LedgerKey,
735 key_val: Option<Val>,
736 can_create_new_objects: bool,
737 ) -> Result<Vec<Val>, HostError> {
738 let mut res = vec![];
739 match lk {
740 LedgerKey::ContractData(cd) => {
741 if can_create_new_objects {
742 let address_val = self
743 .add_host_object(cd.contract.metered_clone(self.as_budget())?)?
744 .into();
745 res.push(address_val);
746 }
747 match &cd.key {
748 ScVal::LedgerKeyContractInstance => (),
749 ScVal::LedgerKeyNonce(n) => {
750 if can_create_new_objects {
751 res.push(self.add_host_object(n.nonce)?.into());
752 }
753 }
754 _ => {
755 if let Some(key) = key_val {
756 res.push(key);
757 }
758 }
759 }
760 }
761 LedgerKey::ContractCode(c) => {
762 if can_create_new_objects {
763 res.push(
764 self.add_host_object(self.scbytes_from_hash(&c.hash)?)?
765 .into(),
766 );
767 }
768 }
769 LedgerKey::Account(_) | LedgerKey::Trustline(_) => {
770 if can_create_new_objects {
771 res.push(self.account_address_from_key(lk)?)
772 }
773 }
774 _ => (),
778 };
779 Ok(res)
780 }
781}
782
783#[cfg(any(test, feature = "recording_mode"))]
784pub(crate) fn is_persistent_key(key: &LedgerKey) -> bool {
785 match key {
786 LedgerKey::ContractData(k) => {
787 matches!(k.durability, ContractDataDurability::Persistent)
788 }
789 LedgerKey::ContractCode(_) => true,
790 _ => false,
791 }
792}