xrpl_common_stdlib/objects/cache.rs
1//! Caching a ledger entry into a host slot.
2//!
3//! Reading any ledger object other than the one the contract is attached to is two steps: compute
4//! the entry's ID (see [`crate::ledger_entry_ids`]), then ask the host to load that entry into one
5//! of its cache slots. [`cache_le`] is the second step, and the slot it returns is what every
6//! slot-based handle is built from — [`LedgerObject::new`](crate::objects::LedgerObject::new) for
7//! an object with no typed wrapper, `<Entry>::new` for one that has it.
8
9use crate::host;
10use crate::host::Result;
11use crate::host::error_codes::match_result_code;
12use crate::ledger_entry_ids::LedgerEntryIdBytes;
13
14/// The host's sentinel for "put this entry in the next free slot" rather than replacing the entry
15/// in a specific one.
16const NEXT_AVAILABLE_SLOT: i32 = 0;
17
18/// Load the ledger entry with the given ID into a host cache slot, returning that slot.
19///
20/// The host assigns the next free slot; its cache holds up to 255 entries at once, so a contract
21/// that caches in a loop can exhaust it and see [`host::Error::SlotsFull`]. An ID that matches no
22/// entry in the ledger is [`host::Error::LedgerObjNotFound`], which is how a contract asks "does
23/// this object exist?".
24///
25/// ```no_run
26/// use xrpl_common_stdlib::host::Result;
27/// use xrpl_common_stdlib::ledger_entry_ids::accountroot_id;
28/// use xrpl_common_stdlib::objects::{AccountRoot, AccountRootFields, cache_le};
29/// use xrpl_common_stdlib::types::account_id::AccountID;
30/// # fn demo(account: &AccountID) {
31/// if let Result::Ok(id) = accountroot_id(account) {
32/// if let Result::Ok(slot) = cache_le(&id) {
33/// let balance = AccountRoot::new(slot).balance();
34/// # let _ = balance;
35/// }
36/// }
37/// # }
38/// ```
39pub fn cache_le(entry_id: &LedgerEntryIdBytes) -> Result<i32> {
40 let slot = unsafe { host::cache_le(entry_id.as_ptr(), entry_id.len(), NEXT_AVAILABLE_SLOT) };
41 match_result_code(slot, || slot)
42}
43
44#[cfg(test)]
45mod tests {
46 use super::*;
47 use crate::host::error_codes::{LEDGER_OBJ_NOT_FOUND, SLOTS_FULL};
48 use crate::host::host_bindings_trait::MockHostBindings;
49 use crate::host::setup_mock;
50 use crate::ledger_entry_ids::XRPL_LEDGER_ENTRY_ID_SIZE;
51 use mockall::predicate::{always, eq};
52
53 #[test]
54 fn test_returns_the_slot_the_host_assigned() {
55 let mut mock = MockHostBindings::new();
56 // The whole 32-byte ID is handed over, and slot 0 means "next available", not a slot.
57 mock.expect_cache_le()
58 .with(always(), eq(XRPL_LEDGER_ENTRY_ID_SIZE), eq(0))
59 .times(1)
60 .returning(|_, _, _| 4);
61 let _guard = setup_mock(mock);
62
63 assert_eq!(cache_le(&[0xAB; 32]).unwrap(), 4);
64 }
65
66 #[test]
67 fn test_reports_a_missing_entry_as_an_error() {
68 let mut mock = MockHostBindings::new();
69 mock.expect_cache_le()
70 .times(1)
71 .returning(|_, _, _| LEDGER_OBJ_NOT_FOUND);
72 let _guard = setup_mock(mock);
73
74 assert_eq!(
75 cache_le(&[0x00; 32]).err().unwrap().code(),
76 LEDGER_OBJ_NOT_FOUND
77 );
78 }
79
80 #[test]
81 fn test_reports_an_exhausted_cache_as_an_error() {
82 let mut mock = MockHostBindings::new();
83 mock.expect_cache_le()
84 .times(1)
85 .returning(|_, _, _| SLOTS_FULL);
86 let _guard = setup_mock(mock);
87
88 assert_eq!(cache_le(&[0x11; 32]).err().unwrap().code(), SLOTS_FULL);
89 }
90}