Skip to main content

xrpl_escrow_stdlib/current_tx/
traits.rs

1//! Escrow-finish-specific transaction field accessor trait.
2
3use xrpl_common_stdlib::current_tx::get_field;
4use xrpl_common_stdlib::current_tx::traits::TransactionCommonFields;
5use xrpl_common_stdlib::host::error_codes::match_result_code_optional;
6use xrpl_common_stdlib::host::{Result, tx_field};
7use xrpl_common_stdlib::sfield;
8use xrpl_common_stdlib::types::account_id::AccountID;
9use xrpl_common_stdlib::types::blob::{ConditionBlob, FulfillmentBlob};
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            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            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 `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_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::host::error_codes::{
155                FIELD_NOT_FOUND, INVALID_FIELD, SOME_ERROR,
156            };
157            use xrpl_common_stdlib::host::host_bindings_trait::MockHostBindings;
158            use xrpl_common_stdlib::host::setup_mock;
159            use xrpl_common_stdlib::sfield;
160            use xrpl_common_stdlib::types::blob::{CONDITION_BLOB_SIZE, FULFILLMENT_BLOB_SIZE};
161
162            use mockall::predicate::{always, eq};
163            use xrpl_common_stdlib::sfield::{Condition, Fulfillment};
164
165            #[test]
166            fn test_optional_fields_return_some() {
167                let mut mock = MockHostBindings::new();
168
169                // get_condition
170                expect_tx_field(&mut mock, Condition, CONDITION_BLOB_SIZE, 1);
171                // get_fulfillment
172                expect_tx_field(&mut mock, Fulfillment, FULFILLMENT_BLOB_SIZE, 1);
173
174                let _guard = setup_mock(mock);
175
176                let escrow = EscrowFinish;
177
178                // All optional fields should return Ok(Some(...))
179                let condition = escrow.get_condition().unwrap();
180                assert!(condition.is_some());
181                assert_eq!(condition.unwrap().len, CONDITION_BLOB_SIZE);
182
183                let fulfillment = escrow.get_fulfillment().unwrap();
184                assert!(fulfillment.is_some());
185                assert_eq!(fulfillment.unwrap().len, FULFILLMENT_BLOB_SIZE);
186            }
187
188            #[test]
189            fn test_optional_fields_return_none_when_zero_length() {
190                let mut mock = MockHostBindings::new();
191
192                // get_condition - returns None when result code is 0
193                mock.expect_tx_field()
194                    .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
195                    .times(1)
196                    .returning(|_, _, _| 0);
197                // get_fulfillment - returns None when result code is 0
198                mock.expect_tx_field()
199                    .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
200                    .times(1)
201                    .returning(|_, _, _| 0);
202
203                let _guard = setup_mock(mock);
204
205                let escrow = EscrowFinish;
206
207                // Variable-size optional fields return None when result code is 0 (not present)
208                assert!(escrow.get_condition().unwrap().is_none());
209                assert!(escrow.get_fulfillment().unwrap().is_none());
210            }
211
212            #[test]
213            fn test_optional_fields_return_error_on_internal_error() {
214                let mut mock = MockHostBindings::new();
215
216                // get_condition
217                mock.expect_tx_field()
218                    .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
219                    .times(1)
220                    .returning(|_, _, _| SOME_ERROR);
221                // get_fulfillment
222                mock.expect_tx_field()
223                    .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
224                    .times(1)
225                    .returning(|_, _, _| SOME_ERROR);
226
227                let _guard = setup_mock(mock);
228
229                let escrow = EscrowFinish;
230
231                // Optional fields should also return Err on SOME_ERROR
232                let condition_result = escrow.get_condition();
233                assert!(condition_result.is_err());
234                assert_eq!(condition_result.err().unwrap().code(), SOME_ERROR);
235
236                let fulfillment_result = escrow.get_fulfillment();
237                assert!(fulfillment_result.is_err());
238                assert_eq!(fulfillment_result.err().unwrap().code(), SOME_ERROR);
239            }
240
241            #[test]
242            fn test_optional_fields_return_error_on_field_not_found() {
243                let mut mock = MockHostBindings::new();
244
245                // get_condition
246                mock.expect_tx_field()
247                    .with(eq(Condition), always(), eq(CONDITION_BLOB_SIZE))
248                    .times(1)
249                    .returning(|_, _, _| FIELD_NOT_FOUND);
250                // get_fulfillment
251                mock.expect_tx_field()
252                    .with(eq(Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
253                    .times(1)
254                    .returning(|_, _, _| FIELD_NOT_FOUND);
255
256                let _guard = setup_mock(mock);
257
258                let escrow = EscrowFinish;
259
260                // Optional fields return Err on FIELD_NOT_FOUND (not None)
261                let condition_result = escrow.get_condition();
262                assert!(condition_result.is_err());
263                assert_eq!(condition_result.err().unwrap().code(), FIELD_NOT_FOUND);
264
265                let fulfillment_result = escrow.get_fulfillment();
266                assert!(fulfillment_result.is_err());
267                assert_eq!(fulfillment_result.err().unwrap().code(), FIELD_NOT_FOUND);
268            }
269
270            #[test]
271            fn test_optional_fields_return_error_on_invalid_field() {
272                let mut mock = MockHostBindings::new();
273
274                // get_condition
275                mock.expect_tx_field()
276                    .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
277                    .times(1)
278                    .returning(|_, _, _| INVALID_FIELD);
279                // get_fulfillment
280                mock.expect_tx_field()
281                    .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
282                    .times(1)
283                    .returning(|_, _, _| INVALID_FIELD);
284
285                let _guard = setup_mock(mock);
286
287                let escrow = EscrowFinish;
288
289                // Optional fields should also return Err on INVALID_FIELD
290                let condition_result = escrow.get_condition();
291                assert!(condition_result.is_err());
292                assert_eq!(condition_result.err().unwrap().code(), INVALID_FIELD);
293
294                let fulfillment_result = escrow.get_fulfillment();
295                assert!(fulfillment_result.is_err());
296                assert_eq!(fulfillment_result.err().unwrap().code(), INVALID_FIELD);
297            }
298        }
299
300        mod mandatory_fields {
301            use crate::current_tx::escrow_finish::EscrowFinish;
302            use crate::current_tx::traits::EscrowFinishFields;
303            use crate::current_tx::traits::tests::expect_tx_field;
304            use mockall::predicate::{always, eq};
305            use xrpl_common_stdlib::host::error_codes::{
306                FIELD_NOT_FOUND, INVALID_FIELD, SOME_ERROR,
307            };
308            use xrpl_common_stdlib::host::host_bindings_trait::MockHostBindings;
309            use xrpl_common_stdlib::host::setup_mock;
310            use xrpl_common_stdlib::sfield;
311            use xrpl_common_stdlib::types::account_id::ACCOUNT_ID_SIZE;
312
313            #[test]
314            fn test_mandatory_fields_return_ok() {
315                let mut mock = MockHostBindings::new();
316
317                // get_owner
318                expect_tx_field(&mut mock, sfield::Owner, ACCOUNT_ID_SIZE, 1);
319                // get_offer_sequence
320                expect_tx_field(&mut mock, sfield::OfferSequence, 4, 1);
321
322                let _guard = setup_mock(mock);
323
324                let escrow = EscrowFinish;
325
326                // All mandatory fields should return Ok
327                assert!(escrow.get_owner().is_ok());
328                assert!(escrow.get_offer_sequence().is_ok());
329            }
330
331            // A zero-length read of a mandatory fixed-size field fails `FieldDecoder::decode`'s
332            // length check and surfaces as `Err(InvalidDecoding)`.
333
334            #[test]
335            fn test_get_owner_errors_when_zero_length() {
336                let mut mock = MockHostBindings::new();
337                mock.expect_tx_field()
338                    .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
339                    .returning(|_, _, _| 0);
340
341                let _guard = setup_mock(mock);
342
343                let result = EscrowFinish.get_owner();
344                assert!(result.is_err());
345                assert_eq!(
346                    result.err().unwrap().code(),
347                    xrpl_common_stdlib::host::Error::InvalidDecoding.code()
348                );
349            }
350
351            #[test]
352            fn test_get_offer_sequence_errors_when_zero_length() {
353                let mut mock = MockHostBindings::new();
354                mock.expect_tx_field()
355                    .with(eq(sfield::OfferSequence), always(), eq(4))
356                    .returning(|_, _, _| 0);
357
358                let _guard = setup_mock(mock);
359
360                let result = EscrowFinish.get_offer_sequence();
361                assert!(result.is_err());
362                assert_eq!(
363                    result.err().unwrap().code(),
364                    xrpl_common_stdlib::host::Error::InvalidDecoding.code()
365                );
366            }
367
368            #[test]
369            fn test_mandatory_fields_return_error_on_field_not_found() {
370                let mut mock = MockHostBindings::new();
371
372                // get_owner
373                mock.expect_tx_field()
374                    .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
375                    .times(1)
376                    .returning(|_, _, _| FIELD_NOT_FOUND);
377                // get_offer_sequence
378                mock.expect_tx_field()
379                    .with(eq(sfield::OfferSequence), always(), eq(4))
380                    .times(1)
381                    .returning(|_, _, _| FIELD_NOT_FOUND);
382
383                let _guard = setup_mock(mock);
384
385                let escrow = EscrowFinish;
386
387                // All mandatory fields should return Err on FIELD_NOT_FOUND
388                let owner_result = escrow.get_owner();
389                assert!(owner_result.is_err());
390                assert_eq!(owner_result.err().unwrap().code(), FIELD_NOT_FOUND);
391
392                let offer_seq_result = escrow.get_offer_sequence();
393                assert!(offer_seq_result.is_err());
394                assert_eq!(offer_seq_result.err().unwrap().code(), FIELD_NOT_FOUND);
395            }
396
397            #[test]
398            fn test_mandatory_fields_return_error_on_internal_error() {
399                let mut mock = MockHostBindings::new();
400
401                // get_owner
402                mock.expect_tx_field()
403                    .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
404                    .times(1)
405                    .returning(|_, _, _| SOME_ERROR);
406                // get_offer_sequence
407                mock.expect_tx_field()
408                    .with(eq(sfield::OfferSequence), always(), eq(4))
409                    .times(1)
410                    .returning(|_, _, _| SOME_ERROR);
411
412                let _guard = setup_mock(mock);
413
414                let escrow = EscrowFinish;
415
416                // All mandatory fields should return Err on SOME_ERROR
417                let owner_result = escrow.get_owner();
418                assert!(owner_result.is_err());
419                assert_eq!(owner_result.err().unwrap().code(), SOME_ERROR);
420
421                let offer_seq_result = escrow.get_offer_sequence();
422                assert!(offer_seq_result.is_err());
423                assert_eq!(offer_seq_result.err().unwrap().code(), SOME_ERROR);
424            }
425
426            #[test]
427            fn test_mandatory_fields_return_error_on_invalid_field() {
428                let mut mock = MockHostBindings::new();
429
430                // get_owner
431                mock.expect_tx_field()
432                    .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
433                    .times(1)
434                    .returning(|_, _, _| INVALID_FIELD);
435                // get_offer_sequence
436                mock.expect_tx_field()
437                    .with(eq(sfield::OfferSequence), always(), eq(4))
438                    .times(1)
439                    .returning(|_, _, _| INVALID_FIELD);
440
441                let _guard = setup_mock(mock);
442
443                let escrow = EscrowFinish;
444
445                // All mandatory fields should return Err on INVALID_FIELD
446                let owner_result = escrow.get_owner();
447                assert!(owner_result.is_err());
448                assert_eq!(owner_result.err().unwrap().code(), INVALID_FIELD);
449
450                let offer_seq_result = escrow.get_offer_sequence();
451                assert!(offer_seq_result.is_err());
452                assert_eq!(offer_seq_result.err().unwrap().code(), INVALID_FIELD);
453            }
454        }
455    }
456}