Skip to main content

xrpl_escrow_stdlib/current_tx/
traits.rs

1//! Escrow-finish-specific transaction field accessor trait.
2
3use xrpl_common_stdlib::core::current_tx::get_field;
4use xrpl_common_stdlib::core::current_tx::traits::TransactionCommonFields;
5use xrpl_common_stdlib::core::types::account_id::AccountID;
6use xrpl_common_stdlib::core::types::blob::{ConditionBlob, FulfillmentBlob};
7use xrpl_common_stdlib::host::error_codes::match_result_code_optional;
8use xrpl_common_stdlib::host::{Result, get_tx_field};
9use xrpl_common_stdlib::sfield;
10
11/// Trait providing access to fields specific to EscrowFinish transactions.
12///
13/// This trait extends `TransactionCommonFields` with methods for retrieving fields that are
14/// unique to EscrowFinish transactions. EscrowFinish transactions are used to complete
15/// time-based or condition-based escrows that were previously created with EscrowCreate
16/// transactions.
17///
18/// ## Implementation Requirements
19///
20/// Types implementing this trait should:
21/// - Also implement `TransactionCommonFields` for access to common transaction fields
22/// - Only be used in the context of processing EscrowFinish transactions
23/// - Ensure proper error handling when accessing conditional fields
24pub trait EscrowFinishFields: TransactionCommonFields {
25    /// Retrieves the owner account from the current EscrowFinish transaction.
26    ///
27    /// This mandatory field identifies the XRPL account that originally created the escrow
28    /// with an EscrowCreate transaction. The owner is the account that deposited the XRP
29    /// into the escrow and specified the conditions for its release.
30    ///
31    /// # Returns
32    ///
33    /// Returns a `Result<AccountID>` where:
34    /// * `Ok(AccountID)` - The 20-byte account identifier of the escrow owner
35    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
36    fn get_owner(&self) -> Result<AccountID> {
37        get_field(sfield::Owner)
38    }
39
40    /// Retrieves the offer sequence from the current EscrowFinish transaction.
41    ///
42    /// This mandatory field specifies the sequence number of the original EscrowCreate
43    /// transaction that created the escrow being finished. This creates a unique reference
44    /// to the specific escrow object, as escrows are identified by the combination of
45    /// the owner account and the sequence number of the creating transaction.
46    ///
47    /// # Returns
48    ///
49    /// Returns a `Result<u32>` where:
50    /// * `Ok(u32)` - The sequence number of the EscrowCreate transaction
51    /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
52    fn get_offer_sequence(&self) -> Result<u32> {
53        get_field(sfield::OfferSequence)
54    }
55
56    /// Retrieves the cryptographic condition from the current EscrowFinish transaction.
57    ///
58    /// This optional field contains the cryptographic condition in full crypto-condition format.
59    /// For PREIMAGE-SHA-256 conditions, this is 39 bytes:
60    /// - 2 bytes: type tag (A025)
61    /// - 2 bytes: fingerprint length tag (8020)
62    /// - 32 bytes: SHA-256 hash (fingerprint)
63    /// - 2 bytes: cost length tag (8101)
64    /// - 1 byte: cost value (00)
65    ///
66    /// # Returns
67    ///
68    /// Returns a `Result<Option<Condition>>` where:
69    /// * `Ok(Some(Condition))` - The full crypto-condition if the escrow is conditional
70    /// * `Ok(None)` - If the escrow has no cryptographic condition (time-based only)
71    /// * `Err(Error)` - If an error occurred during field retrieval
72    fn get_condition(&self) -> Result<Option<ConditionBlob>> {
73        let mut buffer = ConditionBlob::new();
74        let result_code = unsafe {
75            get_tx_field(
76                sfield::Condition.into(),
77                buffer.data.as_mut_ptr(),
78                buffer.capacity(),
79            )
80        };
81        match_result_code_optional(result_code, || {
82            buffer.len = result_code as usize;
83            (result_code > 0).then_some(buffer)
84        })
85    }
86
87    /// Retrieves the cryptographic fulfillment from the current EscrowFinish transaction.
88    ///
89    /// This optional field contains the cryptographic fulfillment that satisfies the condition
90    /// specified in the original EscrowCreate transaction. The fulfillment must cryptographically
91    /// prove that the condition's requirements have been met. This field is only required
92    /// when the escrow has an associated condition.
93    ///
94    /// # Returns
95    ///
96    /// Returns a `Result<Option<Fulfillment>>` where:
97    /// * `Ok(Some(Fulfillment))` - The fulfillment data if provided
98    /// * `Ok(None)` - If no fulfillment is provided (valid for unconditional escrows)
99    /// * `Err(Error)` - If an error occurred during field retrieval
100    ///
101    /// # Fulfillment Validation
102    ///
103    /// The XRPL network automatically validates that:
104    /// - The fulfillment satisfies the escrow's condition
105    /// - The fulfillment is properly formatted according to RFC 3814
106    /// - The cryptographic proof is mathematically valid
107    ///
108    /// # Size Limits
109    ///
110    /// Fulfillments are limited to 256 bytes in the current XRPL implementation.
111    /// This limit ensures network performance while supporting the most practical
112    /// cryptographic proof scenarios.
113    fn get_fulfillment(&self) -> Result<Option<FulfillmentBlob>> {
114        let mut buffer = FulfillmentBlob::new();
115        let result_code = unsafe {
116            get_tx_field(
117                sfield::Fulfillment.into(),
118                buffer.data.as_mut_ptr(),
119                buffer.capacity(),
120            )
121        };
122        match_result_code_optional(result_code, || {
123            buffer.len = result_code as usize;
124            (result_code > 0).then_some(buffer)
125        })
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use mockall::predicate::{always, eq};
132    use xrpl_common_stdlib::host::host_bindings_trait::MockHostBindings;
133    use xrpl_common_stdlib::sfield::SField;
134
135    /// Helper to set up a mock expectation for `get_tx_field`.
136    fn expect_tx_field<T: Send + std::fmt::Debug + PartialEq + 'static, const CODE: i32>(
137        mock: &mut MockHostBindings,
138        field: SField<T, CODE>,
139        size: usize,
140        times: usize,
141    ) {
142        mock.expect_get_tx_field()
143            .with(eq(field), always(), eq(size))
144            .times(times)
145            .returning(move |_, _, _| size as i32);
146    }
147
148    mod escrow_finish_fields {
149
150        mod optional_fields {
151            use crate::current_tx::escrow_finish::EscrowFinish;
152            use crate::current_tx::traits::EscrowFinishFields;
153            use crate::current_tx::traits::tests::expect_tx_field;
154            use xrpl_common_stdlib::core::types::blob::{
155                CONDITION_BLOB_SIZE, FULFILLMENT_BLOB_SIZE,
156            };
157            use xrpl_common_stdlib::host::error_codes::{
158                FIELD_NOT_FOUND, INTERNAL_ERROR, INVALID_FIELD,
159            };
160            use xrpl_common_stdlib::host::host_bindings_trait::MockHostBindings;
161            use xrpl_common_stdlib::host::setup_mock;
162            use xrpl_common_stdlib::sfield;
163
164            use mockall::predicate::{always, eq};
165            use xrpl_common_stdlib::sfield::{Condition, Fulfillment};
166
167            #[test]
168            fn test_optional_fields_return_some() {
169                let mut mock = MockHostBindings::new();
170
171                // get_condition
172                expect_tx_field(&mut mock, Condition, CONDITION_BLOB_SIZE, 1);
173                // get_fulfillment
174                expect_tx_field(&mut mock, Fulfillment, FULFILLMENT_BLOB_SIZE, 1);
175
176                let _guard = setup_mock(mock);
177
178                let escrow = EscrowFinish;
179
180                // All optional fields should return Ok(Some(...))
181                let condition = escrow.get_condition().unwrap();
182                assert!(condition.is_some());
183                assert_eq!(condition.unwrap().len, CONDITION_BLOB_SIZE);
184
185                let fulfillment = escrow.get_fulfillment().unwrap();
186                assert!(fulfillment.is_some());
187                assert_eq!(fulfillment.unwrap().len, FULFILLMENT_BLOB_SIZE);
188            }
189
190            #[test]
191            fn test_optional_fields_return_none_when_zero_length() {
192                let mut mock = MockHostBindings::new();
193
194                // get_condition - returns None when result code is 0
195                mock.expect_get_tx_field()
196                    .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
197                    .times(1)
198                    .returning(|_, _, _| 0);
199                // get_fulfillment - returns None when result code is 0
200                mock.expect_get_tx_field()
201                    .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
202                    .times(1)
203                    .returning(|_, _, _| 0);
204
205                let _guard = setup_mock(mock);
206
207                let escrow = EscrowFinish;
208
209                // Variable-size optional fields return None when result code is 0 (not present)
210                assert!(escrow.get_condition().unwrap().is_none());
211                assert!(escrow.get_fulfillment().unwrap().is_none());
212            }
213
214            #[test]
215            fn test_optional_fields_return_error_on_internal_error() {
216                let mut mock = MockHostBindings::new();
217
218                // get_condition
219                mock.expect_get_tx_field()
220                    .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
221                    .times(1)
222                    .returning(|_, _, _| INTERNAL_ERROR);
223                // get_fulfillment
224                mock.expect_get_tx_field()
225                    .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
226                    .times(1)
227                    .returning(|_, _, _| INTERNAL_ERROR);
228
229                let _guard = setup_mock(mock);
230
231                let escrow = EscrowFinish;
232
233                // Optional fields should also return Err on INTERNAL_ERROR
234                let condition_result = escrow.get_condition();
235                assert!(condition_result.is_err());
236                assert_eq!(condition_result.err().unwrap().code(), INTERNAL_ERROR);
237
238                let fulfillment_result = escrow.get_fulfillment();
239                assert!(fulfillment_result.is_err());
240                assert_eq!(fulfillment_result.err().unwrap().code(), INTERNAL_ERROR);
241            }
242
243            #[test]
244            fn test_optional_fields_return_error_on_field_not_found() {
245                let mut mock = MockHostBindings::new();
246
247                // get_condition
248                mock.expect_get_tx_field()
249                    .with(eq(Condition), always(), eq(CONDITION_BLOB_SIZE))
250                    .times(1)
251                    .returning(|_, _, _| FIELD_NOT_FOUND);
252                // get_fulfillment
253                mock.expect_get_tx_field()
254                    .with(eq(Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
255                    .times(1)
256                    .returning(|_, _, _| FIELD_NOT_FOUND);
257
258                let _guard = setup_mock(mock);
259
260                let escrow = EscrowFinish;
261
262                // Optional fields return Err on FIELD_NOT_FOUND (not None)
263                let condition_result = escrow.get_condition();
264                assert!(condition_result.is_err());
265                assert_eq!(condition_result.err().unwrap().code(), FIELD_NOT_FOUND);
266
267                let fulfillment_result = escrow.get_fulfillment();
268                assert!(fulfillment_result.is_err());
269                assert_eq!(fulfillment_result.err().unwrap().code(), FIELD_NOT_FOUND);
270            }
271
272            #[test]
273            fn test_optional_fields_return_error_on_invalid_field() {
274                let mut mock = MockHostBindings::new();
275
276                // get_condition
277                mock.expect_get_tx_field()
278                    .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
279                    .times(1)
280                    .returning(|_, _, _| INVALID_FIELD);
281                // get_fulfillment
282                mock.expect_get_tx_field()
283                    .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
284                    .times(1)
285                    .returning(|_, _, _| INVALID_FIELD);
286
287                let _guard = setup_mock(mock);
288
289                let escrow = EscrowFinish;
290
291                // Optional fields should also return Err on INVALID_FIELD
292                let condition_result = escrow.get_condition();
293                assert!(condition_result.is_err());
294                assert_eq!(condition_result.err().unwrap().code(), INVALID_FIELD);
295
296                let fulfillment_result = escrow.get_fulfillment();
297                assert!(fulfillment_result.is_err());
298                assert_eq!(fulfillment_result.err().unwrap().code(), INVALID_FIELD);
299            }
300        }
301
302        mod mandatory_fields {
303            use crate::current_tx::escrow_finish::EscrowFinish;
304            use crate::current_tx::traits::EscrowFinishFields;
305            use crate::current_tx::traits::tests::expect_tx_field;
306            use mockall::predicate::{always, eq};
307            use xrpl_common_stdlib::core::types::account_id::ACCOUNT_ID_SIZE;
308            use xrpl_common_stdlib::host::error_codes::{
309                FIELD_NOT_FOUND, INTERNAL_ERROR, INVALID_FIELD,
310            };
311            use xrpl_common_stdlib::host::host_bindings_trait::MockHostBindings;
312            use xrpl_common_stdlib::host::setup_mock;
313            use xrpl_common_stdlib::sfield;
314
315            #[test]
316            fn test_mandatory_fields_return_ok() {
317                let mut mock = MockHostBindings::new();
318
319                // get_owner
320                expect_tx_field(&mut mock, sfield::Owner, ACCOUNT_ID_SIZE, 1);
321                // get_offer_sequence
322                expect_tx_field(&mut mock, sfield::OfferSequence, 4, 1);
323
324                let _guard = setup_mock(mock);
325
326                let escrow = EscrowFinish;
327
328                // All mandatory fields should return Ok
329                assert!(escrow.get_owner().is_ok());
330                assert!(escrow.get_offer_sequence().is_ok());
331            }
332
333            // Zero length for a mandatory fixed-size field panics (byte mismatch). One test
334            // per field, since `#[should_panic]` only catches the first panic.
335
336            #[test]
337            #[should_panic]
338            fn test_get_owner_panics_when_zero_length() {
339                let mut mock = MockHostBindings::new();
340                mock.expect_get_tx_field()
341                    .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
342                    .returning(|_, _, _| 0);
343
344                let _guard = setup_mock(mock);
345
346                let _ = EscrowFinish.get_owner();
347            }
348
349            #[test]
350            #[should_panic]
351            fn test_get_offer_sequence_panics_when_zero_length() {
352                let mut mock = MockHostBindings::new();
353                mock.expect_get_tx_field()
354                    .with(eq(sfield::OfferSequence), always(), eq(4))
355                    .returning(|_, _, _| 0);
356
357                let _guard = setup_mock(mock);
358
359                let _ = EscrowFinish.get_offer_sequence();
360            }
361
362            #[test]
363            fn test_mandatory_fields_return_error_on_field_not_found() {
364                let mut mock = MockHostBindings::new();
365
366                // get_owner
367                mock.expect_get_tx_field()
368                    .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
369                    .times(1)
370                    .returning(|_, _, _| FIELD_NOT_FOUND);
371                // get_offer_sequence
372                mock.expect_get_tx_field()
373                    .with(eq(sfield::OfferSequence), always(), eq(4))
374                    .times(1)
375                    .returning(|_, _, _| FIELD_NOT_FOUND);
376
377                let _guard = setup_mock(mock);
378
379                let escrow = EscrowFinish;
380
381                // All mandatory fields should return Err on FIELD_NOT_FOUND
382                let owner_result = escrow.get_owner();
383                assert!(owner_result.is_err());
384                assert_eq!(owner_result.err().unwrap().code(), FIELD_NOT_FOUND);
385
386                let offer_seq_result = escrow.get_offer_sequence();
387                assert!(offer_seq_result.is_err());
388                assert_eq!(offer_seq_result.err().unwrap().code(), FIELD_NOT_FOUND);
389            }
390
391            #[test]
392            fn test_mandatory_fields_return_error_on_internal_error() {
393                let mut mock = MockHostBindings::new();
394
395                // get_owner
396                mock.expect_get_tx_field()
397                    .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
398                    .times(1)
399                    .returning(|_, _, _| INTERNAL_ERROR);
400                // get_offer_sequence
401                mock.expect_get_tx_field()
402                    .with(eq(sfield::OfferSequence), always(), eq(4))
403                    .times(1)
404                    .returning(|_, _, _| INTERNAL_ERROR);
405
406                let _guard = setup_mock(mock);
407
408                let escrow = EscrowFinish;
409
410                // All mandatory fields should return Err on INTERNAL_ERROR
411                let owner_result = escrow.get_owner();
412                assert!(owner_result.is_err());
413                assert_eq!(owner_result.err().unwrap().code(), INTERNAL_ERROR);
414
415                let offer_seq_result = escrow.get_offer_sequence();
416                assert!(offer_seq_result.is_err());
417                assert_eq!(offer_seq_result.err().unwrap().code(), INTERNAL_ERROR);
418            }
419
420            #[test]
421            fn test_mandatory_fields_return_error_on_invalid_field() {
422                let mut mock = MockHostBindings::new();
423
424                // get_owner
425                mock.expect_get_tx_field()
426                    .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
427                    .times(1)
428                    .returning(|_, _, _| INVALID_FIELD);
429                // get_offer_sequence
430                mock.expect_get_tx_field()
431                    .with(eq(sfield::OfferSequence), always(), eq(4))
432                    .times(1)
433                    .returning(|_, _, _| INVALID_FIELD);
434
435                let _guard = setup_mock(mock);
436
437                let escrow = EscrowFinish;
438
439                // All mandatory fields should return Err on INVALID_FIELD
440                let owner_result = escrow.get_owner();
441                assert!(owner_result.is_err());
442                assert_eq!(owner_result.err().unwrap().code(), INVALID_FIELD);
443
444                let offer_seq_result = escrow.get_offer_sequence();
445                assert!(offer_seq_result.is_err());
446                assert_eq!(offer_seq_result.err().unwrap().code(), INVALID_FIELD);
447            }
448        }
449    }
450}