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