Skip to main content

xrpl_common_stdlib/objects/
traits.rs

1//! Generic ledger-object field accessor traits.
2//!
3//! The base traits `LedgerObjectCommonFields` / `CurrentLedgerObjectCommonFields` are
4//! hand-written here (no field is common to every ledger entry). Every per-ledger-entry
5//! field trait (`AccountRootFields`, `EscrowFields`, `OracleFields`, etc.) is generated in
6//! `crate::objects::generated` and re-exported from `crate::objects`; a couple are also
7//! re-exported here for a stable `objects::traits::*` import path.
8
9pub use crate::objects::generated::{AccountRootFields, EscrowFields};
10
11use crate::fields::locator::LedgerPathBuilder;
12use crate::host::Result;
13use crate::objects::{current_ledger_object, ledger_object};
14use crate::sfield;
15
16/// Trait providing access to common fields present in all ledger objects.
17///
18/// This trait defines methods to access standard fields that are common across
19/// different types of ledger objects in the XRP Ledger.
20pub trait LedgerObjectCommonFields {
21    // NOTE: `get_ledger_index()` is not in this trait because `sfLedgerIndex` is not actually a field on a ledger
22    // object (it's a synthetic field that maps to the `index` field, which is the unique ID of an object in the
23    // ledger's state tree). See https://github.com/XRPLF/rippled/issues/3649 for more context.
24
25    /// Returns the slot number (register number) where the ledger object is stored.
26    ///
27    /// This number is used to identify and access the specific ledger object
28    /// when retrieving or modifying its fields.
29    ///
30    /// # Returns
31    ///
32    /// The slot number as an i32 value
33    fn get_slot_num(&self) -> i32;
34
35    /// Starts a nested-field path rooted at this ledger object, read through its slot.
36    ///
37    /// Use this to reach into arrays and inner objects that the flat getters below can't return
38    /// whole (e.g. `SignerEntries[0].Account`). Chain [`field`](LedgerPathBuilder::field) /
39    /// [`index`](LedgerPathBuilder::index), then [`get::<T>()`](LedgerPathBuilder::get).
40    ///
41    /// ```no_run
42    /// use xrpl_common_stdlib::objects::traits::LedgerObjectCommonFields;
43    /// use xrpl_common_stdlib::sfield;
44    /// use xrpl_common_stdlib::types::account_id::AccountID;
45    /// # fn demo(obj: &impl LedgerObjectCommonFields) {
46    /// let signer = obj.path()
47    ///     .field(sfield::SignerEntries)
48    ///     .index(0)
49    ///     .field(sfield::Account)
50    ///     .get::<AccountID>();
51    /// # let _ = signer; }
52    /// ```
53    fn path(&self) -> LedgerPathBuilder {
54        LedgerPathBuilder::for_ledger_obj(self.get_slot_num())
55    }
56
57    /// Retrieves the flags field of the ledger object.
58    ///
59    /// # Arguments
60    ///
61    /// * `register_num` - The register number where the ledger object is stored
62    ///
63    /// # Returns
64    ///
65    /// The flags as a u32 value
66    fn get_flags(&self) -> Result<u32> {
67        ledger_object::get_field(self.get_slot_num(), sfield::Flags)
68    }
69
70    /// Retrieves the ledger entry type of the object.
71    ///
72    /// The value 0x0075, mapped to the string Escrow, indicates that this is an Escrow entry.
73    ///
74    /// # Returns
75    ///
76    /// The ledger entry type as a u16 value
77    fn get_ledger_entry_type(&self) -> Result<u16> {
78        ledger_object::get_field(self.get_slot_num(), sfield::LedgerEntryType)
79    }
80}
81
82/// Trait providing access to common fields in the current ledger object.
83///
84/// This trait defines methods to access standard fields that are common across
85/// different types of ledger objects, specifically for the current ledger object
86/// being processed.
87pub trait CurrentLedgerObjectCommonFields {
88    // NOTE: `get_ledger_index()` is not in this trait because `sfLedgerIndex` is not actually a field on a ledger
89    // object (it's a synthetic field that maps to the `index` field, which is the unique ID of an object in the
90    // ledger's state tree). See https://github.com/XRPLF/rippled/issues/3649 for more context.
91
92    /// Starts a nested-field path rooted at the current ledger object (no slot).
93    ///
94    /// Use this to reach into arrays and inner objects that the flat getters below can't return
95    /// whole. Chain [`field`](LedgerPathBuilder::field) / [`index`](LedgerPathBuilder::index), then
96    /// [`get::<T>()`](LedgerPathBuilder::get).
97    ///
98    /// ```no_run
99    /// use xrpl_common_stdlib::objects::traits::CurrentLedgerObjectCommonFields;
100    /// use xrpl_common_stdlib::sfield;
101    /// use xrpl_common_stdlib::types::amount::Amount;
102    /// # fn demo(obj: &impl CurrentLedgerObjectCommonFields) {
103    /// let amount = obj.path().field(sfield::Amount).get::<Amount>();
104    /// # let _ = amount; }
105    /// ```
106    fn path(&self) -> LedgerPathBuilder {
107        LedgerPathBuilder::for_current_ledger_obj()
108    }
109
110    /// Retrieves the flags field of the current ledger object.
111    ///
112    /// # Returns
113    ///
114    /// The flags as a u32 value
115    fn get_flags(&self) -> Result<u32> {
116        current_ledger_object::get_field(sfield::Flags)
117    }
118
119    /// Retrieves the ledger entry type of the current ledger object.
120    ///
121    /// The value 0x0075, mapped to the string Escrow, indicates that this is an Escrow entry.
122    ///
123    /// # Returns
124    ///
125    /// The ledger entry type as a u16 value
126    fn get_ledger_entry_type(&self) -> Result<u16> {
127        current_ledger_object::get_field(sfield::LedgerEntryType)
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134    use crate::fields::decoder::FromLedger;
135    use crate::host::error_codes::{INVALID_FIELD, SOME_ERROR};
136    use crate::host::host_bindings_trait::MockHostBindings;
137    use crate::objects::AccountRoot;
138    use crate::sfield::SField;
139    use mockall::predicate::{always, eq};
140
141    // ========================================
142    // Test helper functions
143    // ========================================
144
145    /// Helper to set up a mock expectation for home_le_field
146    ///
147    /// Sets up a mock expectation that will match calls with:
148    /// - field: The SField with the specified CODE
149    /// - size: The expected buffer size
150    /// - times: How many times this expectation should be matched
151    ///
152    /// When a test fails, mockall will show which parameter didn't match.
153    fn expect_current_field<
154        T: FromLedger + Send + std::fmt::Debug + PartialEq + 'static,
155        const CODE: i32,
156    >(
157        mock: &mut MockHostBindings,
158        _field: SField<T, CODE>,
159        size: usize,
160        times: usize,
161    ) {
162        mock.expect_home_le_field()
163            .with(eq(CODE), always(), eq(size))
164            .times(times)
165            .returning(move |_, _, _| size as i32);
166    }
167
168    /// Helper to set up a mock expectation for le_field
169    ///
170    /// Sets up a mock expectation that will match calls with:
171    /// - slot: The ledger object slot number
172    /// - field: The SField with the specified CODE
173    /// - size: The expected buffer size
174    /// - times: How many times this expectation should be matched
175    ///
176    /// When a test fails, mockall will show which parameter didn't match.
177    fn expect_ledger_field<
178        T: FromLedger + Send + std::fmt::Debug + PartialEq + 'static,
179        const CODE: i32,
180    >(
181        mock: &mut MockHostBindings,
182        slot: i32,
183        _field: SField<T, CODE>,
184        size: usize,
185        times: usize,
186    ) {
187        mock.expect_le_field()
188            .with(eq(slot), eq(CODE), always(), eq(size))
189            .times(times)
190            .returning(move |_, _, _, _| size as i32);
191    }
192
193    mod ledger_object_common_fields {
194        use super::*;
195        use crate::host::setup_mock;
196
197        #[test]
198        fn test_mandatory_fields_return_ok() {
199            let mut mock = MockHostBindings::new();
200
201            // get_flags
202            expect_ledger_field(&mut mock, 1, sfield::Flags, 4, 1);
203            // get_ledger_entry_type
204            expect_ledger_field(&mut mock, 1, sfield::LedgerEntryType, 2, 1);
205
206            let _guard = setup_mock(mock);
207
208            let account = AccountRoot::new(1);
209
210            // All mandatory fields should return Ok
211            assert!(account.get_flags().is_ok());
212            assert!(account.get_ledger_entry_type().is_ok());
213        }
214
215        #[test]
216        fn test_mandatory_fields_return_error_on_internal_error() {
217            let mut mock = MockHostBindings::new();
218
219            // get_flags with SOME_ERROR
220            mock.expect_le_field()
221                .with(eq(1), eq(sfield::Flags), always(), eq(4))
222                .times(1)
223                .returning(|_, _, _, _| SOME_ERROR);
224
225            let _guard = setup_mock(mock);
226
227            let account = AccountRoot::new(1);
228            let result = account.get_flags();
229
230            assert!(result.is_err());
231            assert_eq!(result.err().unwrap().code(), SOME_ERROR);
232        }
233
234        #[test]
235        fn test_get_ledger_entry_type_returns_error_on_internal_error() {
236            let mut mock = MockHostBindings::new();
237
238            mock.expect_le_field()
239                .with(eq(1), eq(sfield::LedgerEntryType), always(), eq(2))
240                .times(1)
241                .returning(|_, _, _, _| SOME_ERROR);
242
243            let _guard = setup_mock(mock);
244
245            let account = AccountRoot::new(1);
246            let result = account.get_ledger_entry_type();
247
248            assert!(result.is_err());
249            assert_eq!(result.err().unwrap().code(), SOME_ERROR);
250        }
251
252        #[test]
253        fn test_mandatory_fields_return_error_on_invalid_field() {
254            let mut mock = MockHostBindings::new();
255
256            // get_flags with INVALID_FIELD
257            mock.expect_le_field()
258                .with(eq(1), eq(sfield::Flags), always(), eq(4))
259                .times(1)
260                .returning(|_, _, _, _| INVALID_FIELD);
261
262            let _guard = setup_mock(mock);
263
264            let account = AccountRoot::new(1);
265            let result = account.get_flags();
266
267            assert!(result.is_err());
268            assert_eq!(result.err().unwrap().code(), INVALID_FIELD);
269        }
270    }
271
272    mod current_ledger_object_common_fields {
273        use super::*;
274        use crate::host::setup_mock;
275
276        struct TestCurrentLedgerObject;
277        impl CurrentLedgerObjectCommonFields for TestCurrentLedgerObject {}
278
279        #[test]
280        fn test_mandatory_fields_return_ok() {
281            let mut mock = MockHostBindings::new();
282
283            // get_flags
284            expect_current_field(&mut mock, sfield::Flags, 4, 1);
285            // get_ledger_entry_type
286            expect_current_field(&mut mock, sfield::LedgerEntryType, 2, 1);
287
288            let _guard = setup_mock(mock);
289
290            let escrow = TestCurrentLedgerObject;
291
292            // All mandatory fields should return Ok
293            assert!(escrow.get_flags().is_ok());
294            assert!(escrow.get_ledger_entry_type().is_ok());
295        }
296
297        #[test]
298        fn test_mandatory_fields_return_error_on_internal_error() {
299            let mut mock = MockHostBindings::new();
300
301            // get_flags with SOME_ERROR
302            mock.expect_home_le_field()
303                .with(eq(sfield::Flags), always(), eq(4))
304                .times(1)
305                .returning(|_, _, _| SOME_ERROR);
306
307            let _guard = setup_mock(mock);
308
309            let escrow = TestCurrentLedgerObject;
310            let result = escrow.get_flags();
311
312            assert!(result.is_err());
313            assert_eq!(result.err().unwrap().code(), SOME_ERROR);
314        }
315
316        #[test]
317        fn test_get_ledger_entry_type_returns_error_on_internal_error() {
318            let mut mock = MockHostBindings::new();
319
320            mock.expect_home_le_field()
321                .with(eq(sfield::LedgerEntryType), always(), eq(2))
322                .times(1)
323                .returning(|_, _, _| SOME_ERROR);
324
325            let _guard = setup_mock(mock);
326
327            let escrow = TestCurrentLedgerObject;
328            let result = escrow.get_ledger_entry_type();
329
330            assert!(result.is_err());
331            assert_eq!(result.err().unwrap().code(), SOME_ERROR);
332        }
333
334        #[test]
335        fn test_mandatory_fields_return_error_on_invalid_field() {
336            let mut mock = MockHostBindings::new();
337
338            // get_flags with INVALID_FIELD
339            mock.expect_home_le_field()
340                .with(eq(sfield::Flags), always(), eq(4))
341                .times(1)
342                .returning(|_, _, _| INVALID_FIELD);
343
344            let _guard = setup_mock(mock);
345
346            let escrow = TestCurrentLedgerObject;
347            let result = escrow.get_flags();
348
349            assert!(result.is_err());
350            assert_eq!(result.err().unwrap().code(), INVALID_FIELD);
351        }
352    }
353}