xrpl_wasm_stdlib/core/ledger_objects/
mod.rs

1pub mod account_root;
2pub mod current_escrow;
3pub mod escrow;
4pub mod traits;
5
6use crate::host::error_codes::{
7    match_result_code_with_expected_bytes, match_result_code_with_expected_bytes_optional,
8};
9use crate::host::{Result, get_current_ledger_obj_field, get_ledger_obj_field};
10
11/// Trait for types that can be retrieved from ledger object fields.
12///
13/// This trait provides a unified interface for retrieving typed data from XRPL ledger objects,
14/// replacing the previous collection of type-specific functions with a generic, type-safe approach.
15///
16/// ## Supported Types
17///
18/// The following types implement this trait:
19/// - `u8` - 8-bit unsigned integers (1 byte)
20/// - `u16` - 16-bit unsigned integers (2 bytes)
21/// - `u32` - 32-bit unsigned integers (4 bytes)
22/// - `u64` - 64-bit unsigned integers (8 bytes)
23/// - `AccountID` - 20-byte account identifiers
24/// - `Amount` - XRP amounts and token amounts (variable size, up to 48 bytes)
25/// - `Hash128` - 128-bit cryptographic hashes (16 bytes)
26/// - `Hash256` - 256-bit cryptographic hashes (32 bytes)
27/// - `Blob<N>` - Variable-length binary data (generic over buffer size `N`)
28///
29/// ## Usage Patterns
30///
31/// ```rust,no_run
32/// use xrpl_wasm_stdlib::core::ledger_objects::{ledger_object, current_ledger_object};
33/// use xrpl_wasm_stdlib::core::types::account_id::AccountID;
34/// use xrpl_wasm_stdlib::sfield;
35///
36/// fn example() {
37///   let slot = 0;
38///   // Get a required field from a specific ledger object
39///   let balance: u64 = ledger_object::get_field(slot, sfield::Balance).unwrap();
40///   let account: AccountID = ledger_object::get_field(slot, sfield::Account).unwrap();
41///
42///   // Get an optional field from the current ledger object
43///   let flags: Option<u32> = current_ledger_object::get_field_optional(sfield::Flags).unwrap();
44/// }
45/// ```
46///
47/// ## Error Handling
48///
49/// - Required field methods return `Result<T>` and error if the field is missing
50/// - Optional field methods return `Result<Option<T>>` and return `None` if the field is missing
51/// - All methods return appropriate errors for buffer size mismatches or other retrieval failures
52///
53/// ## Safety Considerations
54///
55/// - All implementations use appropriately sized buffers for their data types
56/// - Buffer sizes are validated against expected field sizes where applicable
57/// - Unsafe operations are contained within the host function calls
58pub trait LedgerObjectFieldGetter: Sized {
59    /// Get a required field from the current ledger object.
60    ///
61    /// # Arguments
62    ///
63    /// * `field_code` - The field code identifying which field to retrieve
64    ///
65    /// # Returns
66    ///
67    /// Returns a `Result<Self>` where:
68    /// * `Ok(Self)` - The field value for the specified field
69    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
70    fn get_from_current_ledger_obj(field_code: i32) -> Result<Self>;
71
72    /// Get an optional field from the current ledger object.
73    ///
74    /// # Arguments
75    ///
76    /// * `field_code` - The field code identifying which field to retrieve
77    ///
78    /// # Returns
79    ///
80    /// Returns a `Result<Option<Self>>` where:
81    /// * `Ok(Some(Self))` - The field value for the specified field
82    /// * `Ok(None)` - If the field is not present
83    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
84    fn get_from_current_ledger_obj_optional(field_code: i32) -> Result<Option<Self>>;
85
86    /// Get a required field from a specific ledger object.
87    ///
88    /// # Arguments
89    ///
90    /// * `register_num` - The register number holding the ledger object
91    /// * `field_code` - The field code identifying which field to retrieve
92    ///
93    /// # Returns
94    ///
95    /// Returns a `Result<Self>` where:
96    /// * `Ok(Self)` - The field value for the specified field
97    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
98    fn get_from_ledger_obj(register_num: i32, field_code: i32) -> Result<Self>;
99
100    /// Get an optional field from a specific ledger object.
101    ///
102    /// # Arguments
103    ///
104    /// * `register_num` - The register number holding the ledger object
105    /// * `field_code` - The field code identifying which field to retrieve
106    ///
107    /// # Returns
108    ///
109    /// Returns a `Result<Option<Self>>` where:
110    /// * `Ok(Some(Self))` - The field value for the specified field
111    /// * `Ok(None)` - If the field is not present in the ledger object
112    /// * `Err(Error)` - If the field retrieval operation failed
113    fn get_from_ledger_obj_optional(register_num: i32, field_code: i32) -> Result<Option<Self>>;
114}
115
116/// Trait for types that can be retrieved as fixed-size fields from ledger objects.
117///
118/// This trait enables a generic implementation of `LedgerObjectFieldGetter` for all fixed-size
119/// unsigned integer types (u8, u16, u32, u64). Types implementing this trait must
120/// have a known, constant size in bytes.
121///
122/// # Implementing Types
123///
124/// - `u8` - 1 byte
125/// - `u16` - 2 bytes
126/// - `u32` - 4 bytes
127/// - `u64` - 8 bytes
128trait FixedSizeFieldType: Sized {
129    /// The size of this type in bytes
130    const SIZE: usize;
131}
132
133impl FixedSizeFieldType for u8 {
134    const SIZE: usize = 1;
135}
136
137impl FixedSizeFieldType for u16 {
138    const SIZE: usize = 2;
139}
140
141impl FixedSizeFieldType for u32 {
142    const SIZE: usize = 4;
143}
144
145impl FixedSizeFieldType for u64 {
146    const SIZE: usize = 8;
147}
148
149/// Generic implementation of `LedgerObjectFieldGetter` for all fixed-size unsigned integer types.
150///
151/// This single implementation handles u8, u16, u32, and u64 by leveraging the
152/// `FixedSizeFieldType` trait. The implementation:
153/// - Allocates a buffer of the appropriate size
154/// - Calls the host function to retrieve the field
155/// - Validates that the returned byte count matches the expected size
156/// - Converts the buffer to the target type
157///
158/// # Buffer Management
159///
160/// Uses `MaybeUninit` for efficient stack allocation without initialization overhead.
161/// The buffer size is determined at compile-time via the `SIZE` constant.
162impl<T: FixedSizeFieldType> LedgerObjectFieldGetter for T {
163    #[inline]
164    fn get_from_current_ledger_obj(field_code: i32) -> Result<Self> {
165        let mut value = core::mem::MaybeUninit::<T>::uninit();
166        let result_code =
167            unsafe { get_current_ledger_obj_field(field_code, value.as_mut_ptr().cast(), T::SIZE) };
168        match_result_code_with_expected_bytes(result_code, T::SIZE, || unsafe {
169            value.assume_init()
170        })
171    }
172
173    #[inline]
174    fn get_from_current_ledger_obj_optional(field_code: i32) -> Result<Option<Self>> {
175        let mut value = core::mem::MaybeUninit::<T>::uninit();
176        let result_code =
177            unsafe { get_current_ledger_obj_field(field_code, value.as_mut_ptr().cast(), T::SIZE) };
178        match_result_code_with_expected_bytes_optional(result_code, T::SIZE, || {
179            Some(unsafe { value.assume_init() })
180        })
181    }
182
183    #[inline]
184    fn get_from_ledger_obj(register_num: i32, field_code: i32) -> Result<Self> {
185        let mut value = core::mem::MaybeUninit::<T>::uninit();
186        let result_code = unsafe {
187            get_ledger_obj_field(register_num, field_code, value.as_mut_ptr().cast(), T::SIZE)
188        };
189        match_result_code_with_expected_bytes(result_code, T::SIZE, || unsafe {
190            value.assume_init()
191        })
192    }
193
194    #[inline]
195    fn get_from_ledger_obj_optional(register_num: i32, field_code: i32) -> Result<Option<Self>> {
196        let mut value = core::mem::MaybeUninit::<T>::uninit();
197        let result_code = unsafe {
198            get_ledger_obj_field(register_num, field_code, value.as_mut_ptr().cast(), T::SIZE)
199        };
200        match_result_code_with_expected_bytes_optional(result_code, T::SIZE, || {
201            Some(unsafe { value.assume_init() })
202        })
203    }
204}
205
206pub mod current_ledger_object {
207    use super::LedgerObjectFieldGetter;
208    use crate::host::Result;
209
210    /// Retrieves a field from the current ledger object.
211    ///
212    /// # Arguments
213    ///
214    /// * `field_code` - The field code identifying which field to retrieve
215    ///
216    /// # Returns
217    ///
218    /// Returns a `Result<T>` where:
219    /// * `Ok(T)` - The field value for the specified field
220    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
221    #[inline]
222    pub fn get_field<T: LedgerObjectFieldGetter>(field_code: i32) -> Result<T> {
223        T::get_from_current_ledger_obj(field_code)
224    }
225
226    /// Retrieves an optionally present field from the current ledger object.
227    ///
228    /// # Arguments
229    ///
230    /// * `field_code` - The field code identifying which field to retrieve
231    ///
232    /// # Returns
233    ///
234    /// Returns a `Result<Option<T>>` where:
235    /// * `Ok(Some(T))` - The field value for the specified field
236    /// * `Ok(None)` - If the field is not present
237    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
238    #[inline]
239    pub fn get_field_optional<T: LedgerObjectFieldGetter>(field_code: i32) -> Result<Option<T>> {
240        T::get_from_current_ledger_obj_optional(field_code)
241    }
242}
243
244pub mod ledger_object {
245    use super::LedgerObjectFieldGetter;
246    use crate::host::Result;
247
248    /// Retrieves a field from a specified ledger object.
249    ///
250    /// # Arguments
251    ///
252    /// * `register_num` - The register number holding the ledger object to look for data in
253    /// * `field_code` - The field code identifying which field to retrieve
254    ///
255    /// # Returns
256    ///
257    /// Returns a `Result<T>` where:
258    /// * `Ok(T)` - The field value for the specified field
259    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
260    #[inline]
261    pub fn get_field<T: LedgerObjectFieldGetter>(register_num: i32, field_code: i32) -> Result<T> {
262        T::get_from_ledger_obj(register_num, field_code)
263    }
264
265    /// Retrieves an optionally present field from a specified ledger object.
266    ///
267    /// # Arguments
268    ///
269    /// * `register_num` - The register number holding the ledger object to look for data in
270    /// * `field_code` - The field code identifying which field to retrieve
271    ///
272    /// # Returns
273    ///
274    /// Returns a `Result<Option<T>>` where:
275    /// * `Ok(Some(T))` - The field value for the specified field
276    /// * `Ok(None)` - If the field is not present in the ledger object
277    /// * `Err(Error)` - If the field retrieval operation failed
278    #[inline]
279    pub fn get_field_optional<T: LedgerObjectFieldGetter>(
280        register_num: i32,
281        field_code: i32,
282    ) -> Result<Option<T>> {
283        T::get_from_ledger_obj_optional(register_num, field_code)
284    }
285
286    #[cfg(test)]
287    mod tests {
288        use super::*;
289        use crate::core::ledger_objects::{current_ledger_object, ledger_object};
290        use crate::core::types::account_id::{ACCOUNT_ID_SIZE, AccountID};
291        use crate::core::types::amount::Amount;
292        use crate::core::types::blob::{Blob, DEFAULT_BLOB_SIZE};
293        use crate::core::types::public_key::PUBLIC_KEY_BUFFER_SIZE;
294        use crate::core::types::uint::{HASH128_SIZE, HASH256_SIZE, Hash128, Hash256};
295        use crate::host::host_bindings_trait::MockHostBindings;
296        use crate::host::setup_mock;
297        use crate::sfield;
298        use mockall::predicate::{always, eq};
299
300        // ========================================
301        // Test helper functions
302        // ========================================
303
304        /// Helper to set up a mock expectation for get_current_ledger_obj_field
305        fn expect_current_field(
306            mock: &mut MockHostBindings,
307            field_code: i32,
308            size: usize,
309            times: usize,
310        ) {
311            mock.expect_get_current_ledger_obj_field()
312                .with(eq(field_code), always(), eq(size))
313                .times(times)
314                .returning(move |_, _, _| size as i32);
315        }
316
317        /// Helper to set up a mock expectation for get_ledger_obj_field
318        fn expect_ledger_field(
319            mock: &mut MockHostBindings,
320            slot: i32,
321            field_code: i32,
322            size: usize,
323            times: usize,
324        ) {
325            mock.expect_get_ledger_obj_field()
326                .with(eq(slot), eq(field_code), always(), eq(size))
327                .times(times)
328                .returning(move |_, _, _, _| size as i32);
329        }
330
331        // ========================================
332        // Basic smoke tests for LedgerObjectFieldGetter implementations
333        // These tests verify that the trait implementations compile and work with the test host.
334        // Note: The test host returns buffer_len as success, so these only verify basic functionality.
335        // ========================================
336
337        #[test]
338        fn test_field_getter_basic_types() {
339            let mut mock = MockHostBindings::new();
340
341            expect_current_field(&mut mock, sfield::LedgerEntryType, 2, 1);
342            expect_current_field(&mut mock, sfield::Flags, 4, 1);
343            expect_current_field(&mut mock, sfield::Balance, 8, 1);
344
345            let _guard = setup_mock(mock);
346
347            // Test that all basic integer types work
348            assert!(u16::get_from_current_ledger_obj(sfield::LedgerEntryType).is_ok());
349            assert!(u32::get_from_current_ledger_obj(sfield::Flags).is_ok());
350            assert!(u64::get_from_current_ledger_obj(sfield::Balance).is_ok());
351        }
352
353        #[test]
354        fn test_field_getter_xrpl_types() {
355            let mut mock = MockHostBindings::new();
356
357            expect_current_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
358            expect_current_field(&mut mock, sfield::Amount, 48, 1);
359            expect_current_field(&mut mock, sfield::EmailHash, HASH128_SIZE, 1);
360            expect_current_field(&mut mock, sfield::PreviousTxnID, HASH256_SIZE, 1);
361            expect_current_field(&mut mock, sfield::PublicKey, DEFAULT_BLOB_SIZE, 1);
362
363            let _guard = setup_mock(mock);
364
365            // Test that XRPL-specific types work
366            assert!(AccountID::get_from_current_ledger_obj(sfield::Account).is_ok());
367            assert!(Amount::get_from_current_ledger_obj(sfield::Amount).is_ok());
368            assert!(Hash128::get_from_current_ledger_obj(sfield::EmailHash).is_ok());
369            assert!(Hash256::get_from_current_ledger_obj(sfield::PreviousTxnID).is_ok());
370
371            let blob: Blob<DEFAULT_BLOB_SIZE> =
372                Blob::get_from_current_ledger_obj(sfield::PublicKey).unwrap();
373            // The test host returns buffer length as the result
374            assert_eq!(blob.len, DEFAULT_BLOB_SIZE);
375        }
376
377        #[test]
378        fn test_field_getter_optional_variants() {
379            let mut mock = MockHostBindings::new();
380
381            expect_current_field(&mut mock, sfield::Flags, 4, 1);
382            expect_current_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
383
384            let _guard = setup_mock(mock);
385
386            // Test optional field retrieval
387            let result = u32::get_from_current_ledger_obj_optional(sfield::Flags);
388            assert!(result.is_ok());
389            assert!(result.unwrap().is_some());
390
391            let result = AccountID::get_from_current_ledger_obj_optional(sfield::Account);
392            assert!(result.is_ok());
393            assert!(result.unwrap().is_some());
394        }
395
396        #[test]
397        fn test_field_getter_with_slot() {
398            let mut mock = MockHostBindings::new();
399            let slot = 0;
400
401            expect_ledger_field(&mut mock, slot, sfield::Flags, 4, 1);
402            expect_ledger_field(&mut mock, slot, sfield::Balance, 8, 1);
403            expect_ledger_field(&mut mock, slot, sfield::Account, ACCOUNT_ID_SIZE, 1);
404
405            let _guard = setup_mock(mock);
406
407            // Test ledger object field retrieval with slot numbers
408            assert!(u32::get_from_ledger_obj(slot, sfield::Flags).is_ok());
409            assert!(u64::get_from_ledger_obj(slot, sfield::Balance).is_ok());
410            assert!(AccountID::get_from_ledger_obj(slot, sfield::Account).is_ok());
411        }
412
413        #[test]
414        fn test_field_getter_optional_with_slot() {
415            let mut mock = MockHostBindings::new();
416            let slot = 0;
417
418            expect_ledger_field(&mut mock, slot, sfield::Flags, 4, 1);
419
420            let _guard = setup_mock(mock);
421
422            // Test optional field retrieval with slot numbers
423            let result = u32::get_from_ledger_obj_optional(slot, sfield::Flags);
424            assert!(result.is_ok());
425            assert!(result.unwrap().is_some());
426        }
427
428        // ========================================
429        // Tests for module-level convenience functions
430        // ========================================
431
432        #[test]
433        fn test_current_ledger_object_module() {
434            let mut mock = MockHostBindings::new();
435
436            expect_current_field(&mut mock, sfield::Flags, 4, 2);
437            expect_current_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
438
439            let _guard = setup_mock(mock);
440
441            // Test the current_ledger_object module's convenience functions
442            assert!(current_ledger_object::get_field::<u32>(sfield::Flags).is_ok());
443            assert!(current_ledger_object::get_field::<AccountID>(sfield::Account).is_ok());
444
445            let result = current_ledger_object::get_field_optional::<u32>(sfield::Flags);
446            assert!(result.is_ok());
447            assert!(result.unwrap().is_some());
448        }
449
450        #[test]
451        fn test_ledger_object_module() {
452            let mut mock = MockHostBindings::new();
453            let slot = 0;
454
455            expect_ledger_field(&mut mock, slot, sfield::LedgerEntryType, 2, 1);
456            expect_ledger_field(&mut mock, slot, sfield::Flags, 4, 2);
457            expect_ledger_field(&mut mock, slot, sfield::Balance, 8, 1);
458            expect_ledger_field(&mut mock, slot, sfield::Account, ACCOUNT_ID_SIZE, 1);
459            expect_ledger_field(&mut mock, slot, sfield::Amount, 48, 1);
460            expect_ledger_field(&mut mock, slot, sfield::EmailHash, HASH128_SIZE, 1);
461            expect_ledger_field(&mut mock, slot, sfield::PreviousTxnID, HASH256_SIZE, 1);
462            expect_ledger_field(&mut mock, slot, sfield::PublicKey, 33, 1);
463
464            let _guard = setup_mock(mock);
465
466            // Test the ledger_object module's convenience functions
467            assert!(ledger_object::get_field::<u16>(slot, sfield::LedgerEntryType).is_ok());
468            assert!(ledger_object::get_field::<u32>(slot, sfield::Flags).is_ok());
469            assert!(ledger_object::get_field::<u64>(slot, sfield::Balance).is_ok());
470            assert!(ledger_object::get_field::<AccountID>(slot, sfield::Account).is_ok());
471            assert!(ledger_object::get_field::<Amount>(slot, sfield::Amount).is_ok());
472            assert!(ledger_object::get_field::<Hash128>(slot, sfield::EmailHash).is_ok());
473            assert!(ledger_object::get_field::<Hash256>(slot, sfield::PreviousTxnID).is_ok());
474            assert!(ledger_object::get_field::<Blob<33>>(slot, sfield::PublicKey).is_ok());
475
476            let result = ledger_object::get_field_optional::<u32>(slot, sfield::Flags);
477            assert!(result.is_ok());
478            assert!(result.unwrap().is_some());
479        }
480
481        // ========================================
482        // Type inference and compilation tests
483        // ========================================
484
485        #[test]
486        fn test_type_inference() {
487            let mut mock = MockHostBindings::new();
488            let slot = 0;
489
490            expect_ledger_field(&mut mock, slot, sfield::Balance, 8, 1);
491            expect_ledger_field(&mut mock, slot, sfield::Account, ACCOUNT_ID_SIZE, 1);
492            expect_ledger_field(&mut mock, slot, sfield::Sequence, 4, 1);
493            expect_ledger_field(&mut mock, slot, sfield::Flags, 4, 1);
494
495            let _guard = setup_mock(mock);
496
497            // Verify type inference works with turbofish syntax
498            let _balance = get_field::<u64>(slot, sfield::Balance);
499            let _account = get_field::<AccountID>(slot, sfield::Account);
500
501            // Verify type inference works with type annotations
502            let _sequence: Result<u32> = get_field(slot, sfield::Sequence);
503            let _flags: Result<u32> = get_field(slot, sfield::Flags);
504        }
505
506        // ========================================
507        // Data size verification tests
508        // ========================================
509
510        #[test]
511        fn test_type_sizes() {
512            let mut mock = MockHostBindings::new();
513
514            expect_current_field(&mut mock, sfield::EmailHash, HASH128_SIZE, 1);
515            expect_current_field(&mut mock, sfield::PreviousTxnID, HASH256_SIZE, 1);
516            expect_current_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
517            expect_current_field(&mut mock, sfield::PublicKey, PUBLIC_KEY_BUFFER_SIZE, 1);
518
519            let _guard = setup_mock(mock);
520
521            // Verify that returned types have the expected sizes
522            let hash128 = Hash128::get_from_current_ledger_obj(sfield::EmailHash).unwrap();
523            assert_eq!(hash128.as_bytes().len(), HASH128_SIZE);
524
525            let hash256 = Hash256::get_from_current_ledger_obj(sfield::PreviousTxnID).unwrap();
526            assert_eq!(hash256.as_bytes().len(), HASH256_SIZE);
527
528            let account = AccountID::get_from_current_ledger_obj(sfield::Account).unwrap();
529            assert_eq!(account.0.len(), ACCOUNT_ID_SIZE);
530
531            let blob: Blob<{ PUBLIC_KEY_BUFFER_SIZE }> =
532                Blob::get_from_current_ledger_obj(sfield::PublicKey).unwrap();
533            // In test environment, host returns buffer size as result code
534            assert_eq!(blob.len, PUBLIC_KEY_BUFFER_SIZE);
535            assert_eq!(blob.data.len(), PUBLIC_KEY_BUFFER_SIZE);
536        }
537    }
538}