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::{ConditionBlob, FulfillmentBlob, SignatureBlob};
45use crate::core::types::public_key::PublicKey;
46use crate::core::types::transaction_type::TransactionType;
47use crate::core::types::uint::Hash256;
48use crate::host::error_codes::match_result_code_optional;
49use crate::host::{Result, get_tx_field};
50use crate::sfield;
51
52/// Trait providing access to common fields present in all XRPL transactions.
53///
54/// ## Implementation Requirements
55///
56/// Types implementing this trait should ensure they are used only in the context of a valid
57/// XRPL transaction. The trait methods assume the current transaction context is properly
58/// established by the XRPL Programmability environment.
59pub trait TransactionCommonFields {
60 /// Retrieves the account field from the current transaction.
61 ///
62 /// This field identifies (Required) The unique address of the account that initiated the
63 /// transaction.
64 ///
65 /// # Returns
66 ///
67 /// Returns a `Result<AccountID>` where:
68 /// * `Ok(AccountID)` - The 20-byte account identifier of the transaction sender
69 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
70 fn get_account(&self) -> Result<AccountID> {
71 get_field(sfield::Account)
72 }
73
74 /// Retrieves the transaction type from the current transaction.
75 ///
76 /// This field specifies the type of transaction. Valid transaction types include:
77 /// Payment, OfferCreate, TrustSet, and many others.
78 ///
79 /// # Returns
80 ///
81 /// Returns a `Result<TransactionType>` where:
82 /// * `Ok(TransactionType)` - An enumerated value representing the transaction type
83 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
84 ///
85 fn get_transaction_type(&self) -> Result<TransactionType> {
86 get_field(sfield::TransactionType)
87 }
88
89 /// Retrieves the computation allowance from the current transaction.
90 ///
91 /// This field specifies the maximum computational resources that the transaction is
92 /// allowed to consume during execution in the XRPL Programmability environment.
93 /// It helps prevent runaway computations and ensures network stability.
94 ///
95 /// # Returns
96 ///
97 /// Returns a `Result<u32>` where:
98 /// * `Ok(u32)` - The computation allowance value in platform-defined units
99 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
100 fn get_computation_allowance(&self) -> Result<u32> {
101 get_field(sfield::ComputationAllowance)
102 }
103
104 /// Retrieves the fee amount from the current transaction.
105 ///
106 /// This field specifies the amount of XRP (in drops) that the sender is willing to pay
107 /// as a transaction fee. The fee is consumed regardless of whether the transaction
108 /// succeeds or fails, and higher fees can improve transaction priority during
109 /// network congestion.
110 ///
111 /// # Returns
112 ///
113 /// Returns a `Result<Amount>` where:
114 /// * `Ok(Amount)` - The fee amount as an XRP amount in drops
115 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
116 ///
117 /// # Note
118 ///
119 /// Returns XRP amounts only (for now). Future versions may support other token types
120 /// when the underlying amount handling is enhanced.
121 fn get_fee(&self) -> Result<Amount> {
122 get_field(sfield::Fee)
123 }
124
125 /// Retrieves the sequence number from the current transaction.
126 ///
127 /// This field represents the sequence number of the account sending the transaction. A
128 /// transaction is only valid if the Sequence number is exactly 1 greater than the previous
129 /// transaction from the same account. The special case 0 means the transaction is using a
130 /// Ticket instead (Added by the TicketBatch amendment).
131 ///
132 /// # Returns
133 ///
134 /// Returns a `Result<u32>` where:
135 /// * `Ok(u32)` - The transaction sequence number
136 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
137 ///
138 /// # Note
139 ///
140 /// If the transaction uses tickets instead of sequence numbers, this field may not
141 /// be present. In such cases, use `get_ticket_sequence()` instead.
142 fn get_sequence(&self) -> Result<u32> {
143 get_field(sfield::Sequence)
144 }
145
146 /// Retrieves the account transaction ID from the current transaction.
147 ///
148 /// This optional field contains the hash value identifying another transaction. If provided,
149 /// this transaction is only valid if the sending account's previously sent transaction matches
150 /// the provided hash.
151 ///
152 /// # Returns
153 ///
154 /// Returns a `Result<Option<Hash256>>` where:
155 /// * `Ok(Some(Hash256))` - The hash of the required previous transaction
156 /// * `Ok(None)` - If no previous transaction requirement is specified
157 /// * `Err(Error)` - If an error occurred during field retrieval
158 fn get_account_txn_id(&self) -> Result<Option<Hash256>> {
159 get_field_optional(sfield::AccountTxnID)
160 }
161
162 /// Retrieves the `flags` field from the current transaction.
163 ///
164 /// This optional field contains a bitfield of transaction-specific flags that modify
165 /// the transaction's behavior.
166 ///
167 /// # Returns
168 ///
169 /// Returns a `Result<Option<u32>>` where:
170 /// * `Ok(Some(u32))` - The flags bitfield if present
171 /// * `Ok(None)` - If no flags are specified (equivalent to flags = 0)
172 /// * `Err(Error)` - If an error occurred during field retrieval
173 fn get_flags(&self) -> Result<Option<u32>> {
174 get_field_optional(sfield::Flags)
175 }
176
177 /// Retrieves the last ledger sequence from the current transaction.
178 ///
179 /// This optional field specifies the highest ledger index this transaction can appear in.
180 /// Specifying this field places a strict upper limit on how long the transaction can wait to
181 /// be validated or rejected. See Reliable Transaction Submission for more details.
182 ///
183 /// # Returns
184 ///
185 /// Returns a `Result<Option<u32>>` where:
186 /// * `Ok(Some(u32))` - The maximum ledger index for transaction inclusion
187 /// * `Ok(None)` - If no expiration is specified (transaction never expires)
188 /// * `Err(Error)` - If an error occurred during field retrieval
189 fn get_last_ledger_sequence(&self) -> Result<Option<u32>> {
190 get_field_optional(sfield::LastLedgerSequence)
191 }
192
193 /// Retrieves the network ID from the current transaction.
194 ///
195 /// This optional field identifies the network ID of the chain this transaction is intended for.
196 /// MUST BE OMITTED for Mainnet and some test networks. REQUIRED on chains whose network ID is
197 /// 1025 or higher.
198 ///
199 /// # Returns
200 ///
201 /// Returns a `Result<Option<u32>>` where:
202 /// * `Ok(Some(u32))` - The network identifier
203 /// * `Ok(None)` - If no specific network is specified (uses default network)
204 /// * `Err(Error)` - If an error occurred during field retrieval
205 fn get_network_id(&self) -> Result<Option<u32>> {
206 get_field_optional(sfield::NetworkID)
207 }
208
209 /// Retrieves the source tag from the current transaction.
210 ///
211 /// This optional field is an arbitrary integer used to identify the reason for this payment, or
212 /// a sender on whose behalf this transaction is made. Conventionally, a refund should specify
213 /// the initial payment's SourceTag as the refund payment's DestinationTag.
214 ///
215 /// # Returns
216 ///
217 /// Returns a `Result<Option<u32>>` where:
218 /// * `Ok(Some(u32))` - The source tag identifier
219 /// * `Ok(None)` - If no source tag is specified
220 /// * `Err(Error)` - If an error occurred during field retrieval
221 fn get_source_tag(&self) -> Result<Option<u32>> {
222 get_field_optional(sfield::SourceTag)
223 }
224
225 /// Retrieves the signing public key from the current transaction.
226 ///
227 /// This field contains the hex representation of the public key that corresponds to the
228 /// private key used to sign this transaction. If an empty string, this field indicates that a
229 /// multi-signature is present in the Signers field instead.
230 ///
231 /// # Returns
232 ///
233 /// Returns a `Result<Option<PublicKey>>` where:
234 /// * `Ok(Some(PublicKey))` - The 33-byte compressed public key for single-signature transactions
235 /// * `Ok(None)` - Empty SigningPubKey field, indicating a multi-signature transaction
236 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size (not 0 or 33 bytes)
237 ///
238 /// # Security Note
239 ///
240 /// The presence of this field doesn't guarantee the signature is valid. Instead, this field
241 /// only provides the key claimed to be used for signing. The XRPL network performs signature
242 /// validation before transaction execution.
243 fn get_signing_pub_key(&self) -> Result<Option<PublicKey>> {
244 get_field(sfield::SigningPubKey).and_then(|blob| match blob.len {
245 0 => Result::Ok(None), // Multi-signature transaction
246 33 => Result::Ok(Some(PublicKey::from(blob.data))), // Single-signature transaction
247 _ => Result::Err(crate::host::Error::from_code(
248 crate::host::error_codes::INTERNAL_ERROR,
249 )),
250 })
251 }
252
253 /// Retrieves the ticket sequence from the current transaction.
254 ///
255 /// This optional field provides the sequence number of the ticket to use in place of a
256 /// Sequence number. If this is provided, Sequence must be 0. Cannot be used with AccountTxnID.
257 ///
258 /// # Returns
259 ///
260 /// Returns a `Result<Option<u32>>` where:
261 /// * `Ok(Some(u32))` - The ticket sequence number if the transaction uses tickets
262 /// * `Ok(None)` - If the transaction uses traditional sequence numbering
263 /// * `Err(Error)` - If an error occurred during field retrieval
264 ///
265 /// # Note
266 ///
267 /// Transactions use either `Sequence` or `TicketSequence`, but not both. Check this
268 /// field when `get_sequence()` fails or when implementing ticket-aware logic.
269 fn get_ticket_sequence(&self) -> Result<Option<u32>> {
270 get_field_optional(sfield::TicketSequence)
271 }
272
273 /// Retrieves the transaction signature from the current transaction.
274 ///
275 /// This mandatory field contains the signature that verifies this transaction as originating
276 /// from the account it says it is from.
277 ///
278 /// Signatures can be either:
279 /// - 64 bytes for EdDSA (Ed25519) signatures
280 /// - 70-72 bytes for ECDSA (secp256k1) signatures
281 ///
282 /// # Returns
283 ///
284 /// Returns a `Result<Signature>` where:
285 /// * `Ok(Signature)` - The transaction signature (up to 72 bytes)
286 /// * `Err(Error)` - If the field cannot be retrieved
287 ///
288 /// # Security Note
289 ///
290 /// The signature is validated by the XRPL network before transaction execution.
291 /// In the programmability context, you can access the signature for logging or
292 /// analysis purposes, but signature validation has already been performed.
293 fn get_txn_signature(&self) -> Result<SignatureBlob> {
294 get_field(sfield::TxnSignature)
295 }
296}
297
298/// Trait providing access to fields specific to EscrowFinish transactions.
299///
300/// This trait extends `TransactionCommonFields` with methods for retrieving fields that are
301/// unique to EscrowFinish transactions. EscrowFinish transactions are used to complete
302/// time-based or condition-based escrows that were previously created with EscrowCreate
303/// transactions.
304///
305/// ## Implementation Requirements
306///
307/// Types implementing this trait should:
308/// - Also implement `TransactionCommonFields` for access to common transaction fields
309/// - Only be used in the context of processing EscrowFinish transactions
310/// - Ensure proper error handling when accessing conditional fields
311pub trait EscrowFinishFields: TransactionCommonFields {
312 /// Retrieves the owner account from the current EscrowFinish transaction.
313 ///
314 /// This mandatory field identifies the XRPL account that originally created the escrow
315 /// with an EscrowCreate transaction. The owner is the account that deposited the XRP
316 /// into the escrow and specified the conditions for its release.
317 ///
318 /// # Returns
319 ///
320 /// Returns a `Result<AccountID>` where:
321 /// * `Ok(AccountID)` - The 20-byte account identifier of the escrow owner
322 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
323 fn get_owner(&self) -> Result<AccountID> {
324 get_field(sfield::Owner)
325 }
326
327 /// Retrieves the offer sequence from the current EscrowFinish transaction.
328 ///
329 /// This mandatory field specifies the sequence number of the original EscrowCreate
330 /// transaction that created the escrow being finished. This creates a unique reference
331 /// to the specific escrow object, as escrows are identified by the combination of
332 /// the owner account and the sequence number of the creating transaction.
333 ///
334 /// # Returns
335 ///
336 /// Returns a `Result<u32>` where:
337 /// * `Ok(u32)` - The sequence number of the EscrowCreate transaction
338 /// * `Err(Error)` - If the field cannot be retrieved or has an unexpected size
339 fn get_offer_sequence(&self) -> Result<u32> {
340 get_field(sfield::OfferSequence)
341 }
342
343 /// Retrieves the cryptographic condition from the current EscrowFinish transaction.
344 ///
345 /// This optional field contains the cryptographic condition in full crypto-condition format.
346 /// For PREIMAGE-SHA-256 conditions, this is 39 bytes:
347 /// - 2 bytes: type tag (A025)
348 /// - 2 bytes: fingerprint length tag (8020)
349 /// - 32 bytes: SHA-256 hash (fingerprint)
350 /// - 2 bytes: cost length tag (8101)
351 /// - 1 byte: cost value (00)
352 ///
353 /// # Returns
354 ///
355 /// Returns a `Result<Option<Condition>>` where:
356 /// * `Ok(Some(Condition))` - The full crypto-condition if the escrow is conditional
357 /// * `Ok(None)` - If the escrow has no cryptographic condition (time-based only)
358 /// * `Err(Error)` - If an error occurred during field retrieval
359 fn get_condition(&self) -> Result<Option<ConditionBlob>> {
360 let mut buffer = ConditionBlob::new();
361 let result_code = unsafe {
362 get_tx_field(
363 sfield::Condition.into(),
364 buffer.data.as_mut_ptr(),
365 buffer.capacity(),
366 )
367 };
368 match_result_code_optional(result_code, || {
369 buffer.len = result_code as usize;
370 (result_code > 0).then_some(buffer)
371 })
372 }
373
374 /// Retrieves the cryptographic fulfillment from the current EscrowFinish transaction.
375 ///
376 /// This optional field contains the cryptographic fulfillment that satisfies the condition
377 /// specified in the original EscrowCreate transaction. The fulfillment must cryptographically
378 /// prove that the condition's requirements have been met. This field is only required
379 /// when the escrow has an associated condition.
380 ///
381 /// # Returns
382 ///
383 /// Returns a `Result<Option<Fulfillment>>` where:
384 /// * `Ok(Some(Fulfillment))` - The fulfillment data if provided
385 /// * `Ok(None)` - If no fulfillment is provided (valid for unconditional escrows)
386 /// * `Err(Error)` - If an error occurred during field retrieval
387 ///
388 /// # Fulfillment Validation
389 ///
390 /// The XRPL network automatically validates that:
391 /// - The fulfillment satisfies the escrow's condition
392 /// - The fulfillment is properly formatted according to RFC 3814
393 /// - The cryptographic proof is mathematically valid
394 ///
395 /// # Size Limits
396 ///
397 /// Fulfillments are limited to 256 bytes in the current XRPL implementation.
398 /// This limit ensures network performance while supporting the most practical
399 /// cryptographic proof scenarios.
400 fn get_fulfillment(&self) -> Result<Option<FulfillmentBlob>> {
401 let mut buffer = FulfillmentBlob::new();
402 let result_code = unsafe {
403 get_tx_field(
404 sfield::Fulfillment.into(),
405 buffer.data.as_mut_ptr(),
406 buffer.capacity(),
407 )
408 };
409 match_result_code_optional(result_code, || {
410 buffer.len = result_code as usize;
411 (result_code > 0).then_some(buffer)
412 })
413 }
414}
415
416#[cfg(test)]
417mod tests {
418
419 use crate::host::host_bindings_trait::MockHostBindings;
420 use crate::sfield::SField;
421 use mockall::predicate::{always, eq};
422 // ========================================
423 // Test helper functions
424 // ========================================
425
426 /// Helper to set up a mock expectation for get_tx_field
427 fn expect_tx_field<T: Send + std::fmt::Debug + PartialEq + 'static, const CODE: i32>(
428 mock: &mut MockHostBindings,
429 field: SField<T, CODE>,
430 size: usize,
431 times: usize,
432 ) {
433 mock.expect_get_tx_field()
434 .with(eq(field), always(), eq(size))
435 .times(times)
436 .returning(move |_, _, _| size as i32);
437 }
438
439 mod escrow_finish_fields {
440
441 mod optional_fields {
442 use crate::core::current_tx::escrow_finish::EscrowFinish;
443 use crate::core::current_tx::traits::EscrowFinishFields;
444 use crate::core::current_tx::traits::tests::expect_tx_field;
445 use crate::core::types::blob::{CONDITION_BLOB_SIZE, FULFILLMENT_BLOB_SIZE};
446 use crate::host::error_codes::{FIELD_NOT_FOUND, INTERNAL_ERROR, INVALID_FIELD};
447 use crate::host::host_bindings_trait::MockHostBindings;
448 use crate::host::setup_mock;
449 use crate::sfield;
450
451 use crate::sfield::{Condition, Fulfillment};
452 use mockall::predicate::{always, eq};
453
454 #[test]
455 fn test_optional_fields_return_some() {
456 let mut mock = MockHostBindings::new();
457
458 // get_condition
459 expect_tx_field(&mut mock, Condition, CONDITION_BLOB_SIZE, 1);
460 // get_fulfillment
461 expect_tx_field(&mut mock, Fulfillment, FULFILLMENT_BLOB_SIZE, 1);
462
463 let _guard = setup_mock(mock);
464
465 let escrow = EscrowFinish;
466
467 // All optional fields should return Ok(Some(...))
468 let condition = escrow.get_condition().unwrap();
469 assert!(condition.is_some());
470 assert_eq!(condition.unwrap().len, CONDITION_BLOB_SIZE);
471
472 let fulfillment = escrow.get_fulfillment().unwrap();
473 assert!(fulfillment.is_some());
474 assert_eq!(fulfillment.unwrap().len, FULFILLMENT_BLOB_SIZE);
475 }
476
477 #[test]
478 fn test_optional_fields_return_none_when_zero_length() {
479 let mut mock = MockHostBindings::new();
480
481 // get_condition - returns None when result code is 0
482 mock.expect_get_tx_field()
483 .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
484 .times(1)
485 .returning(|_, _, _| 0);
486 // get_fulfillment - returns None when result code is 0
487 mock.expect_get_tx_field()
488 .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
489 .times(1)
490 .returning(|_, _, _| 0);
491
492 let _guard = setup_mock(mock);
493
494 let escrow = EscrowFinish;
495
496 // Variable-size optional fields return None when result code is 0 (not present)
497 assert!(escrow.get_condition().unwrap().is_none());
498 assert!(escrow.get_fulfillment().unwrap().is_none());
499 }
500
501 #[test]
502 fn test_optional_fields_return_error_on_internal_error() {
503 let mut mock = MockHostBindings::new();
504
505 // get_condition
506 mock.expect_get_tx_field()
507 .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
508 .times(1)
509 .returning(|_, _, _| INTERNAL_ERROR);
510 // get_fulfillment
511 mock.expect_get_tx_field()
512 .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
513 .times(1)
514 .returning(|_, _, _| INTERNAL_ERROR);
515
516 let _guard = setup_mock(mock);
517
518 let escrow = EscrowFinish;
519
520 // Optional fields should also return Err on INTERNAL_ERROR
521 let condition_result = escrow.get_condition();
522 assert!(condition_result.is_err());
523 assert_eq!(condition_result.err().unwrap().code(), INTERNAL_ERROR);
524
525 let fulfillment_result = escrow.get_fulfillment();
526 assert!(fulfillment_result.is_err());
527 assert_eq!(fulfillment_result.err().unwrap().code(), INTERNAL_ERROR);
528 }
529
530 #[test]
531 fn test_optional_fields_return_error_on_field_not_found() {
532 let mut mock = MockHostBindings::new();
533
534 // get_condition
535 mock.expect_get_tx_field()
536 .with(eq(Condition), always(), eq(CONDITION_BLOB_SIZE))
537 .times(1)
538 .returning(|_, _, _| FIELD_NOT_FOUND);
539 // get_fulfillment
540 mock.expect_get_tx_field()
541 .with(eq(Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
542 .times(1)
543 .returning(|_, _, _| FIELD_NOT_FOUND);
544
545 let _guard = setup_mock(mock);
546
547 let escrow = EscrowFinish;
548
549 // Optional fields return Err on FIELD_NOT_FOUND (not None)
550 let condition_result = escrow.get_condition();
551 assert!(condition_result.is_err());
552 assert_eq!(condition_result.err().unwrap().code(), FIELD_NOT_FOUND);
553
554 let fulfillment_result = escrow.get_fulfillment();
555 assert!(fulfillment_result.is_err());
556 assert_eq!(fulfillment_result.err().unwrap().code(), FIELD_NOT_FOUND);
557 }
558
559 #[test]
560 fn test_optional_fields_return_error_on_invalid_field() {
561 let mut mock = MockHostBindings::new();
562
563 // get_condition
564 mock.expect_get_tx_field()
565 .with(eq(sfield::Condition), always(), eq(CONDITION_BLOB_SIZE))
566 .times(1)
567 .returning(|_, _, _| INVALID_FIELD);
568 // get_fulfillment
569 mock.expect_get_tx_field()
570 .with(eq(sfield::Fulfillment), always(), eq(FULFILLMENT_BLOB_SIZE))
571 .times(1)
572 .returning(|_, _, _| INVALID_FIELD);
573
574 let _guard = setup_mock(mock);
575
576 let escrow = EscrowFinish;
577
578 // Optional fields should also return Err on INVALID_FIELD
579 let condition_result = escrow.get_condition();
580 assert!(condition_result.is_err());
581 assert_eq!(condition_result.err().unwrap().code(), INVALID_FIELD);
582
583 let fulfillment_result = escrow.get_fulfillment();
584 assert!(fulfillment_result.is_err());
585 assert_eq!(fulfillment_result.err().unwrap().code(), INVALID_FIELD);
586 }
587 }
588
589 mod mandatory_fields {
590 use crate::core::current_tx::escrow_finish::EscrowFinish;
591 use crate::core::current_tx::traits::EscrowFinishFields;
592 use crate::core::current_tx::traits::tests::expect_tx_field;
593 use crate::core::types::account_id::ACCOUNT_ID_SIZE;
594 use crate::host::error_codes::{FIELD_NOT_FOUND, INTERNAL_ERROR, INVALID_FIELD};
595 use crate::host::host_bindings_trait::MockHostBindings;
596 use crate::host::setup_mock;
597 use crate::sfield;
598 use mockall::predicate::{always, eq};
599
600 #[test]
601 fn test_mandatory_fields_return_ok() {
602 let mut mock = MockHostBindings::new();
603
604 // get_owner
605 expect_tx_field(&mut mock, sfield::Owner, ACCOUNT_ID_SIZE, 1);
606 // get_offer_sequence
607 expect_tx_field(&mut mock, sfield::OfferSequence, 4, 1);
608
609 let _guard = setup_mock(mock);
610
611 let escrow = EscrowFinish;
612
613 // All mandatory fields should return Ok
614 assert!(escrow.get_owner().is_ok());
615 assert!(escrow.get_offer_sequence().is_ok());
616 }
617
618 #[test]
619 fn test_mandatory_fields_return_error_when_zero_length() {
620 let mut mock = MockHostBindings::new();
621
622 // get_owner - returns 0 (zero length)
623 mock.expect_get_tx_field()
624 .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
625 .times(1)
626 .returning(|_, _, _| 0);
627 // get_offer_sequence - returns 0 (zero length)
628 mock.expect_get_tx_field()
629 .with(eq(sfield::OfferSequence), always(), eq(4))
630 .times(1)
631 .returning(|_, _, _| 0);
632
633 let _guard = setup_mock(mock);
634
635 let escrow = EscrowFinish;
636
637 // Mandatory fields should return Err when zero length (INTERNAL_ERROR due to byte mismatch)
638 let owner_result = escrow.get_owner();
639 assert!(owner_result.is_err());
640
641 let offer_seq_result = escrow.get_offer_sequence();
642 assert!(offer_seq_result.is_err());
643 }
644
645 #[test]
646 fn test_mandatory_fields_return_error_on_field_not_found() {
647 let mut mock = MockHostBindings::new();
648
649 // get_owner
650 mock.expect_get_tx_field()
651 .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
652 .times(1)
653 .returning(|_, _, _| FIELD_NOT_FOUND);
654 // get_offer_sequence
655 mock.expect_get_tx_field()
656 .with(eq(sfield::OfferSequence), always(), eq(4))
657 .times(1)
658 .returning(|_, _, _| FIELD_NOT_FOUND);
659
660 let _guard = setup_mock(mock);
661
662 let escrow = EscrowFinish;
663
664 // All mandatory fields should return Err on FIELD_NOT_FOUND
665 let owner_result = escrow.get_owner();
666 assert!(owner_result.is_err());
667 assert_eq!(owner_result.err().unwrap().code(), FIELD_NOT_FOUND);
668
669 let offer_seq_result = escrow.get_offer_sequence();
670 assert!(offer_seq_result.is_err());
671 assert_eq!(offer_seq_result.err().unwrap().code(), FIELD_NOT_FOUND);
672 }
673
674 #[test]
675 fn test_mandatory_fields_return_error_on_internal_error() {
676 let mut mock = MockHostBindings::new();
677
678 // get_owner
679 mock.expect_get_tx_field()
680 .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
681 .times(1)
682 .returning(|_, _, _| INTERNAL_ERROR);
683 // get_offer_sequence
684 mock.expect_get_tx_field()
685 .with(eq(sfield::OfferSequence), always(), eq(4))
686 .times(1)
687 .returning(|_, _, _| INTERNAL_ERROR);
688
689 let _guard = setup_mock(mock);
690
691 let escrow = EscrowFinish;
692
693 // All mandatory fields should return Err on INTERNAL_ERROR
694 let owner_result = escrow.get_owner();
695 assert!(owner_result.is_err());
696 assert_eq!(owner_result.err().unwrap().code(), INTERNAL_ERROR);
697
698 let offer_seq_result = escrow.get_offer_sequence();
699 assert!(offer_seq_result.is_err());
700 assert_eq!(offer_seq_result.err().unwrap().code(), INTERNAL_ERROR);
701 }
702
703 #[test]
704 fn test_mandatory_fields_return_error_on_invalid_field() {
705 let mut mock = MockHostBindings::new();
706
707 // get_owner
708 mock.expect_get_tx_field()
709 .with(eq(sfield::Owner), always(), eq(ACCOUNT_ID_SIZE))
710 .times(1)
711 .returning(|_, _, _| INVALID_FIELD);
712 // get_offer_sequence
713 mock.expect_get_tx_field()
714 .with(eq(sfield::OfferSequence), always(), eq(4))
715 .times(1)
716 .returning(|_, _, _| INVALID_FIELD);
717
718 let _guard = setup_mock(mock);
719
720 let escrow = EscrowFinish;
721
722 // All mandatory fields should return Err on INVALID_FIELD
723 let owner_result = escrow.get_owner();
724 assert!(owner_result.is_err());
725 assert_eq!(owner_result.err().unwrap().code(), INVALID_FIELD);
726
727 let offer_seq_result = escrow.get_offer_sequence();
728 assert!(offer_seq_result.is_err());
729 assert_eq!(offer_seq_result.err().unwrap().code(), INVALID_FIELD);
730 }
731 }
732 }
733
734 mod transaction_common_fields {
735
736 mod optional_fields {
737 use crate::core::current_tx::escrow_finish::EscrowFinish;
738 use crate::core::current_tx::traits::TransactionCommonFields;
739 use crate::core::current_tx::traits::tests::expect_tx_field;
740 use crate::core::types::uint::HASH256_SIZE;
741 use crate::host::error_codes::{FIELD_NOT_FOUND, INTERNAL_ERROR, INVALID_FIELD};
742 use crate::host::host_bindings_trait::MockHostBindings;
743 use crate::host::setup_mock;
744 use crate::sfield;
745 use mockall::predicate::{always, eq};
746
747 #[test]
748 fn test_optional_fields_return_some() {
749 let mut mock = MockHostBindings::new();
750
751 // get_account_txn_id
752 expect_tx_field(&mut mock, sfield::AccountTxnID, HASH256_SIZE, 1);
753 // get_flags
754 expect_tx_field(&mut mock, sfield::Flags, 4, 1);
755 // get_last_ledger_sequence
756 expect_tx_field(&mut mock, sfield::LastLedgerSequence, 4, 1);
757 // get_network_id
758 expect_tx_field(&mut mock, sfield::NetworkID, 4, 1);
759 // get_source_tag
760 expect_tx_field(&mut mock, sfield::SourceTag, 4, 1);
761 // get_ticket_sequence
762 expect_tx_field(&mut mock, sfield::TicketSequence, 4, 1);
763
764 let _guard = setup_mock(mock);
765
766 let tx = EscrowFinish;
767
768 // All optional fields should return Ok(Some(...))
769 assert!(tx.get_account_txn_id().unwrap().is_some());
770 assert!(tx.get_flags().unwrap().is_some());
771 assert!(tx.get_last_ledger_sequence().unwrap().is_some());
772 assert!(tx.get_network_id().unwrap().is_some());
773 assert!(tx.get_source_tag().unwrap().is_some());
774 assert!(tx.get_ticket_sequence().unwrap().is_some());
775 }
776
777 #[test]
778 fn test_optional_fields_return_none_when_field_not_found() {
779 let mut mock = MockHostBindings::new();
780
781 // get_account_txn_id
782 mock.expect_get_tx_field()
783 .with(eq(sfield::AccountTxnID), always(), eq(HASH256_SIZE))
784 .times(1)
785 .returning(|_, _, _| FIELD_NOT_FOUND);
786 // get_flags
787 mock.expect_get_tx_field()
788 .with(eq(sfield::Flags), always(), eq(4))
789 .times(1)
790 .returning(|_, _, _| FIELD_NOT_FOUND);
791 // get_last_ledger_sequence
792 mock.expect_get_tx_field()
793 .with(eq(sfield::LastLedgerSequence), always(), eq(4))
794 .times(1)
795 .returning(|_, _, _| FIELD_NOT_FOUND);
796 // get_network_id
797 mock.expect_get_tx_field()
798 .with(eq(sfield::NetworkID), always(), eq(4))
799 .times(1)
800 .returning(|_, _, _| FIELD_NOT_FOUND);
801 // get_source_tag
802 mock.expect_get_tx_field()
803 .with(eq(sfield::SourceTag), always(), eq(4))
804 .times(1)
805 .returning(|_, _, _| FIELD_NOT_FOUND);
806 // get_ticket_sequence
807 mock.expect_get_tx_field()
808 .with(eq(sfield::TicketSequence), always(), eq(4))
809 .times(1)
810 .returning(|_, _, _| FIELD_NOT_FOUND);
811
812 let _guard = setup_mock(mock);
813
814 let tx = EscrowFinish;
815
816 // Fixed-size optional fields should return Ok(None) when FIELD_NOT_FOUND
817 assert!(tx.get_account_txn_id().unwrap().is_none());
818 assert!(tx.get_flags().unwrap().is_none());
819 assert!(tx.get_last_ledger_sequence().unwrap().is_none());
820 assert!(tx.get_network_id().unwrap().is_none());
821 assert!(tx.get_source_tag().unwrap().is_none());
822 assert!(tx.get_ticket_sequence().unwrap().is_none());
823 }
824
825 #[test]
826 fn test_optional_fields_return_none_when_zero_length() {
827 let mut mock = MockHostBindings::new();
828
829 // get_account_txn_id - returns 0 (zero length)
830 mock.expect_get_tx_field()
831 .with(eq(sfield::AccountTxnID), always(), eq(HASH256_SIZE))
832 .times(1)
833 .returning(|_, _, _| 0);
834 // get_flags - returns 0 (zero length)
835 mock.expect_get_tx_field()
836 .with(eq(sfield::Flags), always(), eq(4))
837 .times(1)
838 .returning(|_, _, _| 0);
839 // get_last_ledger_sequence - returns 0 (zero length)
840 mock.expect_get_tx_field()
841 .with(eq(sfield::LastLedgerSequence), always(), eq(4))
842 .times(1)
843 .returning(|_, _, _| 0);
844 // get_network_id - returns 0 (zero length)
845 mock.expect_get_tx_field()
846 .with(eq(sfield::NetworkID), always(), eq(4))
847 .times(1)
848 .returning(|_, _, _| 0);
849 // get_source_tag - returns 0 (zero length)
850 mock.expect_get_tx_field()
851 .with(eq(sfield::SourceTag), always(), eq(4))
852 .times(1)
853 .returning(|_, _, _| 0);
854 // get_ticket_sequence - returns 0 (zero length)
855 mock.expect_get_tx_field()
856 .with(eq(sfield::TicketSequence), always(), eq(4))
857 .times(1)
858 .returning(|_, _, _| 0);
859
860 // Mock trace_num calls (2 calls per field for byte mismatch: expected + actual)
861 mock.expect_trace_num()
862 .with(always(), always(), always())
863 .returning(|_, _, _| 0)
864 .times(12); // 6 fields * 2 calls each
865
866 let _guard = setup_mock(mock);
867
868 let tx = EscrowFinish;
869
870 // Fixed-size optional fields should return Err when zero length (byte mismatch)
871 assert!(tx.get_account_txn_id().is_err());
872 assert!(tx.get_flags().is_err());
873 assert!(tx.get_last_ledger_sequence().is_err());
874 assert!(tx.get_network_id().is_err());
875 assert!(tx.get_source_tag().is_err());
876 assert!(tx.get_ticket_sequence().is_err());
877 }
878
879 #[test]
880 fn test_optional_fields_return_error_on_internal_error() {
881 let mut mock = MockHostBindings::new();
882
883 // get_account_txn_id
884 mock.expect_get_tx_field()
885 .with(eq(sfield::AccountTxnID), always(), eq(HASH256_SIZE))
886 .times(1)
887 .returning(|_, _, _| INTERNAL_ERROR);
888 // get_flags
889 mock.expect_get_tx_field()
890 .with(eq(sfield::Flags), always(), eq(4))
891 .times(1)
892 .returning(|_, _, _| INTERNAL_ERROR);
893 // get_last_ledger_sequence
894 mock.expect_get_tx_field()
895 .with(eq(sfield::LastLedgerSequence), always(), eq(4))
896 .times(1)
897 .returning(|_, _, _| INTERNAL_ERROR);
898 // get_network_id
899 mock.expect_get_tx_field()
900 .with(eq(sfield::NetworkID), always(), eq(4))
901 .times(1)
902 .returning(|_, _, _| INTERNAL_ERROR);
903 // get_source_tag
904 mock.expect_get_tx_field()
905 .with(eq(sfield::SourceTag), always(), eq(4))
906 .times(1)
907 .returning(|_, _, _| INTERNAL_ERROR);
908 // get_ticket_sequence
909 mock.expect_get_tx_field()
910 .with(eq(sfield::TicketSequence), always(), eq(4))
911 .times(1)
912 .returning(|_, _, _| INTERNAL_ERROR);
913
914 // Mock trace_num calls (1 call per field for error codes)
915 mock.expect_trace_num()
916 .with(always(), always(), always())
917 .returning(|_, _, _| 0)
918 .times(6); // 6 fields * 1 call each
919
920 let _guard = setup_mock(mock);
921
922 let tx = EscrowFinish;
923
924 // Optional fields should return Err on INTERNAL_ERROR
925 let account_txn_id_result = tx.get_account_txn_id();
926 assert!(account_txn_id_result.is_err());
927 assert_eq!(account_txn_id_result.err().unwrap().code(), INTERNAL_ERROR);
928
929 let flags_result = tx.get_flags();
930 assert!(flags_result.is_err());
931 assert_eq!(flags_result.err().unwrap().code(), INTERNAL_ERROR);
932
933 let last_ledger_seq_result = tx.get_last_ledger_sequence();
934 assert!(last_ledger_seq_result.is_err());
935 assert_eq!(last_ledger_seq_result.err().unwrap().code(), INTERNAL_ERROR);
936
937 let network_id_result = tx.get_network_id();
938 assert!(network_id_result.is_err());
939 assert_eq!(network_id_result.err().unwrap().code(), INTERNAL_ERROR);
940
941 let source_tag_result = tx.get_source_tag();
942 assert!(source_tag_result.is_err());
943 assert_eq!(source_tag_result.err().unwrap().code(), INTERNAL_ERROR);
944
945 let ticket_seq_result = tx.get_ticket_sequence();
946 assert!(ticket_seq_result.is_err());
947 assert_eq!(ticket_seq_result.err().unwrap().code(), INTERNAL_ERROR);
948 }
949
950 #[test]
951 fn test_optional_fields_return_error_on_invalid_field() {
952 let mut mock = MockHostBindings::new();
953
954 // get_account_txn_id
955 mock.expect_get_tx_field()
956 .with(eq(sfield::AccountTxnID), always(), eq(HASH256_SIZE))
957 .times(1)
958 .returning(|_, _, _| INVALID_FIELD);
959 // get_flags
960 mock.expect_get_tx_field()
961 .with(eq(sfield::Flags), always(), eq(4))
962 .times(1)
963 .returning(|_, _, _| INVALID_FIELD);
964 // get_last_ledger_sequence
965 mock.expect_get_tx_field()
966 .with(eq(sfield::LastLedgerSequence), always(), eq(4))
967 .times(1)
968 .returning(|_, _, _| INVALID_FIELD);
969 // get_network_id
970 mock.expect_get_tx_field()
971 .with(eq(sfield::NetworkID), always(), eq(4))
972 .times(1)
973 .returning(|_, _, _| INVALID_FIELD);
974 // get_source_tag
975 mock.expect_get_tx_field()
976 .with(eq(sfield::SourceTag), always(), eq(4))
977 .times(1)
978 .returning(|_, _, _| INVALID_FIELD);
979 // get_ticket_sequence
980 mock.expect_get_tx_field()
981 .with(eq(sfield::TicketSequence), always(), eq(4))
982 .times(1)
983 .returning(|_, _, _| INVALID_FIELD);
984
985 // Mock trace_num calls (1 call per field for error codes)
986 mock.expect_trace_num()
987 .with(always(), always(), always())
988 .returning(|_, _, _| 0)
989 .times(6); // 6 fields * 1 call each
990
991 let _guard = setup_mock(mock);
992
993 let tx = EscrowFinish;
994
995 // Optional fields should return Err on INVALID_FIELD
996 let account_txn_id_result = tx.get_account_txn_id();
997 assert!(account_txn_id_result.is_err());
998 assert_eq!(account_txn_id_result.err().unwrap().code(), INVALID_FIELD);
999
1000 let flags_result = tx.get_flags();
1001 assert!(flags_result.is_err());
1002 assert_eq!(flags_result.err().unwrap().code(), INVALID_FIELD);
1003
1004 let last_ledger_seq_result = tx.get_last_ledger_sequence();
1005 assert!(last_ledger_seq_result.is_err());
1006 assert_eq!(last_ledger_seq_result.err().unwrap().code(), INVALID_FIELD);
1007
1008 let network_id_result = tx.get_network_id();
1009 assert!(network_id_result.is_err());
1010 assert_eq!(network_id_result.err().unwrap().code(), INVALID_FIELD);
1011
1012 let source_tag_result = tx.get_source_tag();
1013 assert!(source_tag_result.is_err());
1014 assert_eq!(source_tag_result.err().unwrap().code(), INVALID_FIELD);
1015
1016 let ticket_seq_result = tx.get_ticket_sequence();
1017 assert!(ticket_seq_result.is_err());
1018 assert_eq!(ticket_seq_result.err().unwrap().code(), INVALID_FIELD);
1019 }
1020 }
1021
1022 mod required_fields {
1023 use crate::core::current_tx::escrow_finish::EscrowFinish;
1024 use crate::core::current_tx::traits::TransactionCommonFields;
1025 use crate::core::current_tx::traits::tests::expect_tx_field;
1026 use crate::core::types::account_id::ACCOUNT_ID_SIZE;
1027 use crate::core::types::amount::AMOUNT_SIZE;
1028 use crate::core::types::blob::SIGNATURE_BLOB_SIZE;
1029 use crate::core::types::public_key::PUBLIC_KEY_BUFFER_SIZE;
1030 use crate::host::error_codes::{FIELD_NOT_FOUND, INTERNAL_ERROR, INVALID_FIELD};
1031 use crate::host::host_bindings_trait::MockHostBindings;
1032 use crate::host::setup_mock;
1033 use crate::sfield;
1034 use mockall::predicate::{always, eq};
1035
1036 #[test]
1037 fn test_mandatory_fields_return_ok() {
1038 let mut mock = MockHostBindings::new();
1039
1040 // get_account
1041 expect_tx_field(&mut mock, sfield::Account, ACCOUNT_ID_SIZE, 1);
1042 // get_transaction_type
1043 expect_tx_field(&mut mock, sfield::TransactionType, 2, 1);
1044 // get_computation_allowance
1045 expect_tx_field(&mut mock, sfield::ComputationAllowance, 4, 1);
1046 // get_fee
1047 expect_tx_field(&mut mock, sfield::Fee, AMOUNT_SIZE, 1);
1048 // get_sequence
1049 expect_tx_field(&mut mock, sfield::Sequence, 4, 1);
1050 // get_signing_pub_key
1051 expect_tx_field(&mut mock, sfield::SigningPubKey, PUBLIC_KEY_BUFFER_SIZE, 1);
1052 // get_txn_signature
1053 expect_tx_field(&mut mock, sfield::TxnSignature, SIGNATURE_BLOB_SIZE, 1);
1054
1055 let _guard = setup_mock(mock);
1056
1057 let tx = EscrowFinish;
1058
1059 // All mandatory fields should return Ok
1060 assert!(tx.get_account().is_ok());
1061 assert!(tx.get_transaction_type().is_ok());
1062 assert!(tx.get_computation_allowance().is_ok());
1063 assert!(tx.get_fee().is_ok());
1064 assert!(tx.get_sequence().is_ok());
1065 assert!(tx.get_signing_pub_key().is_ok());
1066 assert!(tx.get_txn_signature().is_ok());
1067 }
1068
1069 #[test]
1070 fn test_mandatory_fields_return_error_when_zero_length() {
1071 let mut mock = MockHostBindings::new();
1072
1073 // get_account - returns 0 (zero length)
1074 mock.expect_get_tx_field()
1075 .with(eq(sfield::Account), always(), eq(ACCOUNT_ID_SIZE))
1076 .times(1)
1077 .returning(|_, _, _| 0);
1078 // get_transaction_type - returns 0 (zero length)
1079 mock.expect_get_tx_field()
1080 .with(eq(sfield::TransactionType), always(), eq(2))
1081 .times(1)
1082 .returning(|_, _, _| 0);
1083 // get_computation_allowance - returns 0 (zero length)
1084 mock.expect_get_tx_field()
1085 .with(eq(sfield::ComputationAllowance), always(), eq(4))
1086 .times(1)
1087 .returning(|_, _, _| 0);
1088 // get_fee - returns 0 (zero length)
1089 mock.expect_get_tx_field()
1090 .with(eq(sfield::Fee), always(), eq(AMOUNT_SIZE))
1091 .times(1)
1092 .returning(|_, _, _| 0);
1093 // get_sequence - returns 0 (zero length)
1094 mock.expect_get_tx_field()
1095 .with(eq(sfield::Sequence), always(), eq(4))
1096 .times(1)
1097 .returning(|_, _, _| 0);
1098 // get_signing_pub_key - returns 0 (zero length)
1099 mock.expect_get_tx_field()
1100 .with(
1101 eq(sfield::SigningPubKey),
1102 always(),
1103 eq(PUBLIC_KEY_BUFFER_SIZE),
1104 )
1105 .times(1)
1106 .returning(|_, _, _| 0);
1107
1108 let _guard = setup_mock(mock);
1109
1110 let tx = EscrowFinish;
1111
1112 // Fixed-size mandatory fields should return Err when zero length (byte mismatch)
1113 let account_result = tx.get_account();
1114 assert!(account_result.is_err());
1115
1116 let tx_type_result = tx.get_transaction_type();
1117 assert!(tx_type_result.is_err());
1118
1119 let comp_allow_result = tx.get_computation_allowance();
1120 assert!(comp_allow_result.is_err());
1121
1122 // Variable-size field (Amount) returns Ok with zero length
1123 let fee_result = tx.get_fee();
1124 assert!(fee_result.is_ok());
1125
1126 let seq_result = tx.get_sequence();
1127 assert!(seq_result.is_err());
1128
1129 // SigningPubKey is special: zero length indicates multi-signature transaction
1130 // and should return Ok(None), not an error
1131 let signing_key_result = tx.get_signing_pub_key();
1132 assert!(signing_key_result.is_ok());
1133 assert!(signing_key_result.unwrap().is_none());
1134 }
1135
1136 #[test]
1137 fn test_mandatory_fields_return_error_on_field_not_found() {
1138 let mut mock = MockHostBindings::new();
1139
1140 // get_account
1141 mock.expect_get_tx_field()
1142 .with(eq(sfield::Account), always(), eq(ACCOUNT_ID_SIZE))
1143 .times(1)
1144 .returning(|_, _, _| FIELD_NOT_FOUND);
1145 // get_transaction_type
1146 mock.expect_get_tx_field()
1147 .with(eq(sfield::TransactionType), always(), eq(2))
1148 .times(1)
1149 .returning(|_, _, _| FIELD_NOT_FOUND);
1150 // get_computation_allowance
1151 mock.expect_get_tx_field()
1152 .with(eq(sfield::ComputationAllowance), always(), eq(4))
1153 .times(1)
1154 .returning(|_, _, _| FIELD_NOT_FOUND);
1155 // get_fee
1156 mock.expect_get_tx_field()
1157 .with(eq(sfield::Fee), always(), eq(AMOUNT_SIZE))
1158 .times(1)
1159 .returning(|_, _, _| FIELD_NOT_FOUND);
1160 // get_sequence
1161 mock.expect_get_tx_field()
1162 .with(eq(sfield::Sequence), always(), eq(4))
1163 .times(1)
1164 .returning(|_, _, _| FIELD_NOT_FOUND);
1165 // get_signing_pub_key
1166 mock.expect_get_tx_field()
1167 .with(
1168 eq(sfield::SigningPubKey),
1169 always(),
1170 eq(PUBLIC_KEY_BUFFER_SIZE),
1171 )
1172 .times(1)
1173 .returning(|_, _, _| FIELD_NOT_FOUND);
1174
1175 let _guard = setup_mock(mock);
1176
1177 let tx = EscrowFinish;
1178
1179 // All mandatory fields should return Err on FIELD_NOT_FOUND
1180 let account_result = tx.get_account();
1181 assert!(account_result.is_err());
1182 assert_eq!(account_result.err().unwrap().code(), FIELD_NOT_FOUND);
1183
1184 let tx_type_result = tx.get_transaction_type();
1185 assert!(tx_type_result.is_err());
1186 assert_eq!(tx_type_result.err().unwrap().code(), FIELD_NOT_FOUND);
1187
1188 let comp_allow_result = tx.get_computation_allowance();
1189 assert!(comp_allow_result.is_err());
1190 assert_eq!(comp_allow_result.err().unwrap().code(), FIELD_NOT_FOUND);
1191
1192 let fee_result = tx.get_fee();
1193 assert!(fee_result.is_err());
1194 assert_eq!(fee_result.err().unwrap().code(), FIELD_NOT_FOUND);
1195
1196 let seq_result = tx.get_sequence();
1197 assert!(seq_result.is_err());
1198 assert_eq!(seq_result.err().unwrap().code(), FIELD_NOT_FOUND);
1199
1200 let signing_key_result = tx.get_signing_pub_key();
1201 assert!(signing_key_result.is_err());
1202 assert_eq!(signing_key_result.err().unwrap().code(), FIELD_NOT_FOUND);
1203 }
1204
1205 #[test]
1206 fn test_mandatory_fields_return_error_on_internal_error() {
1207 let mut mock = MockHostBindings::new();
1208
1209 // get_account
1210 mock.expect_get_tx_field()
1211 .with(eq(sfield::Account), always(), eq(ACCOUNT_ID_SIZE))
1212 .times(1)
1213 .returning(|_, _, _| INTERNAL_ERROR);
1214 // get_transaction_type
1215 mock.expect_get_tx_field()
1216 .with(eq(sfield::TransactionType), always(), eq(2))
1217 .times(1)
1218 .returning(|_, _, _| INTERNAL_ERROR);
1219 // get_computation_allowance
1220 mock.expect_get_tx_field()
1221 .with(eq(sfield::ComputationAllowance), always(), eq(4))
1222 .times(1)
1223 .returning(|_, _, _| INTERNAL_ERROR);
1224 // get_fee
1225 mock.expect_get_tx_field()
1226 .with(eq(sfield::Fee), always(), eq(AMOUNT_SIZE))
1227 .times(1)
1228 .returning(|_, _, _| INTERNAL_ERROR);
1229 // get_sequence
1230 mock.expect_get_tx_field()
1231 .with(eq(sfield::Sequence), always(), eq(4))
1232 .times(1)
1233 .returning(|_, _, _| INTERNAL_ERROR);
1234 // get_signing_pub_key
1235 mock.expect_get_tx_field()
1236 .with(
1237 eq(sfield::SigningPubKey),
1238 always(),
1239 eq(PUBLIC_KEY_BUFFER_SIZE),
1240 )
1241 .times(1)
1242 .returning(|_, _, _| INTERNAL_ERROR);
1243
1244 let _guard = setup_mock(mock);
1245
1246 let tx = EscrowFinish;
1247
1248 // All mandatory fields should return Err on INTERNAL_ERROR
1249 let account_result = tx.get_account();
1250 assert!(account_result.is_err());
1251 assert_eq!(account_result.err().unwrap().code(), INTERNAL_ERROR);
1252
1253 let tx_type_result = tx.get_transaction_type();
1254 assert!(tx_type_result.is_err());
1255 assert_eq!(tx_type_result.err().unwrap().code(), INTERNAL_ERROR);
1256
1257 let comp_allow_result = tx.get_computation_allowance();
1258 assert!(comp_allow_result.is_err());
1259 assert_eq!(comp_allow_result.err().unwrap().code(), INTERNAL_ERROR);
1260
1261 let fee_result = tx.get_fee();
1262 assert!(fee_result.is_err());
1263 assert_eq!(fee_result.err().unwrap().code(), INTERNAL_ERROR);
1264
1265 let seq_result = tx.get_sequence();
1266 assert!(seq_result.is_err());
1267 assert_eq!(seq_result.err().unwrap().code(), INTERNAL_ERROR);
1268
1269 let signing_key_result = tx.get_signing_pub_key();
1270 assert!(signing_key_result.is_err());
1271 assert_eq!(signing_key_result.err().unwrap().code(), INTERNAL_ERROR);
1272 }
1273
1274 #[test]
1275 fn test_mandatory_fields_return_error_on_invalid_field() {
1276 let mut mock = MockHostBindings::new();
1277
1278 // get_account
1279 mock.expect_get_tx_field()
1280 .with(eq(sfield::Account), always(), eq(ACCOUNT_ID_SIZE))
1281 .times(1)
1282 .returning(|_, _, _| INVALID_FIELD);
1283 // get_transaction_type
1284 mock.expect_get_tx_field()
1285 .with(eq(sfield::TransactionType), always(), eq(2))
1286 .times(1)
1287 .returning(|_, _, _| INVALID_FIELD);
1288 // get_computation_allowance
1289 mock.expect_get_tx_field()
1290 .with(eq(sfield::ComputationAllowance), always(), eq(4))
1291 .times(1)
1292 .returning(|_, _, _| INVALID_FIELD);
1293 // get_fee
1294 mock.expect_get_tx_field()
1295 .with(eq(sfield::Fee), always(), eq(AMOUNT_SIZE))
1296 .times(1)
1297 .returning(|_, _, _| INVALID_FIELD);
1298 // get_sequence
1299 mock.expect_get_tx_field()
1300 .with(eq(sfield::Sequence), always(), eq(4))
1301 .times(1)
1302 .returning(|_, _, _| INVALID_FIELD);
1303 // get_signing_pub_key
1304 mock.expect_get_tx_field()
1305 .with(
1306 eq(sfield::SigningPubKey),
1307 always(),
1308 eq(PUBLIC_KEY_BUFFER_SIZE),
1309 )
1310 .times(1)
1311 .returning(|_, _, _| INVALID_FIELD);
1312
1313 let _guard = setup_mock(mock);
1314
1315 let tx = EscrowFinish;
1316
1317 // All mandatory fields should return Err on INVALID_FIELD
1318 let account_result = tx.get_account();
1319 assert!(account_result.is_err());
1320 assert_eq!(account_result.err().unwrap().code(), INVALID_FIELD);
1321
1322 let tx_type_result = tx.get_transaction_type();
1323 assert!(tx_type_result.is_err());
1324 assert_eq!(tx_type_result.err().unwrap().code(), INVALID_FIELD);
1325
1326 let comp_allow_result = tx.get_computation_allowance();
1327 assert!(comp_allow_result.is_err());
1328 assert_eq!(comp_allow_result.err().unwrap().code(), INVALID_FIELD);
1329
1330 let fee_result = tx.get_fee();
1331 assert!(fee_result.is_err());
1332 assert_eq!(fee_result.err().unwrap().code(), INVALID_FIELD);
1333
1334 let seq_result = tx.get_sequence();
1335 assert!(seq_result.is_err());
1336 assert_eq!(seq_result.err().unwrap().code(), INVALID_FIELD);
1337
1338 let signing_key_result = tx.get_signing_pub_key();
1339 assert!(signing_key_result.is_err());
1340 assert_eq!(signing_key_result.err().unwrap().code(), INVALID_FIELD);
1341 }
1342 }
1343 }
1344}