Skip to main content

xrpl_wasm_stdlib/core/ledger_objects/
mod.rs

1pub mod account_root;
2pub mod array_object;
3pub mod traits;
4
5use crate::core::types::uint::{HASH160_SIZE, HASH192_SIZE, Hash160, Hash192};
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};
10use crate::sfield::SField;
11
12/// Trait for types that can be retrieved from ledger object fields.
13///
14/// This trait provides a unified interface for retrieving typed data from XRPL ledger objects,
15/// replacing the previous collection of type-specific functions with a generic, type-safe approach.
16///
17/// ## Supported Types
18///
19/// The following types implement this trait:
20/// - `u8` - 8-bit unsigned integers (1 byte)
21/// - `u16` - 16-bit unsigned integers (2 bytes)
22/// - `u32` - 32-bit unsigned integers (4 bytes)
23/// - `u64` - 64-bit unsigned integers (8 bytes)
24/// - `AccountID` - 20-byte account identifiers
25/// - `Amount` - XRP amounts and token amounts (variable size, up to 48 bytes)
26/// - `Hash128` - 128-bit cryptographic hashes (16 bytes)
27/// - `Hash256` - 256-bit cryptographic hashes (32 bytes)
28/// - `Blob<N>` - Variable-length binary data (generic over buffer size `N`)
29///
30/// ## Usage Patterns
31///
32/// ```rust,no_run
33/// use xrpl_wasm_stdlib::core::ledger_objects::{ledger_object, current_ledger_object};
34/// use xrpl_wasm_stdlib::core::types::account_id::AccountID;
35/// use xrpl_wasm_stdlib::core::types::amount::Amount;
36/// use xrpl_wasm_stdlib::sfield;
37///
38/// fn example() {
39///   let slot = 0;
40///   // Get a required field from a specific ledger object
41///   let balance = ledger_object::get_field(slot, sfield::Balance).unwrap();
42///   let account = ledger_object::get_field(slot, sfield::Account).unwrap();
43///
44///   // Get an optional field from the current ledger object
45///   let flags = current_ledger_object::get_field_optional(sfield::Flags).unwrap();
46/// }
47/// ```
48///
49/// ## Error Handling
50///
51/// - Required field methods return `Result<T>` and error if the field is missing.
52/// - Optional field methods return `Result<Option<T>>` and return `None` if the field is missing.
53/// - All methods return appropriate errors for buffer size mismatches or other retrieval failures.
54///
55/// ## Safety Considerations
56///
57/// - All implementations use appropriately sized buffers for their data types
58/// - Buffer sizes are validated against expected field sizes where applicable
59/// - Unsafe operations are contained within the host function calls
60pub trait LedgerObjectFieldGetter: Sized {
61    /// Get a required field from the current ledger object.
62    ///
63    /// # Arguments
64    ///
65    /// * `field_code` - The field code identifying which field to retrieve
66    ///
67    /// # Returns
68    ///
69    /// Returns a `Result<Self>` where:
70    /// * `Ok(Self)` - The field value for the specified field
71    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
72    fn get_from_current_ledger_obj<const CODE: i32>(field: SField<Self, CODE>) -> Result<Self>;
73
74    /// Get an optional field from the current ledger object.
75    ///
76    /// # Arguments
77    ///
78    /// * `field` - The SField identifying which field to retrieve
79    ///
80    /// # Returns
81    ///
82    /// Returns a `Result<Option<Self>>` where:
83    /// * `Ok(Some(Self))` - The field value for the specified field
84    /// * `Ok(None)` - If the field is not present
85    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
86    fn get_from_current_ledger_obj_optional<const CODE: i32>(
87        field: SField<Self, CODE>,
88    ) -> Result<Option<Self>>;
89
90    /// Get a required field from a specific ledger object.
91    ///
92    /// # Arguments
93    ///
94    /// * `register_num` - The register number holding the ledger object
95    /// * `field` - The SField identifying which field to retrieve
96    ///
97    /// # Returns
98    ///
99    /// Returns a `Result<Self>` where:
100    /// * `Ok(Self)` - The field value for the specified field
101    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
102    fn get_from_ledger_obj<const CODE: i32>(
103        register_num: i32,
104        field: SField<Self, CODE>,
105    ) -> Result<Self>;
106
107    /// Get an optional field from a specific ledger object.
108    ///
109    /// # Arguments
110    ///
111    /// * `register_num` - The register number holding the ledger object
112    /// * `field` - The SField identifying which field to retrieve
113    ///
114    /// # Returns
115    ///
116    /// Returns a `Result<Option<Self>>` where:
117    /// * `Ok(Some(Self))` - The field value for the specified field
118    /// * `Ok(None)` - If the field is not present in the ledger object
119    /// * `Err(Error)` - If the field retrieval operation failed
120    fn get_from_ledger_obj_optional<const CODE: i32>(
121        register_num: i32,
122        field: SField<Self, CODE>,
123    ) -> Result<Option<Self>>;
124}
125
126/// Trait for types that can be retrieved as fixed-size fields from ledger objects.
127///
128/// This trait enables a generic implementation of `LedgerObjectFieldGetter` for all fixed-size
129/// unsigned integer types (u8, u16, u32, u64). Types implementing this trait must
130/// have a known, constant size in bytes.
131///
132/// # Implementing Types
133///
134/// - `u8` - 1 byte
135/// - `u16` - 2 bytes
136/// - `u32` - 4 bytes
137/// - `u64` - 8 bytes
138trait FixedSizeFieldType: Sized {
139    /// The size of this type in bytes
140    const SIZE: usize;
141}
142
143impl FixedSizeFieldType for u8 {
144    const SIZE: usize = 1;
145}
146
147impl FixedSizeFieldType for u16 {
148    const SIZE: usize = 2;
149}
150
151impl FixedSizeFieldType for u32 {
152    const SIZE: usize = 4;
153}
154
155impl FixedSizeFieldType for u64 {
156    const SIZE: usize = 8;
157}
158
159/// Generic implementation of `LedgerObjectFieldGetter` for all fixed-size unsigned integer types.
160///
161/// This single implementation handles u8, u16, u32, and u64 by leveraging the
162/// `FixedSizeFieldType` trait. The implementation:
163/// - Allocates a buffer of the appropriate size
164/// - Calls the host function to retrieve the field
165/// - Validates that the returned byte count matches the expected size
166/// - Converts the buffer to the target type
167///
168/// # Buffer Management
169///
170/// Uses `MaybeUninit` for efficient stack allocation without initialization overhead.
171/// The buffer size is determined at compile-time via the `SIZE` constant.
172impl<T: FixedSizeFieldType> LedgerObjectFieldGetter for T {
173    #[inline]
174    fn get_from_current_ledger_obj<const CODE: i32>(field: SField<Self, CODE>) -> Result<Self> {
175        let mut value = core::mem::MaybeUninit::<T>::uninit();
176        let result_code = unsafe {
177            get_current_ledger_obj_field(i32::from(field), value.as_mut_ptr().cast(), T::SIZE)
178        };
179        match_result_code_with_expected_bytes(result_code, T::SIZE, || unsafe {
180            value.assume_init()
181        })
182    }
183
184    #[inline]
185    fn get_from_current_ledger_obj_optional<const CODE: i32>(
186        field: SField<Self, CODE>,
187    ) -> Result<Option<Self>> {
188        let mut value = core::mem::MaybeUninit::<T>::uninit();
189        let result_code = unsafe {
190            get_current_ledger_obj_field(i32::from(field), value.as_mut_ptr().cast(), T::SIZE)
191        };
192        match_result_code_with_expected_bytes_optional(result_code, T::SIZE, || {
193            Some(unsafe { value.assume_init() })
194        })
195    }
196
197    #[inline]
198    fn get_from_ledger_obj<const CODE: i32>(
199        register_num: i32,
200        field: SField<Self, CODE>,
201    ) -> Result<Self> {
202        let mut value = core::mem::MaybeUninit::<T>::uninit();
203        let result_code = unsafe {
204            get_ledger_obj_field(
205                register_num,
206                i32::from(field),
207                value.as_mut_ptr().cast(),
208                T::SIZE,
209            )
210        };
211        match_result_code_with_expected_bytes(result_code, T::SIZE, || unsafe {
212            value.assume_init()
213        })
214    }
215
216    #[inline]
217    fn get_from_ledger_obj_optional<const CODE: i32>(
218        register_num: i32,
219        field: SField<Self, CODE>,
220    ) -> Result<Option<Self>> {
221        let mut value = core::mem::MaybeUninit::<T>::uninit();
222        let result_code = unsafe {
223            get_ledger_obj_field(
224                register_num,
225                i32::from(field),
226                value.as_mut_ptr().cast(),
227                T::SIZE,
228            )
229        };
230        match_result_code_with_expected_bytes_optional(result_code, T::SIZE, || {
231            Some(unsafe { value.assume_init() })
232        })
233    }
234}
235
236/// Implementation of `LedgerObjectFieldGetter` for 160-bit cryptographic hashes.
237///
238/// This implementation handles 20-byte hash fields in XRPL ledger objects.
239/// Hash160 values are used for various cryptographic operations and identifiers.
240///
241/// # Buffer Management
242///
243/// Uses a 20-byte buffer (HASH160_SIZE) and validates that exactly 20 bytes
244/// are returned from the host function to ensure data integrity.
245impl LedgerObjectFieldGetter for Hash160 {
246    #[inline]
247    fn get_from_current_ledger_obj<const CODE: i32>(field: SField<Self, CODE>) -> Result<Self> {
248        let mut buffer = core::mem::MaybeUninit::<[u8; HASH160_SIZE]>::uninit();
249        let result_code = unsafe {
250            get_current_ledger_obj_field(i32::from(field), buffer.as_mut_ptr().cast(), HASH160_SIZE)
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_current_ledger_obj_optional<const CODE: i32>(
259        field: SField<Self, CODE>,
260    ) -> Result<Option<Self>> {
261        let mut buffer = core::mem::MaybeUninit::<[u8; HASH160_SIZE]>::uninit();
262        let result_code = unsafe {
263            get_current_ledger_obj_field(i32::from(field), buffer.as_mut_ptr().cast(), HASH160_SIZE)
264        };
265        match_result_code_with_expected_bytes_optional(result_code, HASH160_SIZE, || {
266            Some(Hash160::from(unsafe { buffer.assume_init() }))
267        })
268    }
269
270    #[inline]
271    fn get_from_ledger_obj<const CODE: i32>(
272        register_num: i32,
273        field: SField<Self, CODE>,
274    ) -> Result<Self> {
275        let mut buffer = core::mem::MaybeUninit::<[u8; HASH160_SIZE]>::uninit();
276        let result_code = unsafe {
277            get_ledger_obj_field(
278                register_num,
279                i32::from(field),
280                buffer.as_mut_ptr().cast(),
281                HASH160_SIZE,
282            )
283        };
284        match_result_code_with_expected_bytes(result_code, HASH160_SIZE, || {
285            Hash160::from(unsafe { buffer.assume_init() })
286        })
287    }
288
289    #[inline]
290    fn get_from_ledger_obj_optional<const CODE: i32>(
291        register_num: i32,
292        field: SField<Self, CODE>,
293    ) -> Result<Option<Self>> {
294        let mut buffer = core::mem::MaybeUninit::<[u8; HASH160_SIZE]>::uninit();
295        let result_code = unsafe {
296            get_ledger_obj_field(
297                register_num,
298                i32::from(field),
299                buffer.as_mut_ptr().cast(),
300                HASH160_SIZE,
301            )
302        };
303        match_result_code_with_expected_bytes_optional(result_code, HASH160_SIZE, || {
304            Some(Hash160::from(unsafe { buffer.assume_init() }))
305        })
306    }
307}
308
309/// Implementation of `LedgerObjectFieldGetter` for 192-bit cryptographic hashes.
310///
311/// This implementation handles 24-byte hash fields in XRPL ledger objects.
312/// Hash192 values are used for various cryptographic operations and identifiers.
313///
314/// # Buffer Management
315///
316/// Uses a 24-byte buffer (HASH192_SIZE) and validates that exactly 24 bytes
317/// are returned from the host function to ensure data integrity.
318impl LedgerObjectFieldGetter for Hash192 {
319    #[inline]
320    fn get_from_current_ledger_obj<const CODE: i32>(field: SField<Self, CODE>) -> Result<Self> {
321        let mut buffer = core::mem::MaybeUninit::<[u8; HASH192_SIZE]>::uninit();
322        let result_code = unsafe {
323            get_current_ledger_obj_field(i32::from(field), buffer.as_mut_ptr().cast(), HASH192_SIZE)
324        };
325        match_result_code_with_expected_bytes(result_code, HASH192_SIZE, || {
326            Hash192::from(unsafe { buffer.assume_init() })
327        })
328    }
329
330    #[inline]
331    fn get_from_current_ledger_obj_optional<const CODE: i32>(
332        field: SField<Self, CODE>,
333    ) -> Result<Option<Self>> {
334        let mut buffer = core::mem::MaybeUninit::<[u8; HASH192_SIZE]>::uninit();
335        let result_code = unsafe {
336            get_current_ledger_obj_field(i32::from(field), buffer.as_mut_ptr().cast(), HASH192_SIZE)
337        };
338        match_result_code_with_expected_bytes_optional(result_code, HASH192_SIZE, || {
339            Some(Hash192::from(unsafe { buffer.assume_init() }))
340        })
341    }
342
343    #[inline]
344    fn get_from_ledger_obj<const CODE: i32>(
345        register_num: i32,
346        field: SField<Self, CODE>,
347    ) -> Result<Self> {
348        let mut buffer = core::mem::MaybeUninit::<[u8; HASH192_SIZE]>::uninit();
349        let result_code = unsafe {
350            get_ledger_obj_field(
351                register_num,
352                i32::from(field),
353                buffer.as_mut_ptr().cast(),
354                HASH192_SIZE,
355            )
356        };
357        match_result_code_with_expected_bytes(result_code, HASH192_SIZE, || {
358            Hash192::from(unsafe { buffer.assume_init() })
359        })
360    }
361
362    #[inline]
363    fn get_from_ledger_obj_optional<const CODE: i32>(
364        register_num: i32,
365        field: SField<Self, CODE>,
366    ) -> Result<Option<Self>> {
367        let mut buffer = core::mem::MaybeUninit::<[u8; HASH192_SIZE]>::uninit();
368        let result_code = unsafe {
369            get_ledger_obj_field(
370                register_num,
371                i32::from(field),
372                buffer.as_mut_ptr().cast(),
373                HASH192_SIZE,
374            )
375        };
376        match_result_code_with_expected_bytes_optional(result_code, HASH192_SIZE, || {
377            Some(Hash192::from(unsafe { buffer.assume_init() }))
378        })
379    }
380}
381
382pub mod current_ledger_object {
383    use super::LedgerObjectFieldGetter;
384    use crate::host::Result;
385    use crate::sfield::SField;
386
387    /// Retrieves a field from the current ledger object.
388    ///
389    /// # Arguments
390    ///
391    /// * `field` - An SField constant that encodes both the field code and expected type
392    ///
393    /// # Returns
394    ///
395    /// Returns a `Result<T>` where:
396    /// * `Ok(T)` - The field value for the specified field
397    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
398    ///
399    /// # Example
400    ///
401    /// ```rust,no_run
402    /// use xrpl_wasm_stdlib::core::ledger_objects::current_ledger_object;
403    /// use xrpl_wasm_stdlib::sfield;
404    ///
405    /// // Type is automatically inferred from the SField constant
406    /// let flags = current_ledger_object::get_field(sfield::Flags).unwrap();  // u32
407    /// let balance = current_ledger_object::get_field(sfield::Balance).unwrap();  // u64
408    /// ```
409    #[inline]
410    pub fn get_field<T: LedgerObjectFieldGetter, const CODE: i32>(
411        field: SField<T, CODE>,
412    ) -> Result<T> {
413        T::get_from_current_ledger_obj(field)
414    }
415
416    /// Retrieves an optionally present field from the current ledger object.
417    ///
418    /// # Arguments
419    ///
420    /// * `field` - An SField constant that encodes both the field code and expected type
421    ///
422    /// # Returns
423    ///
424    /// Returns a `Result<Option<T>>` where:
425    /// * `Ok(Some(T))` - The field value for the specified field
426    /// * `Ok(None)` - If the field is not present
427    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
428    #[inline]
429    pub fn get_field_optional<T: LedgerObjectFieldGetter, const CODE: i32>(
430        field: SField<T, CODE>,
431    ) -> Result<Option<T>> {
432        T::get_from_current_ledger_obj_optional(field)
433    }
434
435    #[cfg(test)]
436    mod tests {
437        use super::*;
438        use crate::core::types::account_id::{ACCOUNT_ID_SIZE, AccountID};
439        use crate::core::types::amount::{AMOUNT_SIZE, Amount};
440        use crate::core::types::blob::{Blob, PUBLIC_KEY_BLOB_SIZE, PublicKeyBlob};
441        use crate::core::types::currency::{CURRENCY_SIZE, Currency};
442        use crate::core::types::issue::Issue;
443        use crate::core::types::public_key::PUBLIC_KEY_BUFFER_SIZE;
444        use crate::core::types::uint::{
445            HASH128_SIZE, HASH160_SIZE, HASH192_SIZE, HASH256_SIZE, Hash128, Hash160, Hash192,
446            Hash256,
447        };
448        use crate::host::host_bindings_trait::MockHostBindings;
449        use crate::host::setup_mock;
450        use crate::sfield::{self, SField};
451        use mockall::predicate::{always, eq};
452
453        // ========================================
454        // Test helper functions
455        // ========================================
456
457        /// Helper to set up a mock expectation for get_current_ledger_obj_field.
458        ///
459        /// Zero-fills the output buffer before returning. This is required because
460        /// `get_variable_size_field` and the fixed-size getters allocate the buffer
461        /// via `MaybeUninit` and call `assume_init` after the host call returns —
462        /// leaving the buffer uninitialized would be UB.
463        fn expect_current_field<
464            T: LedgerObjectFieldGetter + Send + std::fmt::Debug + PartialEq + 'static,
465            const CODE: i32,
466        >(
467            mock: &mut MockHostBindings,
468            field: SField<T, CODE>,
469            size: usize,
470            times: usize,
471        ) {
472            mock.expect_get_current_ledger_obj_field()
473                .with(eq(field), always(), eq(size))
474                .times(times)
475                .returning(move |_, buf, buf_size| {
476                    unsafe { core::ptr::write_bytes(buf, 0, buf_size) };
477                    size as i32
478                });
479        }
480
481        /// Like `expect_current_field`, but the host writes fewer bytes than the
482        /// buffer holds — used for variable-size fields (e.g. `Issue` uses a 40-byte
483        /// buffer but returns 20 bytes for the XRP variant).
484        fn expect_current_field_short<
485            T: LedgerObjectFieldGetter + Send + std::fmt::Debug + PartialEq + 'static,
486            const CODE: i32,
487        >(
488            mock: &mut MockHostBindings,
489            field: SField<T, CODE>,
490            buf_size: usize,
491            returned: i32,
492        ) {
493            mock.expect_get_current_ledger_obj_field()
494                .with(eq(field), always(), eq(buf_size))
495                .times(1)
496                .returning(move |_, buf, buf_size| {
497                    unsafe { core::ptr::write_bytes(buf, 0, buf_size) };
498                    returned
499                });
500        }
501
502        #[test]
503        fn test_current_basic_types() {
504            let mut mock = MockHostBindings::new();
505
506            expect_current_field(&mut mock, sfield::LedgerEntryType, 2, 1);
507            expect_current_field(&mut mock, sfield::Flags, 4, 1);
508            expect_current_field(&mut mock, sfield::OwnerNode, 8, 1);
509
510            let _guard = setup_mock(mock);
511
512            assert!(u16::get_from_current_ledger_obj(sfield::LedgerEntryType).is_ok());
513            assert!(u32::get_from_current_ledger_obj(sfield::Flags).is_ok());
514            assert!(u64::get_from_current_ledger_obj(sfield::OwnerNode).is_ok());
515        }
516
517        #[test]
518        fn test_current_xrpl_types() {
519            let mut mock = MockHostBindings::new();
520
521            expect_current_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
522            expect_current_field(&mut mock, sfield::Amount, AMOUNT_SIZE, 1);
523            expect_current_field(&mut mock, sfield::EmailHash, HASH128_SIZE, 1);
524            expect_current_field(&mut mock, sfield::PreviousTxnID, HASH256_SIZE, 1);
525            expect_current_field(&mut mock, sfield::PublicKey, PUBLIC_KEY_BLOB_SIZE, 1);
526            expect_current_field(&mut mock, sfield::TakerPaysCurrency, HASH160_SIZE, 1);
527            expect_current_field(&mut mock, sfield::MPTokenIssuanceID, HASH192_SIZE, 1);
528            expect_current_field(&mut mock, sfield::BaseAsset, CURRENCY_SIZE, 1);
529            expect_current_field_short(&mut mock, sfield::Asset, 40, 20);
530
531            let _guard = setup_mock(mock);
532
533            assert!(AccountID::get_from_current_ledger_obj(sfield::Account).is_ok());
534            assert!(Amount::get_from_current_ledger_obj(sfield::Amount).is_ok());
535            assert!(Hash128::get_from_current_ledger_obj(sfield::EmailHash).is_ok());
536            assert!(Hash256::get_from_current_ledger_obj(sfield::PreviousTxnID).is_ok());
537
538            let blob: PublicKeyBlob = Blob::get_from_current_ledger_obj(sfield::PublicKey).unwrap();
539            assert_eq!(blob.len, 33);
540
541            assert!(Hash160::get_from_current_ledger_obj(sfield::TakerPaysCurrency).is_ok());
542            assert!(Hash192::get_from_current_ledger_obj(sfield::MPTokenIssuanceID).is_ok());
543            assert!(Currency::get_from_current_ledger_obj(sfield::BaseAsset).is_ok());
544            assert!(Issue::get_from_current_ledger_obj(sfield::Asset).is_ok());
545        }
546
547        #[test]
548        fn test_current_optional_fields() {
549            let mut mock = MockHostBindings::new();
550
551            expect_current_field(&mut mock, sfield::Flags, 4, 1);
552            expect_current_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
553            expect_current_field(&mut mock, sfield::Amount, AMOUNT_SIZE, 1);
554            expect_current_field(&mut mock, sfield::EmailHash, HASH128_SIZE, 1);
555            expect_current_field(&mut mock, sfield::PreviousTxnID, HASH256_SIZE, 1);
556            expect_current_field(&mut mock, sfield::TakerPaysCurrency, HASH160_SIZE, 1);
557            expect_current_field(&mut mock, sfield::MPTokenIssuanceID, HASH192_SIZE, 1);
558            expect_current_field(&mut mock, sfield::BaseAsset, CURRENCY_SIZE, 1);
559            expect_current_field(&mut mock, sfield::PublicKey, PUBLIC_KEY_BLOB_SIZE, 1);
560            expect_current_field_short(&mut mock, sfield::Asset, 40, 20);
561
562            let _guard = setup_mock(mock);
563
564            let result = u32::get_from_current_ledger_obj_optional(sfield::Flags);
565            assert!(result.is_ok());
566            assert!(result.unwrap().is_some());
567
568            let result = AccountID::get_from_current_ledger_obj_optional(sfield::Account);
569            assert!(result.is_ok());
570            assert!(result.unwrap().is_some());
571
572            let result = Amount::get_from_current_ledger_obj_optional(sfield::Amount);
573            assert!(result.is_ok());
574            assert!(result.unwrap().is_some());
575
576            let result = Hash128::get_from_current_ledger_obj_optional(sfield::EmailHash);
577            assert!(result.is_ok());
578            assert!(result.unwrap().is_some());
579
580            let result = Hash256::get_from_current_ledger_obj_optional(sfield::PreviousTxnID);
581            assert!(result.is_ok());
582            assert!(result.unwrap().is_some());
583
584            let result = Hash160::get_from_current_ledger_obj_optional(sfield::TakerPaysCurrency);
585            assert!(result.is_ok());
586            assert!(result.unwrap().is_some());
587
588            let result = Hash192::get_from_current_ledger_obj_optional(sfield::MPTokenIssuanceID);
589            assert!(result.is_ok());
590            assert!(result.unwrap().is_some());
591
592            let result = Currency::get_from_current_ledger_obj_optional(sfield::BaseAsset);
593            assert!(result.is_ok());
594            assert!(result.unwrap().is_some());
595
596            let result = PublicKeyBlob::get_from_current_ledger_obj_optional(sfield::PublicKey);
597            assert!(result.is_ok());
598            assert!(result.unwrap().is_some());
599
600            let result = Issue::get_from_current_ledger_obj_optional(sfield::Asset);
601            assert!(result.is_ok());
602            assert!(result.unwrap().is_some());
603        }
604
605        // get_field / get_field_optional are thin wrappers; this test only verifies that they
606        // route correctly for one type each. Per-type coverage lives in test_current_xrpl_types.
607        #[test]
608        fn test_current_module_convenience_functions() {
609            let mut mock = MockHostBindings::new();
610
611            expect_current_field(&mut mock, sfield::Flags, 4, 2);
612            expect_current_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
613
614            let _guard = setup_mock(mock);
615
616            assert!(get_field(sfield::Flags).is_ok());
617            assert!(get_field(sfield::Account).is_ok());
618
619            let result = get_field_optional(sfield::Flags);
620            assert!(result.is_ok());
621            assert!(result.unwrap().is_some());
622        }
623
624        #[test]
625        fn test_type_sizes() {
626            let mut mock = MockHostBindings::new();
627
628            expect_current_field(&mut mock, sfield::EmailHash, HASH128_SIZE, 1);
629            expect_current_field(&mut mock, sfield::PreviousTxnID, HASH256_SIZE, 1);
630            expect_current_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
631            expect_current_field(&mut mock, sfield::PublicKey, PUBLIC_KEY_BUFFER_SIZE, 1);
632
633            let _guard = setup_mock(mock);
634
635            let hash128 = Hash128::get_from_current_ledger_obj(sfield::EmailHash).unwrap();
636            assert_eq!(hash128.as_bytes().len(), HASH128_SIZE);
637
638            let hash256 = Hash256::get_from_current_ledger_obj(sfield::PreviousTxnID).unwrap();
639            assert_eq!(hash256.as_bytes().len(), HASH256_SIZE);
640
641            let account = AccountID::get_from_current_ledger_obj(sfield::Account).unwrap();
642            assert_eq!(account.0.len(), ACCOUNT_ID_SIZE);
643
644            let blob: Blob<PUBLIC_KEY_BUFFER_SIZE> =
645                Blob::get_from_current_ledger_obj(sfield::PublicKey).unwrap();
646            assert_eq!(blob.len, PUBLIC_KEY_BUFFER_SIZE);
647            assert_eq!(blob.data.len(), PUBLIC_KEY_BUFFER_SIZE);
648        }
649
650        // Value-level tests: verify Issue variant detection by populating
651        // the mock buffer with known bytes (not just checking `is_ok()`).
652
653        #[test]
654        fn test_issue_decodes_xrp_variant() {
655            let mut mock = MockHostBindings::new();
656            expect_current_field_short(&mut mock, sfield::Asset, 40, 20);
657
658            let _guard = setup_mock(mock);
659
660            let issue = Issue::get_from_current_ledger_obj(sfield::Asset).unwrap();
661            assert!(matches!(issue, Issue::XRP(_)));
662        }
663
664        #[test]
665        fn test_issue_decodes_mpt_variant() {
666            let mut mock = MockHostBindings::new();
667            mock.expect_get_current_ledger_obj_field()
668                .with(eq(sfield::Asset), always(), eq(40))
669                .times(1)
670                .returning(|_, buf, _| {
671                    // 4 bytes seq=42 (big-endian) + 20 bytes issuer=0xAB → MPT
672                    let slice = unsafe { core::slice::from_raw_parts_mut(buf, 24) };
673                    slice[0..4].copy_from_slice(&42u32.to_be_bytes());
674                    slice[4..24].fill(0xAB);
675                    24
676                });
677
678            let _guard = setup_mock(mock);
679
680            let issue = Issue::get_from_current_ledger_obj(sfield::Asset).unwrap();
681            match issue {
682                Issue::MPT(mpt) => {
683                    assert_eq!(mpt.mpt_id().get_sequence_num(), 42);
684                    assert_eq!(mpt.mpt_id().get_issuer(), AccountID::from([0xAB; 20]));
685                }
686                _ => panic!("expected MPT variant"),
687            }
688        }
689
690        #[test]
691        fn test_issue_decodes_iou_variant() {
692            let mut mock = MockHostBindings::new();
693            mock.expect_get_current_ledger_obj_field()
694                .with(eq(sfield::Asset), always(), eq(40))
695                .times(1)
696                .returning(|_, buf, _| {
697                    // 20 bytes currency=0xCC + 20 bytes issuer=0xDD → IOU
698                    let slice = unsafe { core::slice::from_raw_parts_mut(buf, 40) };
699                    slice[0..20].fill(0xCC);
700                    slice[20..40].fill(0xDD);
701                    40
702                });
703
704            let _guard = setup_mock(mock);
705
706            let issue = Issue::get_from_current_ledger_obj(sfield::Asset).unwrap();
707            match issue {
708                Issue::IOU(iou) => {
709                    let bytes = iou.as_bytes();
710                    assert_eq!(&bytes[..20], &[0xCC; 20]);
711                    assert_eq!(&bytes[20..], &[0xDD; 20]);
712                }
713                _ => panic!("expected IOU variant"),
714            }
715        }
716
717        // Value-level tests: verify Amount variant detection by populating
718        // the mock buffer with known flag bits + payload.
719
720        #[test]
721        fn test_amount_decodes_xrp_variant() {
722            let mut mock = MockHostBindings::new();
723            mock.expect_get_current_ledger_obj_field()
724                .with(eq(sfield::Amount), always(), eq(48))
725                .times(1)
726                .returning(|_, buf, size| {
727                    // XRP positive 1000 drops: byte0 = 0x40 (positive bit, XRP type),
728                    // remaining 7 bytes hold the drop amount big-endian.
729                    let slice = unsafe { core::slice::from_raw_parts_mut(buf, size) };
730                    slice.fill(0);
731                    let mut be = 1000u64.to_be_bytes();
732                    be[0] |= 0x40; // set positive flag in top bits
733                    slice[0..8].copy_from_slice(&be);
734                    8
735                });
736
737            let _guard = setup_mock(mock);
738
739            let amount = Amount::get_from_current_ledger_obj(sfield::Amount).unwrap();
740            assert!(matches!(amount, Amount::XRP { num_drops: 1000 }));
741        }
742
743        #[test]
744        fn test_amount_decodes_mpt_variant() {
745            let mut mock = MockHostBindings::new();
746            mock.expect_get_current_ledger_obj_field()
747                .with(eq(sfield::Amount), always(), eq(48))
748                .times(1)
749                .returning(|_, buf, size| {
750                    // MPT positive: byte0 bit7=0 (not IOU), bit6=1 (positive), bit5=1 (MPT)
751                    // bytes[1..9]  = num_units big-endian
752                    // bytes[9..33] = MptId (4-byte seq + 20-byte issuer)
753                    let slice = unsafe { core::slice::from_raw_parts_mut(buf, size) };
754                    slice.fill(0);
755                    slice[0] = 0x60;
756                    slice[1..9].copy_from_slice(&100u64.to_be_bytes());
757                    slice[9..13].copy_from_slice(&7u32.to_be_bytes());
758                    slice[13..33].fill(0xAB);
759                    33
760                });
761
762            let _guard = setup_mock(mock);
763
764            let amount = Amount::get_from_current_ledger_obj(sfield::Amount).unwrap();
765            match amount {
766                Amount::MPT {
767                    num_units,
768                    is_positive,
769                    mpt_id,
770                } => {
771                    assert_eq!(num_units, 100);
772                    assert!(is_positive);
773                    assert_eq!(mpt_id.get_sequence_num(), 7);
774                    assert_eq!(mpt_id.get_issuer(), AccountID::from([0xAB; 20]));
775                }
776                _ => panic!("expected MPT variant"),
777            }
778        }
779
780        #[test]
781        fn test_amount_decodes_iou_variant() {
782            let mut mock = MockHostBindings::new();
783            mock.expect_get_current_ledger_obj_field()
784                .with(eq(sfield::Amount), always(), eq(48))
785                .times(1)
786                .returning(|_, buf, size| {
787                    // IOU: byte0 bit7=1; bytes[0..8]=OpaqueFloat (opaque, content
788                    // doesn't matter for variant detection), bytes[8..28]=currency,
789                    // bytes[28..48]=issuer.
790                    let slice = unsafe { core::slice::from_raw_parts_mut(buf, size) };
791                    slice.fill(0);
792                    slice[0] = 0x80;
793                    slice[8..28].fill(0xCC);
794                    slice[28..48].fill(0xDD);
795                    48
796                });
797
798            let _guard = setup_mock(mock);
799
800            let amount = Amount::get_from_current_ledger_obj(sfield::Amount).unwrap();
801            match amount {
802                Amount::IOU {
803                    issuer, currency, ..
804                } => {
805                    assert_eq!(issuer, AccountID::from([0xDD; 20]));
806                    assert_eq!(currency, Currency::from([0xCC; 20]));
807                }
808                _ => panic!("expected IOU variant"),
809            }
810        }
811    }
812}
813
814pub mod ledger_object {
815    use super::LedgerObjectFieldGetter;
816    use crate::host::Result;
817    use crate::sfield::SField;
818
819    /// Retrieves a field from a specified ledger object.
820    ///
821    /// # Arguments
822    ///
823    /// * `register_num` - The register number holding the ledger object to look for data in
824    /// * `field` - An SField constant that encodes both the field code and expected type
825    ///
826    /// # Returns
827    ///
828    /// Returns a `Result<T>` where:
829    /// * `Ok(T)` - The field value for the specified field
830    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
831    ///
832    /// # Example
833    ///
834    /// ```rust,no_run
835    /// use xrpl_wasm_stdlib::core::ledger_objects::ledger_object;
836    /// use xrpl_wasm_stdlib::sfield;
837    ///
838    /// // Type is automatically inferred from the SField constant
839    /// let balance = ledger_object::get_field(0, sfield::Balance).unwrap();  // Amount
840    /// let account = ledger_object::get_field(0, sfield::Account).unwrap();  // AccountID
841    /// ```
842    #[inline]
843    pub fn get_field<T: LedgerObjectFieldGetter, const CODE: i32>(
844        register_num: i32,
845        field: SField<T, CODE>,
846    ) -> Result<T> {
847        T::get_from_ledger_obj(register_num, field)
848    }
849
850    /// Retrieves an optionally present field from a specified ledger object.
851    ///
852    /// # Arguments
853    ///
854    /// * `register_num` - The register number holding the ledger object to look for data in
855    /// * `field` - An SField constant that encodes both the field code and expected type
856    ///
857    /// # Returns
858    ///
859    /// Returns a `Result<Option<T>>` where:
860    /// * `Ok(Some(T))` - The field value for the specified field
861    /// * `Ok(None)` - If the field is not present in the ledger object
862    /// * `Err(Error)` - If the field retrieval operation failed
863    #[inline]
864    pub fn get_field_optional<T: LedgerObjectFieldGetter, const CODE: i32>(
865        register_num: i32,
866        field: SField<T, CODE>,
867    ) -> Result<Option<T>> {
868        T::get_from_ledger_obj_optional(register_num, field)
869    }
870
871    #[cfg(test)]
872    mod tests {
873        use super::*;
874        use crate::core::types::account_id::{ACCOUNT_ID_SIZE, AccountID};
875        use crate::core::types::amount::{AMOUNT_SIZE, Amount};
876        use crate::core::types::blob::{Blob, PUBLIC_KEY_BLOB_SIZE, PublicKeyBlob};
877        use crate::core::types::currency::{CURRENCY_SIZE, Currency};
878        use crate::core::types::issue::Issue;
879        use crate::core::types::uint::{
880            HASH128_SIZE, HASH160_SIZE, HASH192_SIZE, HASH256_SIZE, Hash128, Hash160, Hash192,
881            Hash256,
882        };
883        use crate::host::host_bindings_trait::MockHostBindings;
884        use crate::host::setup_mock;
885        use crate::sfield::{self, SField};
886        use mockall::predicate::{always, eq};
887
888        /// Helper to set up a mock expectation for get_ledger_obj_field.
889        ///
890        /// Zero-fills the output buffer before returning. This is required because
891        /// the fixed-size getters allocate the buffer via `MaybeUninit` and call
892        /// `assume_init` after the host call returns — leaving the buffer uninitialized
893        /// would be UB.
894        fn expect_ledger_field<
895            T: LedgerObjectFieldGetter + Send + std::fmt::Debug + PartialEq + 'static,
896            const CODE: i32,
897        >(
898            mock: &mut MockHostBindings,
899            slot: i32,
900            field: SField<T, CODE>,
901            size: usize,
902            times: usize,
903        ) {
904            mock.expect_get_ledger_obj_field()
905                .with(eq(slot), eq(field), always(), eq(size))
906                .times(times)
907                .returning(move |_, _, buf, buf_size| {
908                    unsafe { core::ptr::write_bytes(buf, 0, buf_size) };
909                    size as i32
910                });
911        }
912
913        /// Like `expect_ledger_field`, but the host writes fewer bytes than the
914        /// buffer holds. See `expect_current_field_short`.
915        fn expect_ledger_field_short<
916            T: LedgerObjectFieldGetter + Send + std::fmt::Debug + PartialEq + 'static,
917            const CODE: i32,
918        >(
919            mock: &mut MockHostBindings,
920            slot: i32,
921            field: SField<T, CODE>,
922            buf_size: usize,
923            returned: i32,
924        ) {
925            mock.expect_get_ledger_obj_field()
926                .with(eq(slot), eq(field), always(), eq(buf_size))
927                .times(1)
928                .returning(move |_, _, buf, buf_size| {
929                    unsafe { core::ptr::write_bytes(buf, 0, buf_size) };
930                    returned
931                });
932        }
933
934        #[test]
935        fn test_ledger_basic_types() {
936            let mut mock = MockHostBindings::new();
937            let slot = 0;
938
939            expect_ledger_field(&mut mock, slot, sfield::LedgerEntryType, 2, 1);
940            expect_ledger_field(&mut mock, slot, sfield::Flags, 4, 1);
941            expect_ledger_field(&mut mock, slot, sfield::OwnerNode, 8, 1);
942
943            let _guard = setup_mock(mock);
944
945            assert!(u16::get_from_ledger_obj(slot, sfield::LedgerEntryType).is_ok());
946            assert!(u32::get_from_ledger_obj(slot, sfield::Flags).is_ok());
947            assert!(u64::get_from_ledger_obj(slot, sfield::OwnerNode).is_ok());
948        }
949
950        #[test]
951        fn test_ledger_xrpl_types() {
952            let mut mock = MockHostBindings::new();
953            let slot = 0;
954
955            expect_ledger_field(&mut mock, slot, sfield::Account, ACCOUNT_ID_SIZE, 1);
956            expect_ledger_field(&mut mock, slot, sfield::Amount, AMOUNT_SIZE, 1);
957            expect_ledger_field(&mut mock, slot, sfield::Balance, AMOUNT_SIZE, 1);
958            expect_ledger_field(&mut mock, slot, sfield::EmailHash, HASH128_SIZE, 1);
959            expect_ledger_field(&mut mock, slot, sfield::PreviousTxnID, HASH256_SIZE, 1);
960            expect_ledger_field(&mut mock, slot, sfield::PublicKey, PUBLIC_KEY_BLOB_SIZE, 1);
961            expect_ledger_field(&mut mock, slot, sfield::TakerPaysCurrency, HASH160_SIZE, 1);
962            expect_ledger_field(&mut mock, slot, sfield::MPTokenIssuanceID, HASH192_SIZE, 1);
963            expect_ledger_field(&mut mock, slot, sfield::BaseAsset, CURRENCY_SIZE, 1);
964            expect_ledger_field_short(&mut mock, slot, sfield::Asset, 40, 20);
965
966            let _guard = setup_mock(mock);
967
968            assert!(AccountID::get_from_ledger_obj(slot, sfield::Account).is_ok());
969            assert!(Amount::get_from_ledger_obj(slot, sfield::Amount).is_ok());
970            assert!(Amount::get_from_ledger_obj(slot, sfield::Balance).is_ok());
971            assert!(Hash128::get_from_ledger_obj(slot, sfield::EmailHash).is_ok());
972            assert!(Hash256::get_from_ledger_obj(slot, sfield::PreviousTxnID).is_ok());
973
974            let blob: PublicKeyBlob = Blob::get_from_ledger_obj(slot, sfield::PublicKey).unwrap();
975            assert_eq!(blob.len, PUBLIC_KEY_BLOB_SIZE);
976
977            assert!(Hash160::get_from_ledger_obj(slot, sfield::TakerPaysCurrency).is_ok());
978            assert!(Hash192::get_from_ledger_obj(slot, sfield::MPTokenIssuanceID).is_ok());
979            assert!(Currency::get_from_ledger_obj(slot, sfield::BaseAsset).is_ok());
980            assert!(Issue::get_from_ledger_obj(slot, sfield::Asset).is_ok());
981        }
982
983        #[test]
984        fn test_ledger_optional_fields() {
985            let mut mock = MockHostBindings::new();
986            let slot = 0;
987
988            expect_ledger_field(&mut mock, slot, sfield::SourceTag, 4, 1);
989            expect_ledger_field(&mut mock, slot, sfield::Destination, ACCOUNT_ID_SIZE, 1);
990            expect_ledger_field(&mut mock, slot, sfield::Amount, AMOUNT_SIZE, 1);
991            expect_ledger_field(&mut mock, slot, sfield::TakerPaysCurrency, HASH160_SIZE, 1);
992            expect_ledger_field(&mut mock, slot, sfield::MPTokenIssuanceID, HASH192_SIZE, 1);
993            expect_ledger_field(&mut mock, slot, sfield::BaseAsset, CURRENCY_SIZE, 1);
994            expect_ledger_field(&mut mock, slot, sfield::EmailHash, HASH128_SIZE, 1);
995            expect_ledger_field(&mut mock, slot, sfield::AccountTxnID, HASH256_SIZE, 1);
996            expect_ledger_field(&mut mock, slot, sfield::PublicKey, PUBLIC_KEY_BLOB_SIZE, 1);
997            expect_ledger_field_short(&mut mock, slot, sfield::Asset, 40, 20);
998
999            let _guard = setup_mock(mock);
1000
1001            let result = u32::get_from_ledger_obj_optional(slot, sfield::SourceTag);
1002            assert!(result.is_ok());
1003            assert!(result.unwrap().is_some());
1004
1005            let result = AccountID::get_from_ledger_obj_optional(slot, sfield::Destination);
1006            assert!(result.is_ok());
1007            assert!(result.unwrap().is_some());
1008
1009            let result = Amount::get_from_ledger_obj_optional(slot, sfield::Amount);
1010            assert!(result.is_ok());
1011            assert!(result.unwrap().is_some());
1012
1013            let result = Hash160::get_from_ledger_obj_optional(slot, sfield::TakerPaysCurrency);
1014            assert!(result.is_ok());
1015            assert!(result.unwrap().is_some());
1016
1017            let result = Hash192::get_from_ledger_obj_optional(slot, sfield::MPTokenIssuanceID);
1018            assert!(result.is_ok());
1019            assert!(result.unwrap().is_some());
1020
1021            let result = Currency::get_from_ledger_obj_optional(slot, sfield::BaseAsset);
1022            assert!(result.is_ok());
1023            assert!(result.unwrap().is_some());
1024
1025            let result = Hash128::get_from_ledger_obj_optional(slot, sfield::EmailHash);
1026            assert!(result.is_ok());
1027            assert!(result.unwrap().is_some());
1028
1029            let result = Hash256::get_from_ledger_obj_optional(slot, sfield::AccountTxnID);
1030            assert!(result.is_ok());
1031            assert!(result.unwrap().is_some());
1032
1033            let result = PublicKeyBlob::get_from_ledger_obj_optional(slot, sfield::PublicKey);
1034            assert!(result.is_ok());
1035            assert!(result.unwrap().is_some());
1036
1037            let result = Issue::get_from_ledger_obj_optional(slot, sfield::Asset);
1038            assert!(result.is_ok());
1039            assert!(result.unwrap().is_some());
1040        }
1041
1042        #[test]
1043        fn test_ledger_module_convenience_functions() {
1044            let mut mock = MockHostBindings::new();
1045            let slot = 0;
1046
1047            expect_ledger_field(&mut mock, slot, sfield::Flags, 4, 2);
1048            expect_ledger_field(&mut mock, slot, sfield::Account, ACCOUNT_ID_SIZE, 2);
1049            expect_ledger_field(&mut mock, slot, sfield::Balance, AMOUNT_SIZE, 1);
1050            expect_ledger_field(&mut mock, slot, sfield::PublicKey, PUBLIC_KEY_BLOB_SIZE, 1);
1051
1052            let _guard = setup_mock(mock);
1053
1054            assert!(get_field(slot, sfield::Flags).is_ok());
1055            assert!(get_field(slot, sfield::Account).is_ok());
1056            assert!(get_field(slot, sfield::Balance).is_ok());
1057            assert!(get_field(slot, sfield::PublicKey).is_ok());
1058
1059            let result = get_field_optional(slot, sfield::Flags);
1060            assert!(result.is_ok());
1061            assert!(result.unwrap().is_some());
1062
1063            let result = get_field_optional(slot, sfield::Account);
1064            assert!(result.is_ok());
1065            assert!(result.unwrap().is_some());
1066        }
1067
1068        #[test]
1069        fn test_type_inference() {
1070            let mut mock = MockHostBindings::new();
1071            let slot = 0;
1072
1073            expect_ledger_field(&mut mock, slot, sfield::Balance, AMOUNT_SIZE, 1);
1074            expect_ledger_field(&mut mock, slot, sfield::Account, ACCOUNT_ID_SIZE, 1);
1075            expect_ledger_field(&mut mock, slot, sfield::Sequence, 4, 1);
1076            expect_ledger_field(&mut mock, slot, sfield::Flags, 4, 1);
1077
1078            let _guard = setup_mock(mock);
1079
1080            let _balance = get_field(slot, sfield::Balance);
1081            let _account = get_field(slot, sfield::Account);
1082
1083            let _sequence: Result<u32> = get_field(slot, sfield::Sequence);
1084            let _flags: Result<u32> = get_field(slot, sfield::Flags);
1085        }
1086    }
1087}
1088
1089#[cfg(test)]
1090mod tests {
1091    use super::current_ledger_object;
1092    use super::ledger_object;
1093    use crate::sfield;
1094
1095    #[test]
1096    #[should_panic]
1097    fn test_array_get_field_panics() {
1098        let _ = current_ledger_object::get_field(sfield::Signers);
1099    }
1100
1101    #[test]
1102    #[should_panic]
1103    fn test_array_get_field_optional_panics() {
1104        let _ = current_ledger_object::get_field_optional(sfield::Signers);
1105    }
1106
1107    #[test]
1108    #[should_panic]
1109    fn test_array_get_field_with_slot_panics() {
1110        let _ = ledger_object::get_field(0, sfield::Signers);
1111    }
1112
1113    #[test]
1114    #[should_panic]
1115    fn test_array_get_field_optional_with_slot_panics() {
1116        let _ = ledger_object::get_field_optional(0, sfield::Signers);
1117    }
1118
1119    #[test]
1120    #[should_panic]
1121    fn test_object_get_field_panics() {
1122        let _ = current_ledger_object::get_field(sfield::Memo);
1123    }
1124
1125    #[test]
1126    #[should_panic]
1127    fn test_object_get_field_optional_panics() {
1128        let _ = current_ledger_object::get_field_optional(sfield::Memo);
1129    }
1130
1131    #[test]
1132    #[should_panic]
1133    fn test_object_get_field_with_slot_panics() {
1134        let _ = ledger_object::get_field(0, sfield::Memo);
1135    }
1136
1137    #[test]
1138    #[should_panic]
1139    fn test_object_get_field_optional_with_slot_panics() {
1140        let _ = ledger_object::get_field_optional(0, sfield::Memo);
1141    }
1142}