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