Skip to main content

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}