xrpl_wasm_stdlib/core/current_tx/
traits.rs

1//! # Transaction Field Access Traits
2//!
3//! This module defines traits for accessing fields from XRPL transactions in a type-safe manner.
4//! It provides a structured interface for retrieving both common transaction fields (shared across
5//! all transaction types) and transaction-specific fields (unique to particular transaction types).
6//!
7//! ## Overview
8//!
9//! XRPL transactions contain a variety of fields, some mandatory and others optional. This module
10//! organizes field access into logical groups:
11//!
12//! - **Common Fields**: Fields present in all XRPL transactions (Account, Fee, Sequence, etc.)
13//! - **Transaction-Specific Fields**: Fields unique to specific transaction types
14//!
15//! ## Design Philosophy
16//!
17//! The trait-based design provides several benefits:
18//!
19//! - **Type Safety**: Each field is accessed through methods with appropriate return types
20//! - **Composability**: Transaction types can implement multiple traits as needed
21//! - **Zero-Cost Abstraction**: Trait methods compile down to direct host function calls
22//! - **Extensibility**: New transaction types can easily implement the relevant traits
23//!
24//! ## Field Categories
25//!
26//! ### Mandatory vs. Optional Fields
27//!
28//! - **Mandatory fields** return `Result<T>` and will error if missing
29//! - **Optional fields** return `Result<Option<T>>` and return `None` if missing
30//!
31//! ### Field Types
32//!
33//! - **AccountID**: 20-byte account identifiers
34//! - **Hash256**: 256-bit cryptographic hashes
35//! - **Amount**: XRP amounts (with future support for tokens)
36//! - **u32**: 32-bit unsigned integers for sequence numbers, flags, etc.
37//! - **Blob**: Variable-length binary data
38//! - **PublicKey**: 33-byte compressed public keys
39//! - **TransactionType**: Enumerated transaction type identifiers
40
41use crate::core::current_tx::{get_field, get_field_optional};
42use crate::core::types::account_id::AccountID;
43use crate::core::types::amount::Amount;
44use crate::core::types::blob::{
45    CONDITION_BLOB_SIZE, ConditionBlob, FULFILLMENT_BLOB_SIZE, FulfillmentBlob, SignatureBlob,
46};
47use crate::core::types::public_key::PublicKey;
48use crate::core::types::transaction_type::TransactionType;
49use crate::core::types::uint::Hash256;
50use crate::host::error_codes::match_result_code_optional;
51use crate::host::{Error, Result, get_tx_field};
52use crate::sfield;
53
54/// Trait providing access to common fields present in all XRPL transactions.
55///
56/// ## Implementation Requirements
57///
58/// Types implementing this trait should ensure they are used only in the context of a valid
59/// XRPL transaction. The trait methods assume the current transaction context is properly
60/// established by the XRPL Programmability environment.
61pub trait TransactionCommonFields {
62    /// Retrieves the account field from the current transaction.
63    ///
64    /// This field identifies (Required) The unique address of the account that initiated the
65    /// transaction.
66    ///
67    /// # Returns
68    ///
69    /// Returns a `Result<AccountID>` where:
70    /// * `Ok(AccountID)` - The 20-byte account identifier of the transaction sender
71    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
72    fn get_account(&self) -> Result<AccountID> {
73        get_field(sfield::Account)
74    }
75
76    /// Retrieves the transaction type from the current transaction.
77    ///
78    /// This field specifies the type of transaction. Valid transaction types include:
79    /// Payment, OfferCreate, TrustSet, and many others.
80    ///
81    /// # Returns
82    ///
83    /// Returns a `Result<TransactionType>` where:
84    /// * `Ok(TransactionType)` - An enumerated value representing the transaction type
85    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
86    ///
87    fn get_transaction_type(&self) -> Result<TransactionType> {
88        get_field(sfield::TransactionType)
89    }
90
91    /// Retrieves the computation allowance from the current transaction.
92    ///
93    /// This field specifies the maximum computational resources that the transaction is
94    /// allowed to consume during execution in the XRPL Programmability environment.
95    /// It helps prevent runaway computations and ensures network stability.
96    ///
97    /// # Returns
98    ///
99    /// Returns a `Result<u32>` where:
100    /// * `Ok(u32)` - The computation allowance value in platform-defined units
101    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
102    fn get_computation_allowance(&self) -> Result<u32> {
103        get_field(sfield::ComputationAllowance)
104    }
105
106    /// Retrieves the fee amount from the current transaction.
107    ///
108    /// This field specifies the amount of XRP (in drops) that the sender is willing to pay
109    /// as a transaction fee. The fee is consumed regardless of whether the transaction
110    /// succeeds or fails, and higher fees can improve transaction priority during
111    /// network congestion.
112    ///
113    /// # Returns
114    ///
115    /// Returns a `Result<Amount>` where:
116    /// * `Ok(Amount)` - The fee amount as an XRP amount in drops
117    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
118    ///
119    /// # Note
120    ///
121    /// Returns XRP amounts only (for now). Future versions may support other token types
122    /// when the underlying amount handling is enhanced.
123    fn get_fee(&self) -> Result<Amount> {
124        get_field(sfield::Fee)
125    }
126
127    /// Retrieves the sequence number from the current transaction.
128    ///
129    /// This field represents the sequence number of the account sending the transaction. A
130    /// transaction is only valid if the Sequence number is exactly 1 greater than the previous
131    /// transaction from the same account. The special case 0 means the transaction is using a
132    /// Ticket instead (Added by the TicketBatch amendment).
133    ///
134    /// # Returns
135    ///
136    /// Returns a `Result<u32>` where:
137    /// * `Ok(u32)` - The transaction sequence number
138    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
139    ///
140    /// # Note
141    ///
142    /// If the transaction uses tickets instead of sequence numbers, this field may not
143    /// be present. In such cases, use `get_ticket_sequence()` instead.
144    fn get_sequence(&self) -> Result<u32> {
145        get_field(sfield::Sequence)
146    }
147
148    /// Retrieves the account transaction ID from the current transaction.
149    ///
150    /// This optional field contains the hash value identifying another transaction. If provided,
151    /// this transaction is only valid if the sending account's previously sent transaction matches
152    /// the provided hash.
153    ///
154    /// # Returns
155    ///
156    /// Returns a `Result<Option<Hash256>>` where:
157    /// * `Ok(Some(Hash256))` - The hash of the required previous transaction
158    /// * `Ok(None)` - If no previous transaction requirement is specified
159    /// * `Err(Error)` - If an error occurred during field retrieval
160    fn get_account_txn_id(&self) -> Result<Option<Hash256>> {
161        get_field_optional(sfield::AccountTxnID)
162    }
163
164    /// Retrieves the `flags` field from the current transaction.
165    ///
166    /// This optional field contains a bitfield of transaction-specific flags that modify
167    /// the transaction's behavior.
168    ///
169    /// # Returns
170    ///
171    /// Returns a `Result<Option<u32>>` where:
172    /// * `Ok(Some(u32))` - The flags bitfield if present
173    /// * `Ok(None)` - If no flags are specified (equivalent to flags = 0)
174    /// * `Err(Error)` - If an error occurred during field retrieval
175    fn get_flags(&self) -> Result<Option<u32>> {
176        get_field_optional(sfield::Flags)
177    }
178
179    /// Retrieves the last ledger sequence from the current transaction.
180    ///
181    /// This optional field specifies the highest ledger index this transaction can appear in.
182    /// Specifying this field places a strict upper limit on how long the transaction can wait to
183    /// be validated or rejected. See Reliable Transaction Submission for more details.
184    ///
185    /// # Returns
186    ///
187    /// Returns a `Result<Option<u32>>` where:
188    /// * `Ok(Some(u32))` - The maximum ledger index for transaction inclusion
189    /// * `Ok(None)` - If no expiration is specified (transaction never expires)
190    /// * `Err(Error)` - If an error occurred during field retrieval
191    fn get_last_ledger_sequence(&self) -> Result<Option<u32>> {
192        get_field_optional(sfield::LastLedgerSequence)
193    }
194
195    /// Retrieves the network ID from the current transaction.
196    ///
197    /// This optional field identifies the network ID of the chain this transaction is intended for.
198    /// MUST BE OMITTED for Mainnet and some test networks. REQUIRED on chains whose network ID is
199    /// 1025 or higher.
200    ///
201    /// # Returns
202    ///
203    /// Returns a `Result<Option<u32>>` where:
204    /// * `Ok(Some(u32))` - The network identifier
205    /// * `Ok(None)` - If no specific network is specified (uses default network)
206    /// * `Err(Error)` - If an error occurred during field retrieval
207    fn get_network_id(&self) -> Result<Option<u32>> {
208        get_field_optional(sfield::NetworkID)
209    }
210
211    /// Retrieves the source tag from the current transaction.
212    ///
213    /// This optional field is an arbitrary integer used to identify the reason for this payment, or
214    /// a sender on whose behalf this transaction is made. Conventionally, a refund should specify
215    /// the initial payment's SourceTag as the refund payment's DestinationTag.
216    ///
217    /// # Returns
218    ///
219    /// Returns a `Result<Option<u32>>` where:
220    /// * `Ok(Some(u32))` - The source tag identifier
221    /// * `Ok(None)` - If no source tag is specified
222    /// * `Err(Error)` - If an error occurred during field retrieval
223    fn get_source_tag(&self) -> Result<Option<u32>> {
224        get_field_optional(sfield::SourceTag)
225    }
226
227    /// Retrieves the signing public key from the current transaction.
228    ///
229    /// This field contains the hex representation of the public key that corresponds to the
230    /// private key used to sign this transaction. If an empty string, this field indicates that a
231    /// multi-signature is present in the Signers field instead.
232    ///
233    /// # Returns
234    ///
235    /// Returns a `Result<PublicKey>` where:
236    /// * `Ok(PublicKey)` - The 33-byte compressed public key used for signing
237    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
238    ///
239    /// # Security Note
240    ///
241    /// The presence of this field doesn't guarantee the signature is valid. Instead, this field
242    /// only provides the key claimed to be used for signing. The XRPL network performs signature
243    /// validation before transaction execution.
244    fn get_signing_pub_key(&self) -> Result<PublicKey> {
245        get_field(sfield::SigningPubKey)
246    }
247
248    /// Retrieves the ticket sequence from the current transaction.
249    ///
250    /// This optional field provides the sequence number of the ticket to use in place of a
251    /// Sequence number. If this is provided, Sequence must be 0. Cannot be used with AccountTxnID.
252    ///
253    /// # Returns
254    ///
255    /// Returns a `Result<Option<u32>>` where:
256    /// * `Ok(Some(u32))` - The ticket sequence number if the transaction uses tickets
257    /// * `Ok(None)` - If the transaction uses traditional sequence numbering
258    /// * `Err(Error)` - If an error occurred during field retrieval
259    ///
260    /// # Note
261    ///
262    /// Transactions use either `Sequence` or `TicketSequence`, but not both. Check this
263    /// field when `get_sequence()` fails or when implementing ticket-aware logic.
264    fn get_ticket_sequence(&self) -> Result<Option<u32>> {
265        get_field_optional(sfield::TicketSequence)
266    }
267
268    /// Retrieves the transaction signature from the current transaction.
269    ///
270    /// This mandatory field contains the signature that verifies this transaction as originating
271    /// from the account it says it is from.
272    ///
273    /// Signatures can be either:
274    /// - 64 bytes for EdDSA (Ed25519) signatures
275    /// - 70-72 bytes for ECDSA (secp256k1) signatures
276    ///
277    /// # Returns
278    ///
279    /// Returns a `Result<Signature>` where:
280    /// * `Ok(Signature)` - The transaction signature (up to 72 bytes)
281    /// * `Err(Error)` - If the field cannot be retrieved
282    ///
283    /// # Security Note
284    ///
285    /// The signature is validated by the XRPL network before transaction execution.
286    /// In the programmability context, you can access the signature for logging or
287    /// analysis purposes, but signature validation has already been performed.
288    fn get_txn_signature(&self) -> Result<SignatureBlob> {
289        get_field(sfield::TxnSignature)
290    }
291}
292
293/// Trait providing access to fields specific to EscrowFinish transactions.
294///
295/// This trait extends `TransactionCommonFields` with methods for retrieving fields that are
296/// unique to EscrowFinish transactions. EscrowFinish transactions are used to complete
297/// time-based or condition-based escrows that were previously created with EscrowCreate
298/// transactions.
299///
300/// ## Implementation Requirements
301///
302/// Types implementing this trait should:
303/// - Also implement `TransactionCommonFields` for access to common transaction fields
304/// - Only be used in the context of processing EscrowFinish transactions
305/// - Ensure proper error handling when accessing conditional fields
306pub trait EscrowFinishFields: TransactionCommonFields {
307    /// Retrieves the owner account from the current EscrowFinish transaction.
308    ///
309    /// This mandatory field identifies the XRPL account that originally created the escrow
310    /// with an EscrowCreate transaction. The owner is the account that deposited the XRP
311    /// into the escrow and specified the conditions for its release.
312    ///
313    /// # Returns
314    ///
315    /// Returns a `Result<AccountID>` where:
316    /// * `Ok(AccountID)` - The 20-byte account identifier of the escrow owner
317    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
318    fn get_owner(&self) -> Result<AccountID> {
319        get_field(sfield::Owner)
320    }
321
322    /// Retrieves the offer sequence from the current EscrowFinish transaction.
323    ///
324    /// This mandatory field specifies the sequence number of the original EscrowCreate
325    /// transaction that created the escrow being finished. This creates a unique reference
326    /// to the specific escrow object, as escrows are identified by the combination of
327    /// the owner account and the sequence number of the creating transaction.
328    ///
329    /// # Returns
330    ///
331    /// Returns a `Result<u32>` where:
332    /// * `Ok(u32)` - The sequence number of the EscrowCreate transaction
333    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
334    fn get_offer_sequence(&self) -> Result<u32> {
335        get_field(sfield::OfferSequence)
336    }
337
338    /// Retrieves the cryptographic condition from the current EscrowFinish transaction.
339    ///
340    /// This optional field contains the cryptographic condition in full crypto-condition format.
341    /// For PREIMAGE-SHA-256 conditions, this is 39 bytes:
342    /// - 2 bytes: type tag (A025)
343    /// - 2 bytes: fingerprint length tag (8020)
344    /// - 32 bytes: SHA-256 hash (fingerprint)
345    /// - 2 bytes: cost length tag (8101)
346    /// - 1 byte: cost value (00)
347    ///
348    /// # Returns
349    ///
350    /// Returns a `Result<Option<Condition>>` where:
351    /// * `Ok(Some(Condition))` - The full crypto-condition if the escrow is conditional
352    /// * `Ok(None)` - If the escrow has no cryptographic condition (time-based only)
353    /// * `Err(Error)` - If an error occurred during field retrieval
354    fn get_condition(&self) -> Result<Option<ConditionBlob>> {
355        let mut buffer = [0u8; CONDITION_BLOB_SIZE];
356
357        let result_code =
358            unsafe { get_tx_field(sfield::Condition, buffer.as_mut_ptr(), buffer.len()) };
359
360        if result_code < 0 {
361            Result::Err(Error::from_code(result_code))
362        } else if result_code == 0 {
363            Result::Ok(None)
364        } else {
365            let blob = ConditionBlob {
366                data: buffer,
367                len: result_code as usize,
368            };
369            Result::Ok(Some(blob))
370        }
371    }
372
373    /// Retrieves the cryptographic fulfillment from the current EscrowFinish transaction.
374    ///
375    /// This optional field contains the cryptographic fulfillment that satisfies the condition
376    /// specified in the original EscrowCreate transaction. The fulfillment must cryptographically
377    /// prove that the condition's requirements have been met. This field is only required
378    /// when the escrow has an associated condition.
379    ///
380    /// # Returns
381    ///
382    /// Returns a `Result<Option<Fulfillment>>` where:
383    /// * `Ok(Some(Fulfillment))` - The fulfillment data if provided
384    /// * `Ok(None)` - If no fulfillment is provided (valid for unconditional escrows)
385    /// * `Err(Error)` - If an error occurred during field retrieval
386    ///
387    /// # Fulfillment Validation
388    ///
389    /// The XRPL network automatically validates that:
390    /// - The fulfillment satisfies the escrow's condition
391    /// - The fulfillment is properly formatted according to RFC 3814
392    /// - The cryptographic proof is mathematically valid
393    ///
394    /// # Size Limits
395    ///
396    /// Fulfillments are limited to 256 bytes in the current XRPL implementation.
397    /// This limit ensures network performance while supporting the most practical
398    /// cryptographic proof scenarios.
399    fn get_fulfillment(&self) -> Result<Option<FulfillmentBlob>> {
400        // Fulfillment fields are limited in rippled to 256 bytes, so we don't use `get_blob_field`
401        // but instead just use a smaller buffer directly.
402
403        let mut buffer = [0u8; FULFILLMENT_BLOB_SIZE]; // <-- 256 is the current rippled cap.
404
405        let result_code = unsafe { get_tx_field(sfield::Fulfillment, buffer.as_mut_ptr(), 256) };
406        match_result_code_optional(result_code, || {
407            let blob = FulfillmentBlob {
408                data: buffer,
409                len: result_code as usize,
410            };
411            Some(blob)
412        })
413    }
414}
415
416#[cfg(test)]
417mod tests {
418    use super::*;
419    use crate::core::current_tx::escrow_finish::EscrowFinish;
420
421    #[test]
422    fn test_get_condition_returns_some_with_data() {
423        // When the mock host function returns a positive value (buffer length),
424        // get_condition should return Ok(Some(ConditionBlob))
425        let escrow = EscrowFinish;
426        let result = escrow.get_condition();
427
428        // The mock returns buffer.len() which is CONDITION_BLOB_SIZE (128)
429        assert!(result.is_ok());
430        let condition_opt = result.unwrap();
431        assert!(condition_opt.is_some());
432
433        let condition = condition_opt.unwrap();
434        assert_eq!(condition.len, CONDITION_BLOB_SIZE);
435        assert_eq!(condition.capacity(), CONDITION_BLOB_SIZE);
436    }
437
438    #[test]
439    fn test_get_fulfillment_returns_some_with_data() {
440        // When the mock host function returns a positive value (buffer length),
441        // get_fulfillment should return Ok(Some(FulfillmentBlob))
442        let escrow = EscrowFinish;
443        let result = escrow.get_fulfillment();
444
445        // The mock returns buffer.len() which is 256 (the size passed to get_tx_field)
446        assert!(result.is_ok());
447        let fulfillment_opt = result.unwrap();
448        assert!(fulfillment_opt.is_some());
449
450        let fulfillment = fulfillment_opt.unwrap();
451        assert_eq!(fulfillment.len, 256);
452        assert_eq!(fulfillment.capacity(), FULFILLMENT_BLOB_SIZE);
453    }
454
455    #[test]
456    fn test_get_condition_and_fulfillment_independence() {
457        // Verify that get_condition and get_fulfillment can be called independently
458        let escrow = EscrowFinish;
459
460        let condition_result = escrow.get_condition();
461        let fulfillment_result = escrow.get_fulfillment();
462
463        assert!(condition_result.is_ok());
464        assert!(fulfillment_result.is_ok());
465
466        // Verify they have different sizes
467        if let (Some(condition), Some(fulfillment)) =
468            (condition_result.unwrap(), fulfillment_result.unwrap())
469        {
470            assert_eq!(condition.capacity(), CONDITION_BLOB_SIZE);
471            assert_eq!(fulfillment.capacity(), FULFILLMENT_BLOB_SIZE);
472            assert_ne!(condition.capacity(), fulfillment.capacity());
473        }
474    }
475}