txgate_chain/rlp.rs
1//! RLP decoding utilities for Ethereum transaction parsing.
2//!
3//! This module provides helper functions that wrap `alloy-rlp` to simplify
4//! Ethereum transaction decoding and improve error handling with `TxGate`'s
5//! error types.
6//!
7//! # Overview
8//!
9//! While `alloy-rlp` provides comprehensive RLP encoding/decoding, this module
10//! offers:
11//! - Unified error handling with [`ParseError`]
12//! - Transaction type detection helpers
13//! - Convenient wrapper functions for common decoding patterns
14//!
15//! # Ethereum Transaction Types
16//!
17//! Ethereum has multiple transaction types:
18//! - **Legacy (Type 0)**: Pre-EIP-2718, starts with RLP list prefix (0xc0-0xff)
19//! - **EIP-2930 (Type 1)**: Access list transactions, prefixed with `0x01`
20//! - **EIP-1559 (Type 2)**: Dynamic fee transactions, prefixed with `0x02`
21//! - **EIP-4844 (Type 3)**: Blob transactions, prefixed with `0x03`
22//!
23//! # Example
24//!
25//! ```
26//! use txgate_chain::rlp::{detect_tx_type, is_list, typed_tx_payload};
27//!
28//! // EIP-1559 transaction (type 2)
29//! let eip1559_tx = [0x02, 0xf8, 0x73]; // ... rest of transaction
30//! assert_eq!(detect_tx_type(&eip1559_tx), Some(2));
31//!
32//! // Legacy transaction (starts with RLP list prefix)
33//! let legacy_tx = [0xf8, 0x6c, 0x09]; // ... rest of transaction
34//! assert_eq!(detect_tx_type(&legacy_tx), None);
35//! assert!(is_list(&legacy_tx));
36//! ```
37
38use alloy_primitives::{Address, U256};
39use alloy_rlp::{Decodable, Header, PayloadView};
40use txgate_core::error::ParseError;
41
42/// Result type for RLP operations using [`ParseError`].
43pub type RlpResult<T> = Result<T, ParseError>;
44
45// ============================================================================
46// Transaction Type Detection
47// ============================================================================
48
49/// Detect the transaction type from the first byte.
50///
51/// Ethereum typed transactions (EIP-2718) are prefixed with a type byte:
52/// - `0x01` - EIP-2930 (Access List)
53/// - `0x02` - EIP-1559 (Dynamic Fee)
54/// - `0x03` - EIP-4844 (Blob Transaction)
55///
56/// Legacy transactions start with an RLP list marker (0xc0-0xff).
57///
58/// # Arguments
59///
60/// * `data` - Raw transaction bytes
61///
62/// # Returns
63///
64/// * `Some(type)` - For typed transactions (EIP-2718+)
65/// * `None` - For legacy transactions or empty input
66///
67/// # Example
68///
69/// ```
70/// use txgate_chain::rlp::detect_tx_type;
71///
72/// // EIP-1559 transaction
73/// let typed = [0x02, 0xf8, 0x73, 0x01];
74/// assert_eq!(detect_tx_type(&typed), Some(2));
75///
76/// // Legacy transaction (RLP list prefix)
77/// let legacy = [0xf8, 0x6c, 0x09];
78/// assert_eq!(detect_tx_type(&legacy), None);
79///
80/// // Empty data
81/// assert_eq!(detect_tx_type(&[]), None);
82/// ```
83#[must_use]
84pub fn detect_tx_type(data: &[u8]) -> Option<u8> {
85 data.first().and_then(|&b| {
86 if b >= 0xc0 {
87 // Legacy transaction (RLP list prefix 0xc0-0xff)
88 None
89 } else if b <= 0x03 {
90 // Typed transaction (0x00-0x03)
91 // Note: 0x00 is technically valid but rarely used
92 Some(b)
93 } else {
94 // Unknown prefix - could be invalid or future types
95 // Treat as None for safety, let the parser handle validation
96 None
97 }
98 })
99}
100
101/// Check if data starts with an RLP list prefix.
102///
103/// This is useful for detecting legacy Ethereum transactions,
104/// which are encoded as RLP lists without a type prefix.
105///
106/// # Arguments
107///
108/// * `data` - Raw data bytes
109///
110/// # Returns
111///
112/// * `true` if the first byte is in the range 0xc0-0xff (RLP list markers)
113/// * `false` otherwise
114///
115/// # Example
116///
117/// ```
118/// use txgate_chain::rlp::is_list;
119///
120/// // RLP list prefixes
121/// assert!(is_list(&[0xc0])); // Empty list
122/// assert!(is_list(&[0xc8, 0x01, 0x02])); // Short list
123/// assert!(is_list(&[0xf8, 0x6c])); // Long list
124///
125/// // Not lists
126/// assert!(!is_list(&[0x02])); // Type 2 tx prefix
127/// assert!(!is_list(&[0x80])); // Empty string
128/// assert!(!is_list(&[])); // Empty data
129/// ```
130#[must_use]
131pub fn is_list(data: &[u8]) -> bool {
132 data.first().is_some_and(|&b| b >= 0xc0)
133}
134
135/// Get the payload of a typed transaction (skip the type byte).
136///
137/// For typed transactions (EIP-2718+), the first byte is the type.
138/// This function returns the remaining bytes (the RLP-encoded transaction).
139///
140/// For legacy transactions (starting with 0xc0+), returns the data unchanged.
141///
142/// # Arguments
143///
144/// * `data` - Raw transaction bytes
145///
146/// # Returns
147///
148/// * `Ok(&[u8])` - The transaction payload (without type byte for typed txs)
149/// * `Err(ParseError)` - If data is empty
150///
151/// # Errors
152///
153/// Returns [`ParseError::MalformedTransaction`] if the input data is empty.
154///
155/// # Example
156///
157/// ```
158/// use txgate_chain::rlp::typed_tx_payload;
159///
160/// // EIP-1559 transaction
161/// let typed = [0x02, 0xf8, 0x73, 0x01];
162/// let payload = typed_tx_payload(&typed).unwrap();
163/// assert_eq!(payload, &[0xf8, 0x73, 0x01]);
164///
165/// // Legacy transaction (unchanged)
166/// let legacy = [0xf8, 0x6c, 0x09];
167/// let payload = typed_tx_payload(&legacy).unwrap();
168/// assert_eq!(payload, &[0xf8, 0x6c, 0x09]);
169/// ```
170pub fn typed_tx_payload(data: &[u8]) -> RlpResult<&[u8]> {
171 let first_byte = data
172 .first()
173 .ok_or_else(|| ParseError::MalformedTransaction {
174 context: "Empty transaction data".to_string(),
175 })?;
176
177 // For typed transactions (type byte 0x00-0x03), skip the first byte
178 if *first_byte <= 0x03 {
179 Ok(data.get(1..).unwrap_or_default())
180 } else {
181 // Legacy transaction or other - return as-is
182 Ok(data)
183 }
184}
185
186// ============================================================================
187// Decoding Helpers
188// ============================================================================
189
190/// Decode an RLP-encoded byte string.
191///
192/// This function decodes an RLP string (not a list) into raw bytes.
193/// Uses `Header::decode_bytes` from alloy-rlp internally.
194///
195/// # Arguments
196///
197/// * `data` - RLP-encoded string data
198///
199/// # Returns
200///
201/// * `Ok(Vec<u8>)` - The decoded bytes
202/// * `Err(ParseError)` - If decoding fails or data is a list
203///
204/// # Errors
205///
206/// Returns [`ParseError::InvalidRlp`] if:
207/// - The data is not valid RLP encoding
208/// - The data encodes a list instead of a string
209/// - The data is truncated
210///
211/// # Example
212///
213/// ```
214/// use txgate_chain::rlp::decode_bytes;
215///
216/// // Single byte (< 0x80) is itself
217/// let data = [0x42];
218/// assert_eq!(decode_bytes(&data).unwrap(), vec![0x42]);
219///
220/// // Empty string (0x80)
221/// let empty = [0x80];
222/// assert_eq!(decode_bytes(&empty).unwrap(), Vec::<u8>::new());
223///
224/// // Short string (0x80 + len, then bytes)
225/// let short = [0x83, 0x61, 0x62, 0x63]; // "abc"
226/// assert_eq!(decode_bytes(&short).unwrap(), vec![0x61, 0x62, 0x63]);
227/// ```
228pub fn decode_bytes(data: &[u8]) -> RlpResult<Vec<u8>> {
229 let mut buf = data;
230 // Use Header::decode_bytes with is_list=false to decode a string
231 let bytes = Header::decode_bytes(&mut buf, false).map_err(|e| ParseError::InvalidRlp {
232 context: format!("Failed to decode bytes: {e}"),
233 })?;
234 Ok(bytes.to_vec())
235}
236
237/// Decode an RLP-encoded list and return its items.
238///
239/// Each item in the returned vector is still RLP-encoded and can be
240/// decoded individually using the appropriate decoder.
241///
242/// # Arguments
243///
244/// * `data` - RLP-encoded list data
245///
246/// # Returns
247///
248/// * `Ok(Vec<&[u8]>)` - Vector of RLP-encoded items
249/// * `Err(ParseError)` - If decoding fails or data is not a list
250///
251/// # Errors
252///
253/// Returns [`ParseError::InvalidRlp`] if:
254/// - The data is not valid RLP encoding
255/// - The data encodes a string instead of a list
256/// - The data is truncated
257///
258/// # Example
259///
260/// ```
261/// use txgate_chain::rlp::decode_list;
262///
263/// // Empty list (0xc0)
264/// let empty_list = [0xc0];
265/// assert_eq!(decode_list(&empty_list).unwrap().len(), 0);
266///
267/// // List with two items: [1, 2]
268/// let list = [0xc2, 0x01, 0x02];
269/// let items = decode_list(&list).unwrap();
270/// assert_eq!(items.len(), 2);
271/// assert_eq!(items[0], &[0x01]);
272/// assert_eq!(items[1], &[0x02]);
273/// ```
274pub fn decode_list(data: &[u8]) -> RlpResult<Vec<&[u8]>> {
275 let mut buf = data;
276 let payload = Header::decode_raw(&mut buf).map_err(|e| ParseError::InvalidRlp {
277 context: format!("Failed to decode list: {e}"),
278 })?;
279
280 match payload {
281 PayloadView::List(items) => Ok(items),
282 PayloadView::String(_) => Err(ParseError::InvalidRlp {
283 context: "Expected list, found string".to_string(),
284 }),
285 }
286}
287
288/// Decode a U256 from RLP-encoded data.
289///
290/// # Arguments
291///
292/// * `data` - RLP-encoded U256
293///
294/// # Returns
295///
296/// * `Ok(U256)` - The decoded value
297/// * `Err(ParseError)` - If decoding fails
298///
299/// # Errors
300///
301/// Returns [`ParseError::InvalidRlp`] if the data is not valid RLP encoding
302/// or cannot be decoded as a U256.
303///
304/// # Example
305///
306/// ```
307/// use txgate_chain::rlp::decode_u256;
308/// use alloy_primitives::U256;
309///
310/// // Zero (0x80 = empty string = 0)
311/// let zero = [0x80];
312/// assert_eq!(decode_u256(&zero).unwrap(), U256::ZERO);
313///
314/// // Small value (0x42 = 66)
315/// let small = [0x42];
316/// assert_eq!(decode_u256(&small).unwrap(), U256::from(0x42u64));
317///
318/// // 256 (0x82, 0x01, 0x00)
319/// let medium = [0x82, 0x01, 0x00];
320/// assert_eq!(decode_u256(&medium).unwrap(), U256::from(256u64));
321/// ```
322pub fn decode_u256(data: &[u8]) -> RlpResult<U256> {
323 let mut buf = data;
324 U256::decode(&mut buf).map_err(|e| ParseError::InvalidRlp {
325 context: format!("Failed to decode U256: {e}"),
326 })
327}
328
329/// Decode an Ethereum address from RLP-encoded data.
330///
331/// Ethereum addresses are 20 bytes. The RLP encoding is:
332/// - `0x94` followed by 20 bytes (string of length 20)
333/// - Or empty string `0x80` for null/zero address (contract creation)
334///
335/// # Arguments
336///
337/// * `data` - RLP-encoded address
338///
339/// # Returns
340///
341/// * `Ok(Address)` - The decoded address
342/// * `Err(ParseError)` - If decoding fails or length is wrong
343///
344/// # Errors
345///
346/// Returns [`ParseError::InvalidRlp`] if:
347/// - The data is not valid RLP encoding
348/// - The decoded bytes are not exactly 20 bytes
349///
350/// # Example
351///
352/// ```
353/// use txgate_chain::rlp::decode_address;
354///
355/// // 20-byte address (0x94 = string of length 20)
356/// let addr_data = [
357/// 0x94, // prefix for 20-byte string
358/// 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
359/// 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
360/// ];
361/// let addr = decode_address(&addr_data).unwrap();
362/// assert_eq!(format!("{addr}"), "0x3535353535353535353535353535353535353535");
363/// ```
364pub fn decode_address(data: &[u8]) -> RlpResult<Address> {
365 let mut buf = data;
366 Address::decode(&mut buf).map_err(|e| ParseError::InvalidRlp {
367 context: format!("Failed to decode address: {e}"),
368 })
369}
370
371/// Decode an optional Ethereum address from RLP-encoded data.
372///
373/// This handles the case where the address field can be empty (contract creation).
374///
375/// # Arguments
376///
377/// * `data` - RLP-encoded address or empty string
378///
379/// # Returns
380///
381/// * `Ok(Some(Address))` - For non-empty addresses
382/// * `Ok(None)` - For empty string (contract creation)
383/// * `Err(ParseError)` - If decoding fails
384///
385/// # Errors
386///
387/// Returns [`ParseError::InvalidRlp`] if the non-empty data cannot be
388/// decoded as a valid 20-byte address.
389///
390/// # Example
391///
392/// ```
393/// use txgate_chain::rlp::decode_optional_address;
394///
395/// // Empty address (contract creation)
396/// let empty = [0x80];
397/// assert!(decode_optional_address(&empty).unwrap().is_none());
398///
399/// // Regular address
400/// let addr_data = [
401/// 0x94,
402/// 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
403/// 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
404/// ];
405/// assert!(decode_optional_address(&addr_data).unwrap().is_some());
406/// ```
407pub fn decode_optional_address(data: &[u8]) -> RlpResult<Option<Address>> {
408 // Check for empty string (0x80)
409 if data == [0x80] {
410 return Ok(None);
411 }
412
413 decode_address(data).map(Some)
414}
415
416/// Decode a u64 from RLP-encoded data.
417///
418/// # Arguments
419///
420/// * `data` - RLP-encoded u64
421///
422/// # Returns
423///
424/// * `Ok(u64)` - The decoded value
425/// * `Err(ParseError)` - If decoding fails or value overflows u64
426///
427/// # Errors
428///
429/// Returns [`ParseError::InvalidRlp`] if:
430/// - The data is not valid RLP encoding
431/// - The decoded value overflows u64
432///
433/// # Example
434///
435/// ```
436/// use txgate_chain::rlp::decode_u64;
437///
438/// // Zero
439/// let zero = [0x80];
440/// assert_eq!(decode_u64(&zero).unwrap(), 0);
441///
442/// // Small value
443/// let small = [0x09];
444/// assert_eq!(decode_u64(&small).unwrap(), 9);
445///
446/// // Larger value (21000 = 0x5208)
447/// let gas = [0x82, 0x52, 0x08];
448/// assert_eq!(decode_u64(&gas).unwrap(), 21000);
449/// ```
450pub fn decode_u64(data: &[u8]) -> RlpResult<u64> {
451 let mut buf = data;
452 u64::decode(&mut buf).map_err(|e| ParseError::InvalidRlp {
453 context: format!("Failed to decode u64: {e}"),
454 })
455}
456
457// ============================================================================
458// Tests
459// ============================================================================
460
461#[cfg(test)]
462mod tests {
463 #![allow(
464 clippy::expect_used,
465 clippy::unwrap_used,
466 clippy::panic,
467 clippy::indexing_slicing,
468 clippy::similar_names,
469 clippy::redundant_clone,
470 clippy::manual_string_new,
471 clippy::needless_raw_string_hashes,
472 clippy::needless_collect,
473 clippy::unreadable_literal
474 )]
475
476 use super::*;
477 use alloy_primitives::hex;
478 use alloy_rlp::Encodable;
479
480 // ------------------------------------------------------------------------
481 // Transaction Type Detection Tests
482 // ------------------------------------------------------------------------
483
484 #[test]
485 fn test_detect_tx_type_legacy() {
486 // RLP list prefixes (0xc0-0xff) indicate legacy transactions
487 assert_eq!(detect_tx_type(&[0xc0]), None);
488 assert_eq!(detect_tx_type(&[0xc8, 0x01, 0x02]), None);
489 assert_eq!(detect_tx_type(&[0xf8, 0x6c, 0x09]), None);
490 assert_eq!(detect_tx_type(&[0xff]), None);
491 }
492
493 #[test]
494 fn test_detect_tx_type_typed() {
495 // Type 0 (rarely used but valid)
496 assert_eq!(detect_tx_type(&[0x00, 0xf8, 0x73]), Some(0));
497
498 // Type 1 - EIP-2930
499 assert_eq!(detect_tx_type(&[0x01, 0xf8, 0x73]), Some(1));
500
501 // Type 2 - EIP-1559
502 assert_eq!(detect_tx_type(&[0x02, 0xf8, 0x73]), Some(2));
503
504 // Type 3 - EIP-4844
505 assert_eq!(detect_tx_type(&[0x03, 0xf8, 0x73]), Some(3));
506 }
507
508 #[test]
509 fn test_detect_tx_type_unknown() {
510 // Unknown type bytes (0x04-0xbf) - not currently valid tx types
511 assert_eq!(detect_tx_type(&[0x04]), None);
512 assert_eq!(detect_tx_type(&[0x80]), None);
513 assert_eq!(detect_tx_type(&[0xbf]), None);
514 }
515
516 #[test]
517 fn test_detect_tx_type_empty() {
518 assert_eq!(detect_tx_type(&[]), None);
519 }
520
521 // ------------------------------------------------------------------------
522 // is_list Tests
523 // ------------------------------------------------------------------------
524
525 #[test]
526 fn test_is_list() {
527 // RLP list prefixes
528 assert!(is_list(&[0xc0]));
529 assert!(is_list(&[0xc8, 0x01, 0x02]));
530 assert!(is_list(&[0xf7, 0x01]));
531 assert!(is_list(&[0xf8, 0x6c]));
532 assert!(is_list(&[0xff]));
533
534 // Not lists
535 assert!(!is_list(&[0x00]));
536 assert!(!is_list(&[0x01]));
537 assert!(!is_list(&[0x02]));
538 assert!(!is_list(&[0x80])); // Empty string
539 assert!(!is_list(&[0xbf])); // Max string prefix
540 assert!(!is_list(&[]));
541 }
542
543 // ------------------------------------------------------------------------
544 // typed_tx_payload Tests
545 // ------------------------------------------------------------------------
546
547 #[test]
548 fn test_typed_tx_payload_eip1559() {
549 let typed = [0x02, 0xf8, 0x73, 0x01, 0x02, 0x03];
550 let payload = typed_tx_payload(&typed).unwrap();
551 assert_eq!(payload, &[0xf8, 0x73, 0x01, 0x02, 0x03]);
552 }
553
554 #[test]
555 fn test_typed_tx_payload_legacy() {
556 let legacy = [0xf8, 0x6c, 0x09, 0x84];
557 let payload = typed_tx_payload(&legacy).unwrap();
558 assert_eq!(payload, &[0xf8, 0x6c, 0x09, 0x84]);
559 }
560
561 #[test]
562 fn test_typed_tx_payload_empty() {
563 let result = typed_tx_payload(&[]);
564 assert!(result.is_err());
565 assert!(matches!(
566 result,
567 Err(ParseError::MalformedTransaction { .. })
568 ));
569 }
570
571 #[test]
572 fn test_typed_tx_payload_type_0() {
573 // Type 0 is valid but rare
574 let typed = [0x00, 0xf8, 0x73];
575 let payload = typed_tx_payload(&typed).unwrap();
576 assert_eq!(payload, &[0xf8, 0x73]);
577 }
578
579 // ------------------------------------------------------------------------
580 // decode_bytes Tests
581 // ------------------------------------------------------------------------
582
583 #[test]
584 fn test_decode_bytes_single() {
585 // Single byte (value < 0x80)
586 assert_eq!(decode_bytes(&[0x42]).unwrap(), vec![0x42]);
587 assert_eq!(decode_bytes(&[0x00]).unwrap(), vec![0x00]);
588 assert_eq!(decode_bytes(&[0x7f]).unwrap(), vec![0x7f]);
589 }
590
591 #[test]
592 fn test_decode_bytes_empty() {
593 // Empty string is encoded as 0x80
594 assert_eq!(decode_bytes(&[0x80]).unwrap(), Vec::<u8>::new());
595 }
596
597 #[test]
598 fn test_decode_bytes_short_string() {
599 // Short string (1-55 bytes): 0x80 + len, then bytes
600 let encoded = [0x83, 0x61, 0x62, 0x63]; // "abc"
601 assert_eq!(decode_bytes(&encoded).unwrap(), vec![0x61, 0x62, 0x63]);
602 }
603
604 #[test]
605 fn test_decode_bytes_invalid() {
606 // Invalid RLP (truncated)
607 let result = decode_bytes(&[0x83, 0x61, 0x62]); // Claims 3 bytes but only has 2
608 assert!(result.is_err());
609 }
610
611 // ------------------------------------------------------------------------
612 // decode_list Tests
613 // ------------------------------------------------------------------------
614
615 #[test]
616 fn test_decode_list_empty() {
617 // Empty list is encoded as 0xc0
618 let items = decode_list(&[0xc0]).unwrap();
619 assert!(items.is_empty());
620 }
621
622 #[test]
623 fn test_decode_list_single_item() {
624 // List with single item [1]
625 let items = decode_list(&[0xc1, 0x01]).unwrap();
626 assert_eq!(items.len(), 1);
627 assert_eq!(items[0], &[0x01]);
628 }
629
630 #[test]
631 fn test_decode_list_multiple_items() {
632 // List [1, 2, 3]
633 let items = decode_list(&[0xc3, 0x01, 0x02, 0x03]).unwrap();
634 assert_eq!(items.len(), 3);
635 assert_eq!(items[0], &[0x01]);
636 assert_eq!(items[1], &[0x02]);
637 assert_eq!(items[2], &[0x03]);
638 }
639
640 #[test]
641 fn test_decode_list_not_a_list() {
642 // String instead of list
643 let result = decode_list(&[0x83, 0x61, 0x62, 0x63]);
644 assert!(result.is_err());
645 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
646 }
647
648 // ------------------------------------------------------------------------
649 // decode_u256 Tests
650 // ------------------------------------------------------------------------
651
652 #[test]
653 fn test_decode_u256_zero() {
654 assert_eq!(decode_u256(&[0x80]).unwrap(), U256::ZERO);
655 }
656
657 #[test]
658 fn test_decode_u256_small() {
659 assert_eq!(decode_u256(&[0x01]).unwrap(), U256::from(1u64));
660 assert_eq!(decode_u256(&[0x7f]).unwrap(), U256::from(127u64));
661 }
662
663 #[test]
664 fn test_decode_u256_medium() {
665 // 256 = 0x0100
666 assert_eq!(
667 decode_u256(&[0x82, 0x01, 0x00]).unwrap(),
668 U256::from(256u64)
669 );
670 }
671
672 #[test]
673 fn test_decode_u256_1eth() {
674 // 1 ETH = 10^18 = 0x0de0b6b3a7640000
675 let encoded = [0x88, 0x0d, 0xe0, 0xb6, 0xb3, 0xa7, 0x64, 0x00, 0x00];
676 let expected = U256::from(1_000_000_000_000_000_000u64);
677 assert_eq!(decode_u256(&encoded).unwrap(), expected);
678 }
679
680 // ------------------------------------------------------------------------
681 // decode_address Tests
682 // ------------------------------------------------------------------------
683
684 #[test]
685 fn test_decode_address() {
686 let addr_bytes = [
687 0x94, // 20-byte string prefix
688 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
689 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
690 ];
691
692 let addr = decode_address(&addr_bytes).unwrap();
693 assert_eq!(
694 format!("{addr}"),
695 "0x3535353535353535353535353535353535353535"
696 );
697 }
698
699 #[test]
700 fn test_decode_address_invalid_length() {
701 // 19 bytes instead of 20
702 let short = [
703 0x93, // 19-byte string prefix
704 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
705 0x35, 0x35, 0x35, 0x35, 0x35,
706 ];
707
708 let result = decode_address(&short);
709 assert!(result.is_err());
710 }
711
712 // ------------------------------------------------------------------------
713 // decode_optional_address Tests
714 // ------------------------------------------------------------------------
715
716 #[test]
717 fn test_decode_optional_address_empty() {
718 // Empty string = contract creation
719 assert!(decode_optional_address(&[0x80]).unwrap().is_none());
720 }
721
722 #[test]
723 fn test_decode_optional_address_present() {
724 let addr_bytes = [
725 0x94, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
726 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35,
727 ];
728
729 let addr = decode_optional_address(&addr_bytes).unwrap();
730 assert!(addr.is_some());
731 }
732
733 // ------------------------------------------------------------------------
734 // decode_u64 Tests
735 // ------------------------------------------------------------------------
736
737 #[test]
738 fn test_decode_u64_zero() {
739 assert_eq!(decode_u64(&[0x80]).unwrap(), 0);
740 }
741
742 #[test]
743 fn test_decode_u64_small() {
744 assert_eq!(decode_u64(&[0x09]).unwrap(), 9);
745 assert_eq!(decode_u64(&[0x7f]).unwrap(), 127);
746 }
747
748 #[test]
749 fn test_decode_u64_gas_limit() {
750 // 21000 = 0x5208
751 assert_eq!(decode_u64(&[0x82, 0x52, 0x08]).unwrap(), 21000);
752 }
753
754 #[test]
755 fn test_decode_u64_gas_price() {
756 // 20 gwei = 20_000_000_000 = 0x04a817c800
757 assert_eq!(
758 decode_u64(&[0x85, 0x04, 0xa8, 0x17, 0xc8, 0x00]).unwrap(),
759 20_000_000_000
760 );
761 }
762
763 // ------------------------------------------------------------------------
764 // Integration Tests with Real Transaction Data
765 // ------------------------------------------------------------------------
766
767 #[test]
768 fn test_legacy_transaction_detection() {
769 // Legacy transaction from fixture (starts with 0xf8 = long list)
770 let raw = hex::decode(
771 "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"
772 ).unwrap();
773
774 assert_eq!(detect_tx_type(&raw), None);
775 assert!(is_list(&raw));
776
777 // Should be able to decode as a list
778 let items = decode_list(&raw).unwrap();
779 assert_eq!(items.len(), 9); // nonce, gasPrice, gasLimit, to, value, data, v, r, s
780 }
781
782 #[test]
783 fn test_eip1559_transaction_detection() {
784 // EIP-1559 transaction (type 2) - manually constructed valid structure
785 // Format: 0x02 || RLP([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, v, r, s])
786 let raw = hex::decode(
787 "02f8730101847735940084773594008252089495ad61b0a150d79219dcf64e1e6cc01f0b64c4ce880de0b6b3a764000080c080a0e9d9f35c8b4a8e4da5fb0f6dd3cb0e49da8d4c0b7b0e0c2c2d8c8a1e3f4a5b6c7a07f8e9d0c1b2a394857660544e3d2c1b0a99887766554433221100112233445566"
788 ).unwrap();
789
790 assert_eq!(detect_tx_type(&raw), Some(2));
791 assert!(!is_list(&raw));
792
793 // Get payload without type byte
794 let payload = typed_tx_payload(&raw).unwrap();
795 assert!(is_list(payload));
796 }
797
798 // ------------------------------------------------------------------------
799 // Roundtrip Tests
800 // ------------------------------------------------------------------------
801
802 #[test]
803 fn test_u256_encode_decode_roundtrip() {
804 let values = [
805 U256::ZERO,
806 U256::from(1u64),
807 U256::from(127u64),
808 U256::from(128u64),
809 U256::from(256u64),
810 U256::from(1_000_000_000_000_000_000u64), // 1 ETH
811 U256::MAX,
812 ];
813
814 for value in values {
815 let mut encoded = Vec::new();
816 value.encode(&mut encoded);
817 let decoded = decode_u256(&encoded).unwrap();
818 assert_eq!(decoded, value, "roundtrip failed for {value}");
819 }
820 }
821
822 #[test]
823 fn test_u64_encode_decode_roundtrip() {
824 let values = [0u64, 1, 127, 128, 255, 256, 21000, 20_000_000_000, u64::MAX];
825
826 for value in values {
827 let mut encoded = Vec::new();
828 value.encode(&mut encoded);
829 let decoded = decode_u64(&encoded).unwrap();
830 assert_eq!(decoded, value, "roundtrip failed for {value}");
831 }
832 }
833
834 #[test]
835 fn test_address_encode_decode_roundtrip() {
836 let addresses = [
837 Address::ZERO,
838 Address::from([0x35u8; 20]),
839 Address::from([0xffu8; 20]),
840 ];
841
842 for addr in addresses {
843 let mut encoded = Vec::new();
844 addr.encode(&mut encoded);
845 let decoded = decode_address(&encoded).unwrap();
846 assert_eq!(decoded, addr, "roundtrip failed for {addr}");
847 }
848 }
849
850 // ------------------------------------------------------------------------
851 // RLP Decoding Error Tests
852 // ------------------------------------------------------------------------
853
854 #[test]
855 fn test_decode_bytes_truncated_data() {
856 // Arrange: RLP claims to have more data than actually present
857 // 0x85 = string of length 5, but only 3 bytes follow
858 let truncated = [0x85, 0x01, 0x02, 0x03];
859
860 // Act
861 let result = decode_bytes(&truncated);
862
863 // Assert: Should fail with InvalidRlp
864 assert!(result.is_err());
865 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
866 }
867
868 #[test]
869 fn test_decode_bytes_invalid_length_prefix() {
870 // Arrange: Invalid RLP with malformed length prefix
871 // 0xbf is the maximum single-byte string prefix, 0xc0 starts lists
872 // Using an invalid sequence that doesn't follow RLP rules
873 let invalid = [0xf9, 0x00]; // Long list prefix but data is too short
874
875 // Act
876 let result = decode_bytes(&invalid);
877
878 // Assert: Should fail with InvalidRlp
879 assert!(result.is_err());
880 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
881 }
882
883 #[test]
884 fn test_decode_bytes_empty_input() {
885 // Arrange: Empty input
886 let empty: [u8; 0] = [];
887
888 // Act
889 let result = decode_bytes(&empty);
890
891 // Assert: Should fail with InvalidRlp (no data to decode)
892 assert!(result.is_err());
893 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
894 }
895
896 #[test]
897 fn test_decode_list_truncated_data() {
898 // Arrange: RLP list claims to have more items than present
899 // 0xc3 = list of 3 bytes total payload, but only 2 bytes follow
900 let truncated = [0xc3, 0x01, 0x02];
901
902 // Act
903 let result = decode_list(&truncated);
904
905 // Assert: Should fail with InvalidRlp
906 assert!(result.is_err());
907 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
908 }
909
910 #[test]
911 fn test_decode_list_invalid_structure() {
912 // Arrange: Malformed list structure with invalid length encoding
913 // 0xf8 requires a length byte, but it's missing or invalid
914 let invalid = [0xf8];
915
916 // Act
917 let result = decode_list(&invalid);
918
919 // Assert: Should fail with InvalidRlp
920 assert!(result.is_err());
921 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
922 }
923
924 #[test]
925 fn test_decode_list_string_instead_of_list() {
926 // Arrange: Try to decode a string as a list
927 // 0x83 = string of length 3
928 let string_data = [0x83, 0x61, 0x62, 0x63];
929
930 // Act
931 let result = decode_list(&string_data);
932
933 // Assert: Should fail with InvalidRlp indicating expected list
934 assert!(result.is_err());
935 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
936 if let Err(ParseError::InvalidRlp { context }) = result {
937 assert!(context.contains("Expected list"));
938 }
939 }
940
941 #[test]
942 fn test_decode_list_empty_input() {
943 // Arrange: Empty input
944 let empty: [u8; 0] = [];
945
946 // Act
947 let result = decode_list(&empty);
948
949 // Assert: Should fail with InvalidRlp
950 assert!(result.is_err());
951 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
952 }
953
954 #[test]
955 fn test_decode_u256_invalid_encoding() {
956 // Arrange: Truncated U256 encoding
957 // 0x82 = string of length 2, but only 1 byte follows
958 let invalid = [0x82, 0x01];
959
960 // Act
961 let result = decode_u256(&invalid);
962
963 // Assert: Should fail with InvalidRlp
964 assert!(result.is_err());
965 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
966 }
967
968 #[test]
969 fn test_decode_u256_empty_input() {
970 // Arrange: Empty input
971 let empty: [u8; 0] = [];
972
973 // Act
974 let result = decode_u256(&empty);
975
976 // Assert: Should fail with InvalidRlp
977 assert!(result.is_err());
978 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
979 }
980
981 #[test]
982 fn test_decode_u64_overflow() {
983 // Arrange: Value that's too large for u64 (9 bytes)
984 // 0x89 = string of length 9
985 let too_large = [0x89, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];
986
987 // Act
988 let result = decode_u64(&too_large);
989
990 // Assert: Should fail with InvalidRlp
991 assert!(result.is_err());
992 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
993 }
994
995 #[test]
996 fn test_decode_u64_truncated() {
997 // Arrange: Claims 4 bytes but only has 3
998 let truncated = [0x84, 0x01, 0x02, 0x03];
999
1000 // Act
1001 let result = decode_u64(&truncated);
1002
1003 // Assert: Should fail with InvalidRlp
1004 assert!(result.is_err());
1005 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
1006 }
1007
1008 #[test]
1009 fn test_decode_address_wrong_length() {
1010 // Arrange: Address with wrong length (19 bytes instead of 20)
1011 let wrong_length = [
1012 0x93, // 19-byte string prefix
1013 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1014 0x0f, 0x10, 0x11, 0x12, 0x13,
1015 ];
1016
1017 // Act
1018 let result = decode_address(&wrong_length);
1019
1020 // Assert: Should fail with InvalidRlp
1021 assert!(result.is_err());
1022 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
1023 }
1024
1025 #[test]
1026 fn test_decode_address_truncated() {
1027 // Arrange: Claims 20 bytes but only has 19
1028 let truncated = [
1029 0x94, // 20-byte string prefix
1030 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1031 0x0f, 0x10, 0x11, 0x12, 0x13,
1032 ];
1033
1034 // Act
1035 let result = decode_address(&truncated);
1036
1037 // Assert: Should fail with InvalidRlp
1038 assert!(result.is_err());
1039 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
1040 }
1041
1042 #[test]
1043 fn test_decode_address_empty_input() {
1044 // Arrange: Empty input
1045 let empty: [u8; 0] = [];
1046
1047 // Act
1048 let result = decode_address(&empty);
1049
1050 // Assert: Should fail with InvalidRlp
1051 assert!(result.is_err());
1052 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
1053 }
1054
1055 #[test]
1056 fn test_decode_optional_address_invalid_length() {
1057 // Arrange: Non-empty address with wrong length
1058 let wrong_length = [
1059 0x93, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
1060 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
1061 ];
1062
1063 // Act
1064 let result = decode_optional_address(&wrong_length);
1065
1066 // Assert: Should fail with InvalidRlp
1067 assert!(result.is_err());
1068 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
1069 }
1070
1071 #[test]
1072 fn test_typed_tx_payload_boundary_type_0() {
1073 // Arrange: Type 0 transaction with minimal payload
1074 let type0 = [0x00];
1075
1076 // Act
1077 let result = typed_tx_payload(&type0);
1078
1079 // Assert: Should return empty slice after type byte
1080 assert!(result.is_ok());
1081 let payload = result.unwrap();
1082 assert!(payload.is_empty());
1083 }
1084
1085 #[test]
1086 fn test_typed_tx_payload_boundary_type_3() {
1087 // Arrange: Type 3 transaction (boundary of supported types)
1088 let type3 = [0x03, 0xf8, 0x73];
1089
1090 // Act
1091 let result = typed_tx_payload(&type3);
1092
1093 // Assert: Should return payload without type byte
1094 assert!(result.is_ok());
1095 assert_eq!(result.unwrap(), &[0xf8, 0x73]);
1096 }
1097
1098 #[test]
1099 fn test_rlp_boundary_length_zero() {
1100 // Arrange: RLP string with length = 0 (should be encoded as 0x80)
1101 let zero_length = [0x80];
1102
1103 // Act
1104 let result = decode_bytes(&zero_length);
1105
1106 // Assert: Should decode to empty vec
1107 assert!(result.is_ok());
1108 let decoded = result.unwrap();
1109 assert!(decoded.is_empty());
1110 }
1111
1112 #[test]
1113 fn test_rlp_boundary_length_one() {
1114 // Arrange: RLP with single byte (length = 1)
1115 // Single bytes < 0x80 are encoded as themselves
1116 let one_byte = [0x42];
1117
1118 // Act
1119 let result = decode_bytes(&one_byte);
1120
1121 // Assert: Should decode to vec with single byte
1122 assert!(result.is_ok());
1123 assert_eq!(result.unwrap(), vec![0x42]);
1124 }
1125
1126 #[test]
1127 fn test_rlp_malformed_long_string_length() {
1128 // Arrange: Long string (0xb8+) with invalid length encoding
1129 // 0xb8 means the next byte specifies the length of the length field
1130 // But if that's malformed, it should error
1131 let malformed = [0xb8, 0x01, 0xff]; // Says length-of-length is 1, then 0xff bytes (but not present)
1132
1133 // Act
1134 let result = decode_bytes(&malformed);
1135
1136 // Assert: Should fail with InvalidRlp
1137 assert!(result.is_err());
1138 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
1139 }
1140
1141 #[test]
1142 fn test_rlp_malformed_long_list_length() {
1143 // Arrange: Long list (0xf8+) with invalid length encoding
1144 let malformed = [0xf8, 0x01, 0xff]; // Says length-of-length is 1, then 0xff bytes (but not present)
1145
1146 // Act
1147 let result = decode_list(&malformed);
1148
1149 // Assert: Should fail with InvalidRlp
1150 assert!(result.is_err());
1151 assert!(matches!(result, Err(ParseError::InvalidRlp { .. })));
1152 }
1153}