Skip to main content

xrpl_wasm_stdlib/core/current_tx/
mod.rs

1//! # Current Transaction Retrieval Module
2//!
3//! This module provides utilities for retrieving typed fields from the current XRPL transaction
4//! within the context of XRPL Programmability. It offers a safe, type-safe
5//! interface over the low-level host functions for accessing transaction data, such as from an
6//! `EscrowFinish` transaction.
7//!
8//! ## Overview
9//!
10//! When processing XRPL transactions in a permissionless programmability environment, you often
11//! need to extract specific fields like account IDs, hashes, public keys, and other data. This
12//! module provides convenient wrapper functions that handle the low-level buffer management
13//! and error handling required to safely retrieve these fields.
14//!
15//! ## Field Types Supported
16//!
17//! - **AccountID**: 20-byte account identifiers
18//! - **u32**: 32-bit unsigned integers
19//! - **Hash256**: 256-bit cryptographic hashes
20//! - **PublicKey**: 33-byte public keys
21//! - **Blob**: Variable-length binary data
22//!
23//! ## Optional vs Required Fields
24//!
25//! The module provides both optional and required variants for field retrieval:
26//!
27//! - **Required variants** (e.g., `get_u32_field`): Return an error if the field is missing
28//! - **Optional variants** (e.g., `get_optional_u32_field`): Return `None` if the field is missing
29//!
30//! ## Error Handling
31//!
32//! All functions return `Result<T>` or `Result<Option<T>>` types that encapsulate
33//! the custom error handling required for the XRPL Programmability environment.
34//!
35//! ## Safety Considerations
36//!
37//! - All functions use fixed-size buffers appropriate for their data types
38//! - Buffer sizes are validated against expected field sizes
39//! - Unsafe operations are contained within the low-level host function calls
40//! - Memory safety is ensured through proper buffer management
41//! - Field codes are validated by the underlying host functions
42//!
43//! ## Performance Notes
44//!
45//! - All functions are marked `#[inline]` to minimize call overhead
46//! - Buffer allocations are stack-based and have minimal cost
47//! - Host function calls are the primary performance bottleneck
48//!
49//! ## Example
50//!
51//! Get sender Account and optional flags:
52//!
53//! ```no_run
54//! use xrpl_wasm_stdlib::core::current_tx::escrow_finish::EscrowFinish;
55//! use xrpl_wasm_stdlib::core::current_tx::traits::TransactionCommonFields;
56//! let tx = EscrowFinish;
57//! let account = tx.get_account().unwrap_or_panic();
58//! let _flags = tx.get_flags().unwrap_or_panic();
59//! ```
60
61pub mod escrow_finish;
62pub mod traits;
63
64use crate::host::error_codes::{
65    match_result_code_with_expected_bytes, match_result_code_with_expected_bytes_optional,
66};
67use crate::host::{Result, get_tx_field};
68
69/// Trait for types that can be retrieved from current transaction fields.
70///
71/// This trait provides a unified interface for retrieving typed data from the current
72/// XRPL transaction being processed, replacing the previous collection of type-specific
73/// functions with a generic, type-safe approach.
74///
75/// ## Supported Types
76///
77/// The following types implement this trait:
78/// - `u32` - 32-bit unsigned integers for sequence numbers, flags, timestamps
79/// - `AccountID` - 20-byte account identifiers for transaction participants
80/// - `Amount` - XRP amounts and token amounts for transaction values
81/// - `Hash256` - 256-bit hashes for transaction IDs and references
82/// - `PublicKey` - 33-byte compressed public keys for cryptographic operations
83/// - `Blob<N>` - Variable-length binary data (generic over buffer size `N`)
84///
85/// ## Usage Patterns
86///
87/// ```rust,no_run
88/// use xrpl_wasm_stdlib::core::current_tx::{get_field, get_field_optional};
89/// use xrpl_wasm_stdlib::core::types::account_id::AccountID;
90/// use xrpl_wasm_stdlib::core::types::amount::Amount;
91/// use xrpl_wasm_stdlib::core::types::blob::{MemoBlob, MEMO_BLOB_SIZE};
92/// use xrpl_wasm_stdlib::sfield;
93/// # fn example() {
94///   // Get required fields from the current transaction
95///   let account: AccountID = get_field(sfield::Account).unwrap();
96///   let sequence: u32 = get_field(sfield::Sequence).unwrap();
97///   let fee: Amount = get_field(sfield::Fee).unwrap();
98///
99///   // Get optional fields from the current transaction
100///   let flags: Option<u32> = get_field_optional(sfield::Flags).unwrap();
101///   let memo: Option<MemoBlob> = get_field_optional(sfield::Memo).unwrap();
102/// # }
103/// ```
104///
105/// ## Error Handling
106///
107/// - Required field methods return `Result<T>` and error if the field is missing
108/// - Optional field methods return `Result<Option<T>>` and return `None` if the field is missing
109/// - All methods return appropriate errors for buffer size mismatches or other retrieval failures
110///
111/// ## Transaction Context
112///
113/// This trait operates on the "current transaction" - the transaction currently being
114/// processed in the XRPL Programmability environment. The transaction context is
115/// established by the XRPL host environment before calling into WASM code.
116///
117/// ## Safety Considerations
118///
119/// - All implementations use appropriately sized buffers for their data types
120/// - Buffer sizes are validated against expected field sizes where applicable
121/// - Unsafe operations are contained within the host function calls
122/// - Transaction field access is validated by the host environment
123pub trait CurrentTxFieldGetter: Sized {
124    /// Get a required field from the current transaction.
125    ///
126    /// This method retrieves a field that must be present in the transaction.
127    /// If the field is missing, an error is returned.
128    ///
129    /// # Arguments
130    ///
131    /// * `field_code` - The field code identifying which field to retrieve
132    ///
133    /// # Returns
134    ///
135    /// Returns a `Result<Self>` where:
136    /// * `Ok(Self)` - The field value for the specified field
137    /// * `Err(Error::FieldNotFound)` - If the field is not present in the transaction
138    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
139    fn get_from_current_tx(field_code: i32) -> Result<Self>;
140
141    /// Get an optional field from the current transaction.
142    ///
143    /// This method retrieves a field that may or may not be present in the transaction.
144    /// If the field is missing, `None` is returned rather than an error.
145    ///
146    /// # Arguments
147    ///
148    /// * `field_code` - The field code identifying which field to retrieve
149    ///
150    /// # Returns
151    ///
152    /// Returns a `Result<Option<Self>>` where:
153    /// * `Ok(Some(Self))` - The field value for the specified field
154    /// * `Ok(None)` - If the field is not present in the transaction
155    /// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
156    fn get_from_current_tx_optional(field_code: i32) -> Result<Option<Self>>;
157}
158
159/// Trait for types that can be retrieved as fixed-size fields from transactions.
160///
161/// This trait enables a generic implementation of `CurrentTxFieldGetter` for all fixed-size
162/// unsigned integer types (u8, u16, u32, u64). Types implementing this trait must
163/// have a known, constant size in bytes.
164///
165/// # Implementing Types
166///
167/// - `u8` - 1 byte
168/// - `u16` - 2 bytes
169/// - `u32` - 4 bytes
170/// - `u64` - 8 bytes
171trait FixedSizeFieldType: Sized {
172    /// The size of this type in bytes
173    const SIZE: usize;
174}
175
176impl FixedSizeFieldType for u8 {
177    const SIZE: usize = 1;
178}
179
180impl FixedSizeFieldType for u16 {
181    const SIZE: usize = 2;
182}
183
184impl FixedSizeFieldType for u32 {
185    const SIZE: usize = 4;
186}
187
188impl FixedSizeFieldType for u64 {
189    const SIZE: usize = 8;
190}
191
192/// Generic implementation of `CurrentTxFieldGetter` for all fixed-size unsigned integer types.
193///
194/// This single implementation handles u8, u16, u32, and u64 by leveraging the
195/// `FixedSizeFieldType` trait. The implementation:
196/// - Allocates a buffer of the appropriate size
197/// - Calls the host function to retrieve the field
198/// - Validates that the returned byte count matches the expected size
199/// - Converts the buffer to the target type
200///
201/// # Buffer Management
202///
203/// Uses `MaybeUninit` for efficient stack allocation without initialization overhead.
204/// The buffer size is determined at compile-time via the `SIZE` constant.
205impl<T: FixedSizeFieldType> CurrentTxFieldGetter for T {
206    #[inline]
207    fn get_from_current_tx(field_code: i32) -> Result<Self> {
208        let mut value = core::mem::MaybeUninit::<T>::uninit();
209        let result_code = unsafe { get_tx_field(field_code, value.as_mut_ptr().cast(), T::SIZE) };
210        match_result_code_with_expected_bytes(result_code, T::SIZE, || unsafe {
211            value.assume_init()
212        })
213    }
214
215    #[inline]
216    fn get_from_current_tx_optional(field_code: i32) -> Result<Option<Self>> {
217        let mut value = core::mem::MaybeUninit::<T>::uninit();
218        let result_code = unsafe { get_tx_field(field_code, value.as_mut_ptr().cast(), T::SIZE) };
219        match_result_code_with_expected_bytes_optional(result_code, T::SIZE, || {
220            Some(unsafe { value.assume_init() })
221        })
222    }
223}
224
225/// Retrieves a field from the current transaction.
226///
227/// # Arguments
228///
229/// * `field_code` - The field code identifying which field to retrieve (can be an i32 or SField)
230///
231/// # Returns
232///
233/// Returns a `Result<T>` where:
234/// * `Ok(T)` - The field value for the specified field
235/// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
236#[inline]
237pub fn get_field<T: CurrentTxFieldGetter, F: Into<i32>>(field_code: F) -> Result<T> {
238    T::get_from_current_tx(field_code.into())
239}
240
241/// Retrieves an optionally present field from the current transaction.
242///
243/// # Arguments
244///
245/// * `field_code` - The field code identifying which field to retrieve (can be an i32 or SField)
246///
247/// # Returns
248///
249/// Returns a `Result<Option<T>>` where:
250/// * `Ok(Some(T))` - The field value for the specified field
251/// * `Ok(None)` - If the field is not present
252/// * `Err(Error)` - If the field cannot be retrieved or has unexpected size
253#[inline]
254pub fn get_field_optional<T: CurrentTxFieldGetter, F: Into<i32>>(
255    field_code: F,
256) -> Result<Option<T>> {
257    T::get_from_current_tx_optional(field_code.into())
258}
259
260#[cfg(test)]
261mod tests {
262    use super::{CurrentTxFieldGetter, get_field, get_field_optional};
263    use crate::core::types::account_id::{ACCOUNT_ID_SIZE, AccountID};
264    use crate::core::types::amount::{AMOUNT_SIZE, Amount};
265    use crate::core::types::blob::{Blob, DEFAULT_BLOB_SIZE};
266    use crate::core::types::public_key::{PUBLIC_KEY_BUFFER_SIZE, PublicKey};
267    use crate::core::types::transaction_type::TransactionType;
268    use crate::core::types::uint::{HASH256_SIZE, Hash256};
269    use crate::host::host_bindings_trait::MockHostBindings;
270    use crate::host::setup_mock;
271    use crate::sfield;
272    use mockall::predicate::{always, eq};
273
274    /// Helper to set up a mock expectation for get_tx_field
275    fn expect_tx_field(mock: &mut MockHostBindings, field_code: i32, size: usize, times: usize) {
276        mock.expect_get_tx_field()
277            .with(eq(field_code), always(), eq(size))
278            .times(times)
279            .returning(move |_, _, _| size as i32);
280    }
281
282    #[test]
283    fn test_basic_types() {
284        let mut mock = MockHostBindings::new();
285
286        expect_tx_field(&mut mock, sfield::Flags.into(), 4, 1);
287        expect_tx_field(&mut mock, sfield::Sequence.into(), 4, 1);
288
289        let _guard = setup_mock(mock);
290
291        assert!(u32::get_from_current_tx(sfield::Flags.into()).is_ok());
292        assert!(u32::get_from_current_tx(sfield::Sequence.into()).is_ok());
293    }
294
295    #[test]
296    fn test_xrpl_types() {
297        let mut mock = MockHostBindings::new();
298
299        expect_tx_field(&mut mock, sfield::Account.into(), ACCOUNT_ID_SIZE, 1);
300        expect_tx_field(&mut mock, sfield::PreviousTxnID.into(), HASH256_SIZE, 1);
301        expect_tx_field(&mut mock, sfield::Fee.into(), AMOUNT_SIZE, 1);
302        expect_tx_field(
303            &mut mock,
304            sfield::SigningPubKey.into(),
305            PUBLIC_KEY_BUFFER_SIZE,
306            1,
307        );
308        expect_tx_field(&mut mock, sfield::TransactionType.into(), 2, 1);
309        expect_tx_field(&mut mock, sfield::MemoData.into(), DEFAULT_BLOB_SIZE, 1);
310
311        let _guard = setup_mock(mock);
312
313        assert!(AccountID::get_from_current_tx(sfield::Account.into()).is_ok());
314        assert!(Hash256::get_from_current_tx(sfield::PreviousTxnID.into()).is_ok());
315        assert!(Amount::get_from_current_tx(sfield::Fee.into()).is_ok());
316        assert!(PublicKey::get_from_current_tx(sfield::SigningPubKey.into()).is_ok());
317        assert!(TransactionType::get_from_current_tx(sfield::TransactionType.into()).is_ok());
318        assert!(Blob::<DEFAULT_BLOB_SIZE>::get_from_current_tx(sfield::MemoData.into()).is_ok());
319    }
320
321    #[test]
322    fn test_optional_variants() {
323        let mut mock = MockHostBindings::new();
324
325        expect_tx_field(&mut mock, sfield::Flags.into(), 4, 1);
326        expect_tx_field(&mut mock, sfield::Account.into(), ACCOUNT_ID_SIZE, 1);
327        expect_tx_field(&mut mock, sfield::Fee.into(), AMOUNT_SIZE, 1);
328        expect_tx_field(&mut mock, sfield::PreviousTxnID.into(), HASH256_SIZE, 1);
329        expect_tx_field(
330            &mut mock,
331            sfield::SigningPubKey.into(),
332            PUBLIC_KEY_BUFFER_SIZE,
333            1,
334        );
335        expect_tx_field(&mut mock, sfield::TransactionType.into(), 2, 1);
336        expect_tx_field(&mut mock, sfield::MemoData.into(), DEFAULT_BLOB_SIZE, 1);
337
338        let _guard = setup_mock(mock);
339
340        let result = u32::get_from_current_tx_optional(sfield::Flags.into());
341        assert!(result.is_ok());
342        assert!(result.unwrap().is_some());
343
344        let result = AccountID::get_from_current_tx_optional(sfield::Account.into());
345        assert!(result.is_ok());
346        assert!(result.unwrap().is_some());
347
348        let result = Amount::get_from_current_tx_optional(sfield::Fee.into());
349        assert!(result.is_ok());
350        assert!(result.unwrap().is_some());
351
352        let result = Hash256::get_from_current_tx_optional(sfield::PreviousTxnID.into());
353        assert!(result.is_ok());
354        assert!(result.unwrap().is_some());
355
356        let result = PublicKey::get_from_current_tx_optional(sfield::SigningPubKey.into());
357        assert!(result.is_ok());
358        assert!(result.unwrap().is_some());
359
360        let result = TransactionType::get_from_current_tx_optional(sfield::TransactionType.into());
361        assert!(result.is_ok());
362        assert!(result.unwrap().is_some());
363
364        let result =
365            Blob::<DEFAULT_BLOB_SIZE>::get_from_current_tx_optional(sfield::MemoData.into());
366        assert!(result.is_ok());
367        assert!(result.unwrap().is_some());
368    }
369
370    #[test]
371    fn test_get_field_convenience() {
372        let mut mock = MockHostBindings::new();
373
374        expect_tx_field(&mut mock, sfield::Flags.into(), 4, 2);
375        expect_tx_field(&mut mock, sfield::Account.into(), ACCOUNT_ID_SIZE, 1);
376
377        let _guard = setup_mock(mock);
378
379        assert!(get_field::<u32, _>(sfield::Flags).is_ok());
380        assert!(get_field::<AccountID, _>(sfield::Account).is_ok());
381
382        let result = get_field_optional::<u32, _>(sfield::Flags);
383        assert!(result.is_ok());
384        assert!(result.unwrap().is_some());
385    }
386
387    #[test]
388    fn test_get_field_returns_err_on_host_error() {
389        let mut mock = MockHostBindings::new();
390        mock.expect_get_tx_field()
391            .with(eq::<i32>(sfield::Flags.into()), always(), eq(4))
392            .times(1)
393            .returning(|_, _, _| crate::host::error_codes::INTERNAL_ERROR);
394
395        let _guard = setup_mock(mock);
396
397        assert!(get_field::<u32, _>(sfield::Flags).is_err());
398    }
399}