Skip to main content

xrpl_wasm_stdlib/core/ledger_objects/
mod.rs

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