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}
56pub trait SnapshotSource {
62 fn get(&self, key: &Rc<LedgerKey>) -> Result<Option<EntryWithLiveUntil>, HostError>;
65}
66
67#[derive(Clone, Default, Hash)]
77pub struct Footprint(pub FootprintMap);
78
79impl Footprint {
80 #[cfg(any(test, feature = "recording_mode"))]
81 pub(crate) fn record_access(
82 &mut self,
83 key: &Rc<LedgerKey>,
84 ty: AccessType,
85 budget: &Budget,
86 ) -> Result<(), HostError> {
87 if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
88 match (existing, ty) {
89 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
90 (AccessType::ReadOnly, AccessType::ReadWrite) => {
91 self.0 = self.0.insert(Rc::clone(key), ty, budget)?;
94 Ok(())
95 }
96 (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
97 (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
98 }
99 } else {
100 self.0 = self.0.insert(Rc::clone(key), ty, budget)?;
101 Ok(())
102 }
103 }
104
105 pub(crate) fn enforce_access(
106 &mut self,
107 key: &Rc<LedgerKey>,
108 ty: AccessType,
109 budget: &Budget,
110 ) -> Result<(), HostError> {
111 if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
118 match (existing, ty) {
119 (AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
120 (AccessType::ReadOnly, AccessType::ReadWrite) => {
121 Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
122 }
123 (AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
124 (AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
125 }
126 } else {
127 Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
128 }
129 }
130}
131
132#[derive(Clone, Default)]
133pub(crate) enum FootprintMode {
134 #[cfg(any(test, feature = "recording_mode"))]
135 Recording(Rc<dyn SnapshotSource>),
136 #[default]
137 Enforcing,
138}
139
140#[derive(Clone, Default)]
154pub struct Storage {
155 pub footprint: Footprint,
156 pub(crate) mode: FootprintMode,
157 pub map: StorageMap,
158}
159
160impl Storage {
163 pub fn check_supported_ledger_entry_type(le: &LedgerEntry) -> Result<(), HostError> {
169 use crate::xdr::LedgerEntryData::*;
170 match le.data {
171 Account(_) | Trustline(_) | ContractData(_) | ContractCode(_) => Ok(()),
172 Offer(_) | Data(_) | ClaimableBalance(_) | LiquidityPool(_) | ConfigSetting(_)
173 | Ttl(_) => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
174 }
175 }
176
177 pub fn check_supported_ledger_key_type(lk: &LedgerKey) -> Result<(), HostError> {
183 use LedgerKey::*;
184 match lk {
185 Account(_) | Trustline(_) | ContractData(_) | ContractCode(_) => Ok(()),
186 Offer(_) | Data(_) | ClaimableBalance(_) | LiquidityPool(_) | ConfigSetting(_)
187 | Ttl(_) => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
188 }
189 }
190
191 pub fn with_enforcing_footprint_and_map(footprint: Footprint, map: StorageMap) -> Self {
195 Self {
196 mode: FootprintMode::Enforcing,
197 footprint,
198 map,
199 }
200 }
201
202 #[cfg(any(test, feature = "recording_mode"))]
205 pub fn with_recording_footprint(src: Rc<dyn SnapshotSource>) -> Self {
206 Self {
207 mode: FootprintMode::Recording(src),
208 footprint: Footprint::default(),
209 map: Default::default(),
210 }
211 }
212
213 fn try_get_full_helper(
215 &mut self,
216 key: &Rc<LedgerKey>,
217 host: &Host,
218 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
219 let _span = tracy_span!("storage get");
220 Self::check_supported_ledger_key_type(key)?;
221 self.prepare_read_only_access(key, host)?;
222 match self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())? {
223 None => Err((ScErrorType::Storage, ScErrorCode::InternalError).into()),
226 Some(pair_option) => Ok(pair_option.clone()),
227 }
228 }
229
230 pub(crate) fn try_get_full(
231 &mut self,
232 key: &Rc<LedgerKey>,
233 host: &Host,
234 key_val: Option<Val>,
235 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
236 let res = self
237 .try_get_full_helper(key, host)
238 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))?;
239 Ok(res)
240 }
241
242 pub(crate) fn get(
243 &mut self,
244 key: &Rc<LedgerKey>,
245 host: &Host,
246 key_val: Option<Val>,
247 ) -> Result<Rc<LedgerEntry>, HostError> {
248 self.try_get_full(key, host, key_val)?
249 .ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
250 .map(|e| e.0)
251 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
252 }
253
254 pub(crate) fn try_get(
257 &mut self,
258 key: &Rc<LedgerKey>,
259 host: &Host,
260 key_val: Option<Val>,
261 ) -> Result<Option<Rc<LedgerEntry>>, HostError> {
262 self.try_get_full(key, host, key_val)
263 .map(|ok| ok.map(|pair| pair.0))
264 }
265
266 pub(crate) fn get_with_live_until_ledger(
281 &mut self,
282 key: &Rc<LedgerKey>,
283 host: &Host,
284 key_val: Option<Val>,
285 ) -> Result<EntryWithLiveUntil, HostError> {
286 self.try_get_full(key, host, key_val)
287 .and_then(|maybe_entry| {
288 maybe_entry.ok_or_else(|| (ScErrorType::Storage, ScErrorCode::MissingValue).into())
289 })
290 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
291 }
292
293 fn put_opt_helper(
295 &mut self,
296 key: &Rc<LedgerKey>,
297 val: Option<EntryWithLiveUntil>,
298 host: &Host,
299 ) -> Result<(), HostError> {
300 Self::check_supported_ledger_key_type(key)?;
301 if let Some(le) = &val {
302 Self::check_supported_ledger_entry_type(&le.0)?;
303 }
304 #[cfg(any(test, feature = "recording_mode"))]
305 self.handle_maybe_expired_entry(&key, host)?;
306
307 let ty = AccessType::ReadWrite;
308 match &self.mode {
309 #[cfg(any(test, feature = "recording_mode"))]
310 FootprintMode::Recording(_) => {
311 self.footprint.record_access(key, ty, host.budget_ref())?;
312 }
313 FootprintMode::Enforcing => {
314 self.footprint.enforce_access(key, ty, host.budget_ref())?;
315 }
316 };
317 self.map = self.map.insert(Rc::clone(key), val, host.budget_ref())?;
318 Ok(())
319 }
320
321 fn put_opt(
322 &mut self,
323 key: &Rc<LedgerKey>,
324 val: Option<EntryWithLiveUntil>,
325 host: &Host,
326 key_val: Option<Val>,
327 ) -> Result<(), HostError> {
328 self.put_opt_helper(key, val, host)
329 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
330 }
331
332 pub(crate) fn put(
342 &mut self,
343 key: &Rc<LedgerKey>,
344 val: &Rc<LedgerEntry>,
345 live_until_ledger: Option<u32>,
346 host: &Host,
347 key_val: Option<Val>,
348 ) -> Result<(), HostError> {
349 let _span = tracy_span!("storage put");
350 self.put_opt(key, Some((val.clone(), live_until_ledger)), host, key_val)
351 }
352
353 pub(crate) fn del(
363 &mut self,
364 key: &Rc<LedgerKey>,
365 host: &Host,
366 key_val: Option<Val>,
367 ) -> Result<(), HostError> {
368 let _span = tracy_span!("storage del");
369 self.put_opt(key, None, host, key_val)
370 .map_err(|e| host.decorate_storage_error(e, key.as_ref(), key_val))
371 }
372
373 pub(crate) fn has(
383 &mut self,
384 key: &Rc<LedgerKey>,
385 host: &Host,
386 key_val: Option<Val>,
387 ) -> Result<bool, HostError> {
388 let _span = tracy_span!("storage has");
389 Ok(self.try_get_full(key, host, key_val)?.is_some())
390 }
391
392 pub(crate) fn extend_ttl(
407 &mut self,
408 host: &Host,
409 key: Rc<LedgerKey>,
410 threshold: u32,
411 extend_to: u32,
412 key_val: Option<Val>,
413 ) -> Result<(), HostError> {
414 let _span = tracy_span!("extend key");
415 Self::check_supported_ledger_key_type(&key)?;
416
417 if threshold > extend_to {
418 return Err(host.err(
419 ScErrorType::Storage,
420 ScErrorCode::InvalidInput,
421 "threshold must be <= extend_to",
422 &[threshold.into(), extend_to.into()],
423 ));
424 }
425 #[cfg(any(test, feature = "recording_mode"))]
426 self.handle_maybe_expired_entry(&key, host)?;
427
428 let (entry, old_live_until) = self.get_with_live_until_ledger(&key, &host, key_val)?;
431 let old_live_until = old_live_until.ok_or_else(|| {
432 host.err(
433 ScErrorType::Storage,
434 ScErrorCode::InternalError,
435 "trying to extend invalid entry",
436 &[],
437 )
438 })?;
439
440 let ledger_seq: u32 = host.get_ledger_sequence()?.into();
441 if old_live_until < ledger_seq {
442 return Err(host.err(
443 ScErrorType::Storage,
444 ScErrorCode::InternalError,
445 "accessing no-longer-live entry",
446 &[old_live_until.into(), ledger_seq.into()],
447 ));
448 }
449
450 let mut new_live_until = host.with_ledger_info(|li| {
451 li.sequence_number.checked_add(extend_to).ok_or_else(|| {
452 HostError::from(Error::from_type_and_code(
457 ScErrorType::Context,
458 ScErrorCode::InternalError,
459 ))
460 })
461 })?;
462
463 if new_live_until > host.max_live_until_ledger()? {
464 if let Some(durability) = get_key_durability(&key) {
465 if matches!(durability, ContractDataDurability::Persistent) {
466 new_live_until = host.max_live_until_ledger()?;
467 } else {
468 return Err(host.err(
474 ScErrorType::Storage,
475 ScErrorCode::InvalidAction,
476 "trying to extend past max live_until ledger",
477 &[new_live_until.into()],
478 ));
479 }
480 } else {
481 return Err(host.err(
482 ScErrorType::Storage,
483 ScErrorCode::InternalError,
484 "durability is missing",
485 &[],
486 ));
487 }
488 }
489
490 if new_live_until > old_live_until && old_live_until.saturating_sub(ledger_seq) <= threshold
491 {
492 self.map = self.map.insert(
493 key,
494 Some((entry.clone(), Some(new_live_until))),
495 host.budget_ref(),
496 )?;
497 }
498 Ok(())
499 }
500
501 #[cfg(any(test, feature = "recording_mode"))]
502 pub(crate) fn get_snapshot_value(
503 &self,
504 host: &Host,
505 key: &Rc<LedgerKey>,
506 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
507 match &self.mode {
508 FootprintMode::Recording(snapshot) => snapshot.get(key),
509 FootprintMode::Enforcing => Err(host.err(
510 ScErrorType::Storage,
511 ScErrorCode::InternalError,
512 "trying to get snapshot value in enforcing mode",
513 &[],
514 )),
515 }
516 }
517
518 #[cfg(any(test, feature = "testutils"))]
521 pub(crate) fn get_from_map(
522 &self,
523 key: &Rc<LedgerKey>,
524 host: &Host,
525 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
526 match self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())? {
527 Some(pair_option) => Ok(pair_option.clone()),
528 None => Ok(None),
529 }
530 }
531
532 fn prepare_read_only_access(
533 &mut self,
534 key: &Rc<LedgerKey>,
535 host: &Host,
536 ) -> Result<(), HostError> {
537 let ty = AccessType::ReadOnly;
538 match self.mode {
539 #[cfg(any(test, feature = "recording_mode"))]
540 FootprintMode::Recording(ref src) => {
541 self.footprint.record_access(key, ty, host.budget_ref())?;
542 if !self
545 .map
546 .contains_key::<Rc<LedgerKey>>(key, host.budget_ref())?
547 {
548 let value = src.get(&key)?;
549 self.map = self.map.insert(key.clone(), value, host.budget_ref())?;
550 }
551 self.footprint.record_access(key, ty, host.budget_ref())?;
552 self.handle_maybe_expired_entry(key, host)?;
553 }
554 FootprintMode::Enforcing => {
555 self.footprint.enforce_access(key, ty, host.budget_ref())?;
556 }
557 };
558 Ok(())
559 }
560
561 #[cfg(any(test, feature = "recording_mode"))]
562 fn handle_maybe_expired_entry(
563 &mut self,
564 key: &Rc<LedgerKey>,
565 host: &Host,
566 ) -> Result<(), HostError> {
567 host.with_ledger_info(|li| {
568 let budget = host.budget_ref();
569 if let Some(Some((entry, live_until))) =
570 self.map.get::<Rc<LedgerKey>>(key, host.budget_ref())?
571 {
572 if let Some(durability) = get_key_durability(key.as_ref()) {
573 let live_until = live_until.ok_or_else(|| {
574 host.err(
575 ScErrorType::Storage,
576 ScErrorCode::InternalError,
577 "unexpected contract entry without TTL",
578 &[],
579 )
580 })?;
581 if live_until < li.sequence_number {
582 match durability {
583 ContractDataDurability::Temporary => {
584 self.map = self.map.insert(key.clone(), None, budget)?;
585 }
586 ContractDataDurability::Persistent => {
587 self.footprint
588 .record_access(key, AccessType::ReadWrite, budget)?;
589 let new_live_until = li
590 .min_live_until_ledger_checked(
591 ContractDataDurability::Persistent,
592 )
593 .ok_or_else(|| {
594 host.err(ScErrorType::Storage,
595 ScErrorCode::InternalError,
596 "persistent entry TTL overflow, ledger is mis-configured",
597 &[],)
598 })?;
599 self.map = self.map.insert(
600 key.clone(),
601 Some((entry.clone(), Some(new_live_until))),
602 budget,
603 )?;
604 }
605 };
606 }
607 }
608 }
609 Ok(())
610 })
611 }
612
613 #[cfg(any(test, feature = "testutils"))]
614 pub(crate) fn reset_footprint(&mut self) {
615 self.footprint = Footprint::default();
616 }
617}
618
619fn get_key_type_string_for_error(lk: &LedgerKey) -> &str {
620 match lk {
621 LedgerKey::ContractData(cd) => match cd.key {
622 ScVal::LedgerKeyContractInstance => "contract instance",
623 ScVal::LedgerKeyNonce(_) => "nonce",
624 _ => "contract data key",
625 },
626 LedgerKey::ContractCode(_) => "contract code",
627 LedgerKey::Account(_) => "account",
628 LedgerKey::Trustline(_) => "account trustline",
629 _ => "ledger key",
633 }
634}
635
636impl Host {
637 fn decorate_storage_error(
638 &self,
639 err: HostError,
640 lk: &LedgerKey,
641 key_val: Option<Val>,
642 ) -> HostError {
643 let mut err = err;
644 self.with_debug_mode(|| {
645 if !err.error.is_type(ScErrorType::Storage) {
646 return Ok(());
647 }
648 if !err.error.is_code(ScErrorCode::ExceededLimit)
649 && !err.error.is_code(ScErrorCode::MissingValue)
650 {
651 return Ok(());
652 }
653
654 let key_type_str = get_key_type_string_for_error(lk);
655 let can_create_new_objects = err.error.is_code(ScErrorCode::ExceededLimit);
662 let args = self
663 .get_args_for_error(lk, key_val, can_create_new_objects)
664 .unwrap_or_else(|_| vec![]);
665 if err.error.is_code(ScErrorCode::ExceededLimit) {
666 err = self.err(
667 ScErrorType::Storage,
668 ScErrorCode::ExceededLimit,
669 format!("trying to access {} outside of the footprint", key_type_str).as_str(),
670 args.as_slice(),
671 );
672 } else if err.error.is_code(ScErrorCode::MissingValue) {
673 err = self.err(
674 ScErrorType::Storage,
675 ScErrorCode::MissingValue,
676 format!("trying to get non-existing value for {}", key_type_str).as_str(),
677 args.as_slice(),
678 );
679 }
680
681 Ok(())
682 });
683 err
684 }
685
686 fn get_args_for_error(
687 &self,
688 lk: &LedgerKey,
689 key_val: Option<Val>,
690 can_create_new_objects: bool,
691 ) -> Result<Vec<Val>, HostError> {
692 let mut res = vec![];
693 match lk {
694 LedgerKey::ContractData(cd) => {
695 if can_create_new_objects {
696 let address_val = self
697 .add_host_object(cd.contract.metered_clone(self.as_budget())?)?
698 .into();
699 res.push(address_val);
700 }
701 match &cd.key {
702 ScVal::LedgerKeyContractInstance => (),
703 ScVal::LedgerKeyNonce(n) => {
704 if can_create_new_objects {
705 res.push(self.add_host_object(n.nonce)?.into());
706 }
707 }
708 _ => {
709 if let Some(key) = key_val {
710 res.push(key);
711 }
712 }
713 }
714 }
715 LedgerKey::ContractCode(c) => {
716 if can_create_new_objects {
717 res.push(
718 self.add_host_object(self.scbytes_from_hash(&c.hash)?)?
719 .into(),
720 );
721 }
722 }
723 LedgerKey::Account(_) | LedgerKey::Trustline(_) => {
724 if can_create_new_objects {
725 res.push(self.account_address_from_key(lk)?)
726 }
727 }
728 _ => (),
732 };
733 Ok(res)
734 }
735}
736
737#[cfg(any(test, feature = "recording_mode"))]
738pub(crate) fn is_persistent_key(key: &LedgerKey) -> bool {
739 match key {
740 LedgerKey::ContractData(k) => {
741 matches!(k.durability, ContractDataDurability::Persistent)
742 }
743 LedgerKey::ContractCode(_) => true,
744 _ => false,
745 }
746}