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}