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