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::sfield;
296
297        // ========================================
298        // Basic smoke tests for LedgerObjectFieldGetter implementations
299        // These tests verify that the trait implementations compile and work with the test host.
300        // Note: The test host returns buffer_len as success, so these only verify basic functionality.
301        // ========================================
302
303        #[test]
304        fn test_field_getter_basic_types() {
305            // Test that all basic integer types work
306            assert!(u16::get_from_current_ledger_obj(sfield::LedgerEntryType).is_ok());
307            assert!(u32::get_from_current_ledger_obj(sfield::Flags).is_ok());
308            assert!(u64::get_from_current_ledger_obj(sfield::Balance).is_ok());
309        }
310
311        #[test]
312        fn test_field_getter_xrpl_types() {
313            // Test that XRPL-specific types work
314            assert!(AccountID::get_from_current_ledger_obj(sfield::Account).is_ok());
315            assert!(Amount::get_from_current_ledger_obj(sfield::Amount).is_ok());
316            assert!(Hash128::get_from_current_ledger_obj(sfield::EmailHash).is_ok());
317            assert!(Hash256::get_from_current_ledger_obj(sfield::PreviousTxnID).is_ok());
318
319            let blob: Blob<DEFAULT_BLOB_SIZE> =
320                Blob::get_from_current_ledger_obj(sfield::PublicKey).unwrap();
321            // The test host returns buffer length as the result
322            assert_eq!(blob.len, DEFAULT_BLOB_SIZE);
323        }
324
325        #[test]
326        fn test_field_getter_optional_variants() {
327            // Test optional field retrieval
328            let result = u32::get_from_current_ledger_obj_optional(sfield::Flags);
329            assert!(result.is_ok());
330            assert!(result.unwrap().is_some());
331
332            let result = AccountID::get_from_current_ledger_obj_optional(sfield::Account);
333            assert!(result.is_ok());
334            assert!(result.unwrap().is_some());
335        }
336
337        #[test]
338        fn test_field_getter_with_slot() {
339            // Test ledger object field retrieval with slot numbers
340            let slot = 0;
341            assert!(u32::get_from_ledger_obj(slot, sfield::Flags).is_ok());
342            assert!(u64::get_from_ledger_obj(slot, sfield::Balance).is_ok());
343            assert!(AccountID::get_from_ledger_obj(slot, sfield::Account).is_ok());
344        }
345
346        #[test]
347        fn test_field_getter_optional_with_slot() {
348            // Test optional field retrieval with slot numbers
349            let slot = 0;
350            let result = u32::get_from_ledger_obj_optional(slot, sfield::Flags);
351            assert!(result.is_ok());
352            assert!(result.unwrap().is_some());
353        }
354
355        // ========================================
356        // Tests for module-level convenience functions
357        // ========================================
358
359        #[test]
360        fn test_current_ledger_object_module() {
361            // Test the current_ledger_object module's convenience functions
362            assert!(current_ledger_object::get_field::<u32>(sfield::Flags).is_ok());
363            assert!(current_ledger_object::get_field::<AccountID>(sfield::Account).is_ok());
364
365            let result = current_ledger_object::get_field_optional::<u32>(sfield::Flags);
366            assert!(result.is_ok());
367            assert!(result.unwrap().is_some());
368        }
369
370        #[test]
371        fn test_ledger_object_module() {
372            // Test the ledger_object module's convenience functions
373            let slot = 0;
374            assert!(ledger_object::get_field::<u16>(slot, sfield::LedgerEntryType).is_ok());
375            assert!(ledger_object::get_field::<u32>(slot, sfield::Flags).is_ok());
376            assert!(ledger_object::get_field::<u64>(slot, sfield::Balance).is_ok());
377            assert!(ledger_object::get_field::<AccountID>(slot, sfield::Account).is_ok());
378            assert!(ledger_object::get_field::<Amount>(slot, sfield::Amount).is_ok());
379            assert!(ledger_object::get_field::<Hash128>(slot, sfield::EmailHash).is_ok());
380            assert!(ledger_object::get_field::<Hash256>(slot, sfield::PreviousTxnID).is_ok());
381            assert!(ledger_object::get_field::<Blob<33>>(slot, sfield::PublicKey).is_ok());
382
383            let result = ledger_object::get_field_optional::<u32>(slot, sfield::Flags);
384            assert!(result.is_ok());
385            assert!(result.unwrap().is_some());
386        }
387
388        // ========================================
389        // Type inference and compilation tests
390        // ========================================
391
392        #[test]
393        fn test_type_inference() {
394            let slot = 0;
395            // Verify type inference works with turbofish syntax
396            let _balance = get_field::<u64>(slot, sfield::Balance);
397            let _account = get_field::<AccountID>(slot, sfield::Account);
398
399            // Verify type inference works with type annotations
400            let _sequence: Result<u32> = get_field(slot, sfield::Sequence);
401            let _flags: Result<u32> = get_field(slot, sfield::Flags);
402        }
403
404        // ========================================
405        // Data size verification tests
406        // ========================================
407
408        #[test]
409        fn test_type_sizes() {
410            // Verify that returned types have the expected sizes
411            let hash128 = Hash128::get_from_current_ledger_obj(sfield::EmailHash).unwrap();
412            assert_eq!(hash128.as_bytes().len(), HASH128_SIZE);
413
414            let hash256 = Hash256::get_from_current_ledger_obj(sfield::PreviousTxnID).unwrap();
415            assert_eq!(hash256.as_bytes().len(), HASH256_SIZE);
416
417            let account = AccountID::get_from_current_ledger_obj(sfield::Account).unwrap();
418            assert_eq!(account.0.len(), ACCOUNT_ID_SIZE);
419
420            let blob: Blob<{ PUBLIC_KEY_BUFFER_SIZE }> =
421                Blob::get_from_current_ledger_obj(sfield::PublicKey).unwrap();
422            // In test environment, host returns buffer size as result code
423            assert_eq!(blob.len, PUBLIC_KEY_BUFFER_SIZE);
424            assert_eq!(blob.data.len(), PUBLIC_KEY_BUFFER_SIZE);
425        }
426    }
427}