Skip to main content

ethrex_common/types/
transaction.rs

1use std::{
2    cmp::min,
3    fmt::Display,
4    num::NonZeroUsize,
5    sync::{LazyLock, Mutex},
6};
7
8use crate::utils::keccak;
9use bytes::Bytes;
10use ethereum_types::{Address, H256, U256};
11use ethrex_crypto::{Crypto, CryptoError, NativeCrypto};
12use lru::LruCache;
13pub use mempool::MempoolTransaction;
14
15const MAX_SIGNER_CACHE_ENTRIES: usize = 100_000;
16
17/// Global cache mapping transaction hash → recovered sender address.
18/// Keyed by tx hash (unique per transaction), so each entry is safe to reuse.
19/// Not suitable for EIP-7702 authorization tuples where the same message hash
20/// can correspond to different signers (the message excludes the signature).
21/// Uses LRU eviction to avoid periodic cold-start spikes from clearing all entries.
22///
23/// Lock with `.unwrap_or_else(|e| e.into_inner())` — a poisoned mutex just means
24/// a thread panicked mid-update; the LruCache invariants are maintained by the
25/// std Mutex (data is still accessible), and a missing entry only costs one
26/// redundant recovery, so it's safe to keep using.
27pub static GLOBAL_SIGNER_CACHE: LazyLock<Mutex<LruCache<H256, Address>>> = LazyLock::new(|| {
28    Mutex::new(LruCache::new(
29        NonZeroUsize::new(MAX_SIGNER_CACHE_ENTRIES).expect("MAX_SIGNER_CACHE_ENTRIES is non-zero"),
30    ))
31});
32use rkyv::{Archive, Deserialize as RDeserialize, Serialize as RSerialize};
33use serde::{Serialize, ser::SerializeStruct};
34pub use serde_impl::{
35    AccessListEntry, AuthorizationTupleEntry, FrameEntry, GenericTransaction,
36    GenericTransactionError,
37};
38
39/// The serialized length of a default eip1559 transaction
40pub const EIP1559_DEFAULT_SERIALIZED_LENGTH: usize = 15;
41
42use ethrex_rlp::{
43    constants::RLP_NULL,
44    decode::{RLPDecode, decode_bytes, decode_rlp_item},
45    encode::{PayloadRLPEncode, RLPEncode},
46    error::RLPDecodeError,
47    structs::{Decoder, Encoder},
48};
49
50#[cfg(all(feature = "eip-8025", target_arch = "riscv64"))]
51use super::eip8025_cell::OnceCell;
52use crate::types::{
53    AccessList, AuthorizationList, BlobsBundle, constants::VERSIONED_HASH_VERSION_KZG,
54};
55#[cfg(not(all(feature = "eip-8025", target_arch = "riscv64")))]
56use once_cell::sync::OnceCell;
57
58// The `#[serde(untagged)]` attribute allows the `Transaction` enum to be serialized without
59// a tag indicating the variant type. This means that Serde will serialize the enum's variants
60// directly according to the structure of the variant itself.
61// For each variant, Serde will use the serialization logic implemented
62// for the inner type of that variant (like `LegacyTransaction`, `EIP2930Transaction`, etc.).
63// The serialization will fail if the data does not match the structure of any variant.
64//
65// A custom Deserialization method is implemented to match the specific transaction `type`.
66#[derive(Clone, Debug, PartialEq, Eq, Serialize, RSerialize, RDeserialize, Archive)]
67#[serde(untagged)]
68pub enum Transaction {
69    LegacyTransaction(LegacyTransaction),
70    EIP2930Transaction(EIP2930Transaction),
71    EIP1559Transaction(EIP1559Transaction),
72    EIP4844Transaction(EIP4844Transaction),
73    EIP7702Transaction(EIP7702Transaction),
74    PrivilegedL2Transaction(PrivilegedL2Transaction),
75    FeeTokenTransaction(FeeTokenTransaction),
76    FrameTransaction(FrameTransaction),
77}
78
79/// The same as a Transaction enum, only that blob transactions are in wrapped format, including
80/// the blobs bundle.
81/// PrivilegedL2Transaction is not included as it is not expected to be sent over P2P.
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub enum P2PTransaction {
84    LegacyTransaction(LegacyTransaction),
85    EIP2930Transaction(EIP2930Transaction),
86    EIP1559Transaction(EIP1559Transaction),
87    EIP4844TransactionWithBlobs(WrappedEIP4844Transaction),
88    EIP7702Transaction(EIP7702Transaction),
89    FeeTokenTransaction(FeeTokenTransaction),
90    FrameTransaction(FrameTransaction),
91}
92
93impl TryInto<Transaction> for P2PTransaction {
94    type Error = String;
95
96    fn try_into(self) -> Result<Transaction, Self::Error> {
97        match self {
98            P2PTransaction::LegacyTransaction(itx) => Ok(Transaction::LegacyTransaction(itx)),
99            P2PTransaction::EIP2930Transaction(itx) => Ok(Transaction::EIP2930Transaction(itx)),
100            P2PTransaction::EIP1559Transaction(itx) => Ok(Transaction::EIP1559Transaction(itx)),
101            P2PTransaction::EIP7702Transaction(itx) => Ok(Transaction::EIP7702Transaction(itx)),
102            P2PTransaction::FrameTransaction(itx) => Ok(Transaction::FrameTransaction(itx)),
103            _ => Err("Can't convert blob p2p transaction into regular transaction. Blob bundle would be lost.".to_string()),
104        }
105    }
106}
107
108impl RLPEncode for P2PTransaction {
109    fn encode(&self, buf: &mut dyn bytes::BufMut) {
110        match self {
111            P2PTransaction::LegacyTransaction(t) => t.encode(buf),
112            tx => <[u8] as RLPEncode>::encode(&tx.encode_canonical_to_vec(), buf),
113        };
114    }
115}
116
117impl RLPDecode for P2PTransaction {
118    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
119        let (is_list, payload, remainder) = decode_rlp_item(rlp)?;
120        if !is_list {
121            let tx_type = *payload.first().ok_or(RLPDecodeError::InvalidLength)?;
122            let tx_encoding = payload.get(1..).ok_or(RLPDecodeError::InvalidLength)?;
123            // `from_type_byte` is the single gate for valid envelope types: it rejects 0x00
124            // (legacy is the bare-list branch below) and any unassigned type byte.
125            let tx = match EnvelopeTxType::from_type_byte(tx_type)? {
126                EnvelopeTxType::EIP2930 => {
127                    P2PTransaction::EIP2930Transaction(EIP2930Transaction::decode(tx_encoding)?)
128                }
129                EnvelopeTxType::EIP1559 => {
130                    P2PTransaction::EIP1559Transaction(EIP1559Transaction::decode(tx_encoding)?)
131                }
132                EnvelopeTxType::EIP4844 => P2PTransaction::EIP4844TransactionWithBlobs(
133                    WrappedEIP4844Transaction::decode(tx_encoding)?,
134                ),
135                EnvelopeTxType::EIP7702 => {
136                    P2PTransaction::EIP7702Transaction(EIP7702Transaction::decode(tx_encoding)?)
137                }
138                EnvelopeTxType::Frame => {
139                    P2PTransaction::FrameTransaction(FrameTransaction::decode(tx_encoding)?)
140                }
141                EnvelopeTxType::FeeToken => {
142                    P2PTransaction::FeeTokenTransaction(FeeTokenTransaction::decode(tx_encoding)?)
143                }
144                // Privileged (L2) transactions are never gossiped over p2p.
145                EnvelopeTxType::Privileged => {
146                    return Err(RLPDecodeError::Custom(
147                        "privileged transactions are not sent over p2p".to_string(),
148                    ));
149                }
150            };
151            Ok((tx, remainder))
152        } else {
153            // LegacyTransaction
154            LegacyTransaction::decode_unfinished(rlp)
155                .map(|(tx, rem)| (P2PTransaction::LegacyTransaction(tx), rem))
156        }
157    }
158}
159
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub struct WrappedEIP4844Transaction {
162    pub tx: EIP4844Transaction,
163    pub wrapper_version: Option<u8>,
164    pub blobs_bundle: BlobsBundle,
165}
166
167impl RLPEncode for WrappedEIP4844Transaction {
168    fn encode(&self, buf: &mut dyn bytes::BufMut) {
169        let encoder = Encoder::new(buf);
170        encoder
171            .encode_field(&self.tx)
172            .encode_optional_field(&self.wrapper_version)
173            .encode_field(&self.blobs_bundle.blobs)
174            .encode_field(&self.blobs_bundle.commitments)
175            .encode_field(&self.blobs_bundle.proofs)
176            .finish();
177    }
178}
179
180impl RLPDecode for WrappedEIP4844Transaction {
181    fn decode_unfinished(rlp: &[u8]) -> Result<(WrappedEIP4844Transaction, &[u8]), RLPDecodeError> {
182        let decoder = Decoder::new(rlp)?;
183        let Ok((tx, decoder)) = decoder.decode_field("tx") else {
184            // Handle the case of blobless transaction
185            let (tx, rest) = EIP4844Transaction::decode_unfinished(rlp)?;
186            return Ok((
187                WrappedEIP4844Transaction {
188                    tx,
189                    wrapper_version: None,
190                    // Empty blobs bundles are not valid
191                    blobs_bundle: BlobsBundle::empty(),
192                },
193                rest,
194            ));
195        };
196
197        let (wrapper_version, decoder) = decoder.decode_optional_field();
198        let (blobs, decoder) = decoder.decode_field("blobs")?;
199        let (commitments, decoder) = decoder.decode_field("commitments")?;
200        let (proofs, decoder) = decoder.decode_field("proofs")?;
201
202        let wrapped = WrappedEIP4844Transaction {
203            tx,
204            wrapper_version,
205            blobs_bundle: BlobsBundle {
206                blobs,
207                commitments,
208                proofs,
209                version: wrapper_version.unwrap_or_default(),
210            },
211        };
212        Ok((wrapped, decoder.finish()?))
213    }
214}
215
216#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
217pub struct LegacyTransaction {
218    pub nonce: u64,
219    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
220    pub gas_price: U256,
221    pub gas: u64,
222    /// The recipient of the transaction.
223    /// Create transactions contain a [`null`](RLP_NULL) value in this field.
224    pub to: TxKind,
225    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
226    pub value: U256,
227    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
228    pub data: Bytes,
229    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
230    pub v: U256,
231    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
232    pub r: U256,
233    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
234    pub s: U256,
235    #[rkyv(with=rkyv::with::Skip)]
236    pub inner_hash: OnceCell<H256>,
237    #[rkyv(with=rkyv::with::Skip)]
238    pub sender_cache: OnceCell<Address>,
239}
240
241#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
242pub struct EIP2930Transaction {
243    pub chain_id: u64,
244    pub nonce: u64,
245    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
246    pub gas_price: U256,
247    pub gas_limit: u64,
248    pub to: TxKind,
249    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
250    pub value: U256,
251    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
252    pub data: Bytes,
253    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
254    pub access_list: AccessList,
255    pub signature_y_parity: bool,
256    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
257    pub signature_r: U256,
258    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
259    pub signature_s: U256,
260    #[rkyv(with=rkyv::with::Skip)]
261    pub inner_hash: OnceCell<H256>,
262    #[rkyv(with=rkyv::with::Skip)]
263    pub sender_cache: OnceCell<Address>,
264    #[rkyv(with=rkyv::with::Skip)]
265    pub cached_canonical: OnceCell<Vec<u8>>,
266}
267
268#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
269pub struct EIP1559Transaction {
270    pub chain_id: u64,
271    pub nonce: u64,
272    pub max_priority_fee_per_gas: u64,
273    pub max_fee_per_gas: u64,
274    pub gas_limit: u64,
275    pub to: TxKind,
276    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
277    pub value: U256,
278    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
279    pub data: Bytes,
280    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
281    pub access_list: AccessList,
282    pub signature_y_parity: bool,
283    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
284    pub signature_r: U256,
285    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
286    pub signature_s: U256,
287    #[rkyv(with=rkyv::with::Skip)]
288    pub inner_hash: OnceCell<H256>,
289    #[rkyv(with=rkyv::with::Skip)]
290    pub sender_cache: OnceCell<Address>,
291    #[rkyv(with=rkyv::with::Skip)]
292    pub cached_canonical: OnceCell<Vec<u8>>,
293}
294
295#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
296pub struct EIP4844Transaction {
297    pub chain_id: u64,
298    pub nonce: u64,
299    pub max_priority_fee_per_gas: u64,
300    pub max_fee_per_gas: u64,
301    pub gas: u64,
302    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
303    pub to: Address,
304    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
305    pub value: U256,
306    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
307    pub data: Bytes,
308    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
309    pub access_list: AccessList,
310    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
311    pub max_fee_per_blob_gas: U256,
312    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H256Wrapper>)]
313    pub blob_versioned_hashes: Vec<H256>,
314    pub signature_y_parity: bool,
315    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
316    pub signature_r: U256,
317    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
318    pub signature_s: U256,
319    #[rkyv(with=rkyv::with::Skip)]
320    pub inner_hash: OnceCell<H256>,
321    #[rkyv(with=rkyv::with::Skip)]
322    pub sender_cache: OnceCell<Address>,
323    #[rkyv(with=rkyv::with::Skip)]
324    pub cached_canonical: OnceCell<Vec<u8>>,
325}
326
327#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
328pub struct EIP7702Transaction {
329    pub chain_id: u64,
330    pub nonce: u64,
331    pub max_priority_fee_per_gas: u64,
332    pub max_fee_per_gas: u64,
333    pub gas_limit: u64,
334    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
335    pub to: Address,
336    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
337    pub value: U256,
338    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
339    pub data: Bytes,
340    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
341    pub access_list: AccessList,
342    pub authorization_list: AuthorizationList,
343    pub signature_y_parity: bool,
344    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
345    pub signature_r: U256,
346    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
347    pub signature_s: U256,
348    #[rkyv(with=rkyv::with::Skip)]
349    pub inner_hash: OnceCell<H256>,
350    #[rkyv(with=rkyv::with::Skip)]
351    pub sender_cache: OnceCell<Address>,
352    #[rkyv(with=rkyv::with::Skip)]
353    pub cached_canonical: OnceCell<Vec<u8>>,
354}
355#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
356pub struct PrivilegedL2Transaction {
357    pub chain_id: u64,
358    pub nonce: u64,
359    pub max_priority_fee_per_gas: u64,
360    pub max_fee_per_gas: u64,
361    pub gas_limit: u64,
362    pub to: TxKind,
363    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
364    pub value: U256,
365    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
366    pub data: Bytes,
367    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
368    pub access_list: AccessList,
369    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
370    pub from: Address,
371    #[rkyv(with=rkyv::with::Skip)]
372    pub inner_hash: OnceCell<H256>,
373    #[rkyv(with=rkyv::with::Skip)]
374    pub sender_cache: OnceCell<Address>,
375    #[rkyv(with=rkyv::with::Skip)]
376    pub cached_canonical: OnceCell<Vec<u8>>,
377}
378
379#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
380pub enum TxType {
381    #[default]
382    Legacy = 0x00,
383    EIP2930 = 0x01,
384    EIP1559 = 0x02,
385    EIP4844 = 0x03,
386    EIP7702 = 0x04,
387    Frame = 0x06,
388    FeeToken = 0x7d,
389    // We take the same approach as Optimism to define the privileged tx prefix
390    // https://github.com/ethereum-optimism/specs/blob/c6903a3b2cad575653e1f5ef472debb573d83805/specs/protocol/deposits.md#the-deposited-transaction-type
391    Privileged = 0x7e,
392}
393
394/// The EIP-2718 typed-transaction **envelope** types — the subset of [`TxType`] that
395/// can be encoded as a `0x{type} || payload` envelope. Excludes [`TxType::Legacy`]:
396/// a legacy transaction is a bare RLP list, never a typed envelope. Decoders that
397/// dispatch on the leading type byte match this exhaustively, so there is no
398/// impossible `Legacy` arm to handle.
399#[derive(Clone, Copy, Debug, PartialEq, Eq)]
400pub enum EnvelopeTxType {
401    EIP2930,
402    EIP1559,
403    EIP4844,
404    EIP7702,
405    Frame,
406    FeeToken,
407    Privileged,
408}
409
410impl EnvelopeTxType {
411    /// Resolve a typed-transaction **envelope** type byte (EIP-2718) to its type.
412    ///
413    /// Reuses [`TxType::from_u8`] for the byte mapping (single source of truth) and
414    /// [`TxType::as_envelope`] to reject non-envelope types. **Rejects `0x00`**: per EIP-2718
415    /// a legacy transaction is a bare RLP list, never a `0x00`-typed envelope, so
416    /// `0x00 || rlp(..)` is non-canonical and must be rejected (matching go-ethereum) rather
417    /// than silently decoded as legacy. Unassigned type bytes are rejected too.
418    pub fn from_type_byte(byte: u8) -> Result<Self, RLPDecodeError> {
419        TxType::from_u8(byte)
420            .and_then(TxType::as_envelope)
421            .ok_or_else(|| RLPDecodeError::Custom(format!("Invalid transaction type: {byte}")))
422    }
423}
424
425impl From<TxType> for u8 {
426    fn from(val: TxType) -> Self {
427        match val {
428            TxType::Legacy => 0x00,
429            TxType::EIP2930 => 0x01,
430            TxType::EIP1559 => 0x02,
431            TxType::EIP4844 => 0x03,
432            TxType::EIP7702 => 0x04,
433            TxType::Frame => 0x06,
434            TxType::FeeToken => 0x7d,
435            TxType::Privileged => 0x7e,
436        }
437    }
438}
439
440impl Display for TxType {
441    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442        match self {
443            TxType::Legacy => write!(f, "Legacy"),
444            TxType::EIP2930 => write!(f, "EIP2930"),
445            TxType::EIP1559 => write!(f, "EIP1559"),
446            TxType::EIP4844 => write!(f, "EIP4844"),
447            TxType::EIP7702 => write!(f, "EIP7702"),
448            TxType::Frame => write!(f, "Frame"),
449            TxType::Privileged => write!(f, "Privileged"),
450            TxType::FeeToken => write!(f, "FeeToken"),
451        }
452    }
453}
454
455impl Transaction {
456    pub fn tx_type(&self) -> TxType {
457        match self {
458            Transaction::LegacyTransaction(_) => TxType::Legacy,
459            Transaction::EIP2930Transaction(_) => TxType::EIP2930,
460            Transaction::EIP1559Transaction(_) => TxType::EIP1559,
461            Transaction::EIP4844Transaction(_) => TxType::EIP4844,
462            Transaction::EIP7702Transaction(_) => TxType::EIP7702,
463            Transaction::FeeTokenTransaction(_) => TxType::FeeToken,
464            Transaction::PrivilegedL2Transaction(_) => TxType::Privileged,
465            Transaction::FrameTransaction(_) => TxType::Frame,
466        }
467    }
468
469    fn calc_effective_gas_price(&self, base_fee_per_gas: Option<u64>) -> Option<U256> {
470        let base_fee = base_fee_per_gas?;
471        let max_fee = self.max_fee_per_gas()?;
472        if max_fee < base_fee {
473            // This is invalid, can't calculate
474            return None;
475        }
476
477        let priority_fee_per_gas = min(self.max_priority_fee()?, max_fee.saturating_sub(base_fee));
478        Some(U256::from(priority_fee_per_gas) + U256::from(base_fee))
479    }
480
481    pub fn effective_gas_price(&self, base_fee_per_gas: Option<u64>) -> Option<U256> {
482        match self.tx_type() {
483            TxType::Legacy => Some(self.gas_price()),
484            TxType::EIP2930 => Some(self.gas_price()),
485            TxType::EIP1559 => self.calc_effective_gas_price(base_fee_per_gas),
486            TxType::EIP4844 => self.calc_effective_gas_price(base_fee_per_gas),
487            TxType::EIP7702 => self.calc_effective_gas_price(base_fee_per_gas),
488            TxType::Frame => self.calc_effective_gas_price(base_fee_per_gas),
489            TxType::FeeToken => self.calc_effective_gas_price(base_fee_per_gas),
490            TxType::Privileged => Some(self.gas_price()),
491        }
492    }
493
494    pub fn cost_without_base_fee(&self) -> Option<U256> {
495        let price = match self.tx_type() {
496            TxType::Legacy => self.gas_price(),
497            TxType::EIP2930 => self.gas_price(),
498            TxType::EIP1559 => U256::from(self.max_fee_per_gas()?),
499            TxType::EIP4844 => U256::from(self.max_fee_per_gas()?),
500            TxType::EIP7702 => U256::from(self.max_fee_per_gas()?),
501            TxType::Frame => U256::from(self.max_fee_per_gas()?),
502            TxType::FeeToken => U256::from(self.max_fee_per_gas()?),
503            TxType::Privileged => self.gas_price(),
504        };
505
506        let base = U256::saturating_add(
507            U256::saturating_mul(price, self.gas_limit().into()),
508            self.value(),
509        );
510
511        // EIP-4844 blob txs pay an additional `blob_gas * max_fee_per_blob_gas`
512        // upfront. Every peer client (geth, reth, nethermind, erigon, besu)
513        // includes this in the balance-sufficiency check.
514        if let Transaction::EIP4844Transaction(tx) = self {
515            let blob_gas = U256::from(crate::constants::GAS_PER_BLOB)
516                .saturating_mul(U256::from(tx.blob_versioned_hashes.len() as u64));
517            let blob_cost = blob_gas.saturating_mul(tx.max_fee_per_blob_gas);
518            return Some(base.saturating_add(blob_cost));
519        }
520
521        Some(base)
522    }
523
524    pub fn fee_token(&self) -> Option<Address> {
525        if let Transaction::FeeTokenTransaction(tx) = self {
526            Some(tx.fee_token)
527        } else {
528            None
529        }
530    }
531
532    /// Returns a reference to the `cached_canonical` cell for non-legacy
533    /// transaction types, or `None` for legacy transactions.
534    fn cached_canonical_cell(&self) -> Option<&OnceCell<Vec<u8>>> {
535        match self {
536            Transaction::LegacyTransaction(_) => None,
537            Transaction::EIP2930Transaction(t) => Some(&t.cached_canonical),
538            Transaction::EIP1559Transaction(t) => Some(&t.cached_canonical),
539            Transaction::EIP4844Transaction(t) => Some(&t.cached_canonical),
540            Transaction::EIP7702Transaction(t) => Some(&t.cached_canonical),
541            Transaction::PrivilegedL2Transaction(t) => Some(&t.cached_canonical),
542            Transaction::FeeTokenTransaction(t) => Some(&t.cached_canonical),
543            Transaction::FrameTransaction(t) => Some(&t.cached_canonical),
544        }
545    }
546}
547
548impl RLPEncode for Transaction {
549    /// Transactions can be encoded in the following formats:
550    /// A) Legacy transactions: rlp(LegacyTransaction)
551    /// B) Non legacy transactions: rlp(Bytes) where Bytes represents the canonical encoding for the transaction as a bytes object.
552    /// Checkout [Transaction::encode_canonical] for more information
553    fn encode(&self, buf: &mut dyn bytes::BufMut) {
554        match self {
555            Transaction::LegacyTransaction(t) => t.encode(buf),
556            _ => {
557                let canonical = self.encode_canonical_to_vec();
558                <[u8] as RLPEncode>::encode(canonical.as_slice(), buf)
559            }
560        };
561    }
562}
563
564impl RLPDecode for Transaction {
565    /// Transactions can be encoded in the following formats:
566    /// A) Legacy transactions: rlp(LegacyTransaction)
567    /// B) Non legacy transactions: rlp(Bytes) where Bytes represents the canonical encoding for the transaction as a bytes object.
568    /// Checkout [Transaction::decode_canonical] for more information
569    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
570        let (is_list, payload, remainder) = decode_rlp_item(rlp)?;
571        if !is_list {
572            let tx_type = *payload.first().ok_or(RLPDecodeError::InvalidLength)?;
573            let tx_encoding = payload.get(1..).ok_or(RLPDecodeError::InvalidLength)?;
574            // `from_type_byte` is the single gate for valid envelope types: it rejects 0x00
575            // (a legacy tx is a bare RLP list, handled below, not a typed envelope) and any
576            // unassigned type byte.
577            let tx = match EnvelopeTxType::from_type_byte(tx_type)? {
578                EnvelopeTxType::EIP2930 => {
579                    Transaction::EIP2930Transaction(EIP2930Transaction::decode(tx_encoding)?)
580                }
581                EnvelopeTxType::EIP1559 => {
582                    Transaction::EIP1559Transaction(EIP1559Transaction::decode(tx_encoding)?)
583                }
584                EnvelopeTxType::EIP4844 => {
585                    Transaction::EIP4844Transaction(EIP4844Transaction::decode(tx_encoding)?)
586                }
587                EnvelopeTxType::EIP7702 => {
588                    Transaction::EIP7702Transaction(EIP7702Transaction::decode(tx_encoding)?)
589                }
590                EnvelopeTxType::Frame => {
591                    Transaction::FrameTransaction(FrameTransaction::decode(tx_encoding)?)
592                }
593                EnvelopeTxType::FeeToken => {
594                    Transaction::FeeTokenTransaction(FeeTokenTransaction::decode(tx_encoding)?)
595                }
596                EnvelopeTxType::Privileged => Transaction::PrivilegedL2Transaction(
597                    PrivilegedL2Transaction::decode(tx_encoding)?,
598                ),
599            };
600            Ok((tx, remainder))
601        } else {
602            // LegacyTransaction
603            LegacyTransaction::decode_unfinished(rlp)
604                .map(|(tx, rem)| (Transaction::LegacyTransaction(tx), rem))
605        }
606    }
607}
608
609/// The transaction's kind: call or create.
610#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
611pub enum TxKind {
612    Call(#[rkyv(with=crate::rkyv_utils::H160Wrapper)] Address),
613    #[default]
614    Create,
615}
616
617impl RLPEncode for TxKind {
618    fn encode(&self, buf: &mut dyn bytes::BufMut) {
619        match self {
620            Self::Call(address) => address.encode(buf),
621            Self::Create => buf.put_u8(RLP_NULL),
622        }
623    }
624}
625
626impl RLPDecode for TxKind {
627    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
628        let first_byte = rlp.first().ok_or(RLPDecodeError::InvalidLength)?;
629        if *first_byte == RLP_NULL {
630            return Ok((Self::Create, &rlp[1..]));
631        }
632        Address::decode_unfinished(rlp).map(|(t, rest)| (Self::Call(t), rest))
633    }
634}
635
636impl RLPEncode for LegacyTransaction {
637    fn encode(&self, buf: &mut dyn bytes::BufMut) {
638        Encoder::new(buf)
639            .encode_field(&self.nonce)
640            .encode_field(&self.gas_price)
641            .encode_field(&self.gas)
642            .encode_field(&self.to)
643            .encode_field(&self.value)
644            .encode_field(&self.data)
645            .encode_field(&self.v)
646            .encode_field(&self.r)
647            .encode_field(&self.s)
648            .finish();
649    }
650}
651
652impl RLPEncode for EIP2930Transaction {
653    fn encode(&self, buf: &mut dyn bytes::BufMut) {
654        Encoder::new(buf)
655            .encode_field(&self.chain_id)
656            .encode_field(&self.nonce)
657            .encode_field(&self.gas_price)
658            .encode_field(&self.gas_limit)
659            .encode_field(&self.to)
660            .encode_field(&self.value)
661            .encode_field(&self.data)
662            .encode_field(&self.access_list)
663            .encode_field(&self.signature_y_parity)
664            .encode_field(&self.signature_r)
665            .encode_field(&self.signature_s)
666            .finish()
667    }
668}
669
670impl RLPEncode for EIP1559Transaction {
671    fn encode(&self, buf: &mut dyn bytes::BufMut) {
672        Encoder::new(buf)
673            .encode_field(&self.chain_id)
674            .encode_field(&self.nonce)
675            .encode_field(&self.max_priority_fee_per_gas)
676            .encode_field(&self.max_fee_per_gas)
677            .encode_field(&self.gas_limit)
678            .encode_field(&self.to)
679            .encode_field(&self.value)
680            .encode_field(&self.data)
681            .encode_field(&self.access_list)
682            .encode_field(&self.signature_y_parity)
683            .encode_field(&self.signature_r)
684            .encode_field(&self.signature_s)
685            .finish()
686    }
687}
688
689impl RLPEncode for EIP4844Transaction {
690    fn encode(&self, buf: &mut dyn bytes::BufMut) {
691        Encoder::new(buf)
692            .encode_field(&self.chain_id)
693            .encode_field(&self.nonce)
694            .encode_field(&self.max_priority_fee_per_gas)
695            .encode_field(&self.max_fee_per_gas)
696            .encode_field(&self.gas)
697            .encode_field(&self.to)
698            .encode_field(&self.value)
699            .encode_field(&self.data)
700            .encode_field(&self.access_list)
701            .encode_field(&self.max_fee_per_blob_gas)
702            .encode_field(&self.blob_versioned_hashes)
703            .encode_field(&self.signature_y_parity)
704            .encode_field(&self.signature_r)
705            .encode_field(&self.signature_s)
706            .finish()
707    }
708}
709
710impl RLPEncode for EIP7702Transaction {
711    fn encode(&self, buf: &mut dyn bytes::BufMut) {
712        Encoder::new(buf)
713            .encode_field(&self.chain_id)
714            .encode_field(&self.nonce)
715            .encode_field(&self.max_priority_fee_per_gas)
716            .encode_field(&self.max_fee_per_gas)
717            .encode_field(&self.gas_limit)
718            .encode_field(&self.to)
719            .encode_field(&self.value)
720            .encode_field(&self.data)
721            .encode_field(&self.access_list)
722            .encode_field(&self.authorization_list)
723            .encode_field(&self.signature_y_parity)
724            .encode_field(&self.signature_r)
725            .encode_field(&self.signature_s)
726            .finish()
727    }
728}
729
730impl RLPEncode for PrivilegedL2Transaction {
731    fn encode(&self, buf: &mut dyn bytes::BufMut) {
732        Encoder::new(buf)
733            .encode_field(&self.chain_id)
734            .encode_field(&self.nonce)
735            .encode_field(&self.max_priority_fee_per_gas)
736            .encode_field(&self.max_fee_per_gas)
737            .encode_field(&self.gas_limit)
738            .encode_field(&self.to)
739            .encode_field(&self.value)
740            .encode_field(&self.data)
741            .encode_field(&self.access_list)
742            .encode_field(&self.from)
743            .finish()
744    }
745}
746
747impl RLPEncode for FeeTokenTransaction {
748    fn encode(&self, buf: &mut dyn bytes::BufMut) {
749        Encoder::new(buf)
750            .encode_field(&self.chain_id)
751            .encode_field(&self.nonce)
752            .encode_field(&self.max_priority_fee_per_gas)
753            .encode_field(&self.max_fee_per_gas)
754            .encode_field(&self.gas_limit)
755            .encode_field(&self.to)
756            .encode_field(&self.value)
757            .encode_field(&self.data)
758            .encode_field(&self.access_list)
759            .encode_field(&self.fee_token)
760            .encode_field(&self.signature_y_parity)
761            .encode_field(&self.signature_r)
762            .encode_field(&self.signature_s)
763            .finish()
764    }
765}
766
767impl PayloadRLPEncode for Transaction {
768    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
769        match self {
770            Transaction::LegacyTransaction(tx) => tx.encode_payload(buf),
771            Transaction::EIP1559Transaction(tx) => tx.encode_payload(buf),
772            Transaction::EIP2930Transaction(tx) => tx.encode_payload(buf),
773            Transaction::EIP4844Transaction(tx) => tx.encode_payload(buf),
774            Transaction::EIP7702Transaction(tx) => tx.encode_payload(buf),
775            Transaction::PrivilegedL2Transaction(tx) => tx.encode_payload(buf),
776            Transaction::FeeTokenTransaction(tx) => tx.encode_payload(buf),
777            Transaction::FrameTransaction(tx) => tx.encode_payload(buf),
778        }
779    }
780}
781
782impl PayloadRLPEncode for LegacyTransaction {
783    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
784        Encoder::new(buf)
785            .encode_field(&self.nonce)
786            .encode_field(&self.gas_price)
787            .encode_field(&self.gas)
788            .encode_field(&self.to)
789            .encode_field(&self.value)
790            .encode_field(&self.data)
791            .finish();
792    }
793}
794
795impl PayloadRLPEncode for EIP1559Transaction {
796    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
797        Encoder::new(buf)
798            .encode_field(&self.chain_id)
799            .encode_field(&self.nonce)
800            .encode_field(&self.max_priority_fee_per_gas)
801            .encode_field(&self.max_fee_per_gas)
802            .encode_field(&self.gas_limit)
803            .encode_field(&self.to)
804            .encode_field(&self.value)
805            .encode_field(&self.data)
806            .encode_field(&self.access_list)
807            .finish();
808    }
809}
810
811impl PayloadRLPEncode for EIP2930Transaction {
812    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
813        Encoder::new(buf)
814            .encode_field(&self.chain_id)
815            .encode_field(&self.nonce)
816            .encode_field(&self.gas_price)
817            .encode_field(&self.gas_limit)
818            .encode_field(&self.to)
819            .encode_field(&self.value)
820            .encode_field(&self.data)
821            .encode_field(&self.access_list)
822            .finish();
823    }
824}
825
826impl PayloadRLPEncode for EIP4844Transaction {
827    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
828        Encoder::new(buf)
829            .encode_field(&self.chain_id)
830            .encode_field(&self.nonce)
831            .encode_field(&self.max_priority_fee_per_gas)
832            .encode_field(&self.max_fee_per_gas)
833            .encode_field(&self.gas)
834            .encode_field(&self.to)
835            .encode_field(&self.value)
836            .encode_field(&self.data)
837            .encode_field(&self.access_list)
838            .encode_field(&self.max_fee_per_blob_gas)
839            .encode_field(&self.blob_versioned_hashes)
840            .finish();
841    }
842}
843
844impl PayloadRLPEncode for EIP7702Transaction {
845    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
846        Encoder::new(buf)
847            .encode_field(&self.chain_id)
848            .encode_field(&self.nonce)
849            .encode_field(&self.max_priority_fee_per_gas)
850            .encode_field(&self.max_fee_per_gas)
851            .encode_field(&self.gas_limit)
852            .encode_field(&self.to)
853            .encode_field(&self.value)
854            .encode_field(&self.data)
855            .encode_field(&self.access_list)
856            .encode_field(&self.authorization_list)
857            .finish();
858    }
859}
860
861impl PayloadRLPEncode for PrivilegedL2Transaction {
862    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
863        Encoder::new(buf)
864            .encode_field(&self.chain_id)
865            .encode_field(&self.nonce)
866            .encode_field(&self.max_priority_fee_per_gas)
867            .encode_field(&self.max_fee_per_gas)
868            .encode_field(&self.gas_limit)
869            .encode_field(&self.to)
870            .encode_field(&self.value)
871            .encode_field(&self.data)
872            .encode_field(&self.access_list)
873            .encode_field(&self.from)
874            .finish();
875    }
876}
877
878impl PayloadRLPEncode for FeeTokenTransaction {
879    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
880        Encoder::new(buf)
881            .encode_field(&self.chain_id)
882            .encode_field(&self.nonce)
883            .encode_field(&self.max_priority_fee_per_gas)
884            .encode_field(&self.max_fee_per_gas)
885            .encode_field(&self.gas_limit)
886            .encode_field(&self.to)
887            .encode_field(&self.value)
888            .encode_field(&self.data)
889            .encode_field(&self.access_list)
890            .encode_field(&self.fee_token)
891            .finish();
892    }
893}
894
895/// Decode a transaction `nonce` field.
896///
897/// EEST encodes an over-`u64` nonce (a value >= 2**64, i.e. more than 8
898/// significant bytes) to exercise `TransactionException.NONCE_IS_MAX`. ethrex
899/// represents the nonce as a `u64`, so a plain RLP decode fails with a generic
900/// field-decode error that the EEST/Hive exception mapper classifies as
901/// `RLP_STRUCTURES_ENCODING` instead of `NONCE_IS_MAX`. Detect the over-`u64`
902/// case and surface a "Nonce is max" message so it maps to `NONCE_IS_MAX` in
903/// both the upstream Python `ethrex.py` mapper and the local Rust mapper.
904fn decode_nonce_field(decoder: Decoder) -> Result<(u64, Decoder), RLPDecodeError> {
905    // Wrap every decode failure exactly as `Decoder::decode_field` would, so
906    // non-overflow errors keep their original wording (and mapper classification);
907    // only the intentional over-u64 case below diverges.
908    let wrap =
909        |err| RLPDecodeError::Custom(format!("Error decoding field 'nonce' of type u64: {err}"));
910    let (item, rest) = decoder.get_encoded_item_ref().map_err(wrap)?;
911    // A *canonical* over-u64 nonce (>= 2**64) has more than 8 significant bytes and,
912    // being canonical, no leading zero byte. EEST uses it for
913    // TransactionException.NONCE_IS_MAX; surface a nonce-domain rejection rather than
914    // a generic RLP field-decode error so the exception mapper maps it to NONCE_IS_MAX
915    // in both the upstream Python and local Rust mappers.
916    //
917    // A non-canonical >8-byte encoding (leading-zero padding) is a malformed RLP
918    // integer, not an over-u64 value, so it must NOT be reported as NONCE_IS_MAX. Leave
919    // it to fall through to `u64::decode_unfinished` below, which rejects it as an
920    // ordinary field-decode error (mapped to RLP_STRUCTURES_ENCODING) — matching strict
921    // mappers like Hive.
922    if let Ok((content, _)) = decode_bytes(item)
923        && content.len() > 8
924        && content.first() != Some(&0)
925    {
926        return Err(RLPDecodeError::Custom("Nonce is max".into()));
927    }
928    let (nonce, _) = u64::decode_unfinished(item).map_err(wrap)?;
929    Ok((nonce, rest))
930}
931
932impl RLPDecode for LegacyTransaction {
933    fn decode_unfinished(rlp: &[u8]) -> Result<(LegacyTransaction, &[u8]), RLPDecodeError> {
934        let decoder = Decoder::new(rlp)?;
935        let (nonce, decoder) = decode_nonce_field(decoder)?;
936        let (gas_price, decoder) = decoder.decode_field("gas_price")?;
937        let (gas, decoder) = decoder.decode_field("gas")?;
938        let (to, decoder) = decoder.decode_field("to")?;
939        let (value, decoder) = decoder.decode_field("value")?;
940        let (data, decoder) = decoder.decode_field("data")?;
941        let (v, decoder) = decoder.decode_field("v")?;
942        let (r, decoder) = decoder.decode_field("r")?;
943        let (s, decoder) = decoder.decode_field("s")?;
944        let inner_hash = OnceCell::new();
945        let sender_cache = OnceCell::new();
946
947        let tx = LegacyTransaction {
948            nonce,
949            gas_price,
950            gas,
951            to,
952            value,
953            data,
954            v,
955            r,
956            s,
957            inner_hash,
958            sender_cache,
959        };
960        Ok((tx, decoder.finish()?))
961    }
962}
963
964impl RLPDecode for EIP2930Transaction {
965    fn decode_unfinished(rlp: &[u8]) -> Result<(EIP2930Transaction, &[u8]), RLPDecodeError> {
966        let decoder = Decoder::new(rlp)?;
967        let (chain_id, decoder) = decoder.decode_field("chain_id")?;
968        let (nonce, decoder) = decode_nonce_field(decoder)?;
969        let (gas_price, decoder) = decoder.decode_field("gas_price")?;
970        let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
971        let (to, decoder) = decoder.decode_field("to")?;
972        let (value, decoder) = decoder.decode_field("value")?;
973        let (data, decoder) = decoder.decode_field("data")?;
974        let (access_list, decoder) = decoder.decode_field("access_list")?;
975        let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
976        let (signature_r, decoder) = decoder.decode_field("signature_r")?;
977        let (signature_s, decoder) = decoder.decode_field("signature_s")?;
978        let inner_hash = OnceCell::new();
979        let sender_cache = OnceCell::new();
980        let cached_canonical = OnceCell::new();
981
982        let tx = EIP2930Transaction {
983            chain_id,
984            nonce,
985            gas_price,
986            gas_limit,
987            to,
988            value,
989            data,
990            access_list,
991            signature_y_parity,
992            signature_r,
993            signature_s,
994            inner_hash,
995            sender_cache,
996            cached_canonical,
997        };
998        Ok((tx, decoder.finish()?))
999    }
1000}
1001
1002impl RLPDecode for EIP1559Transaction {
1003    fn decode_unfinished(rlp: &[u8]) -> Result<(EIP1559Transaction, &[u8]), RLPDecodeError> {
1004        let decoder = Decoder::new(rlp)?;
1005        let (chain_id, decoder) = decoder.decode_field("chain_id")?;
1006        let (nonce, decoder) = decode_nonce_field(decoder)?;
1007        let (max_priority_fee_per_gas, decoder) =
1008            decoder.decode_field("max_priority_fee_per_gas")?;
1009        let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
1010        let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
1011        let (to, decoder) = decoder.decode_field("to")?;
1012        let (value, decoder) = decoder.decode_field("value")?;
1013        let (data, decoder) = decoder.decode_field("data")?;
1014        let (access_list, decoder) = decoder.decode_field("access_list")?;
1015        let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
1016        let (signature_r, decoder) = decoder.decode_field("signature_r")?;
1017        let (signature_s, decoder) = decoder.decode_field("signature_s")?;
1018        let inner_hash = OnceCell::new();
1019        let sender_cache = OnceCell::new();
1020        let cached_canonical = OnceCell::new();
1021
1022        let tx = EIP1559Transaction {
1023            chain_id,
1024            nonce,
1025            max_priority_fee_per_gas,
1026            max_fee_per_gas,
1027            gas_limit,
1028            to,
1029            value,
1030            data,
1031            access_list,
1032            signature_y_parity,
1033            signature_r,
1034            signature_s,
1035            inner_hash,
1036            sender_cache,
1037            cached_canonical,
1038        };
1039        Ok((tx, decoder.finish()?))
1040    }
1041}
1042
1043impl RLPDecode for EIP4844Transaction {
1044    fn decode_unfinished(rlp: &[u8]) -> Result<(EIP4844Transaction, &[u8]), RLPDecodeError> {
1045        let decoder = Decoder::new(rlp)?;
1046        let (chain_id, decoder) = decoder.decode_field("chain_id")?;
1047        let (nonce, decoder) = decode_nonce_field(decoder)?;
1048        let (max_priority_fee_per_gas, decoder) =
1049            decoder.decode_field("max_priority_fee_per_gas")?;
1050        let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
1051        let (gas, decoder) = decoder.decode_field("gas")?;
1052        let (to, decoder) = decoder.decode_field("to")?;
1053        let (value, decoder) = decoder.decode_field("value")?;
1054        let (data, decoder) = decoder.decode_field("data")?;
1055        let (access_list, decoder) = decoder.decode_field("access_list")?;
1056        let (max_fee_per_blob_gas, decoder) = decoder.decode_field("max_fee_per_blob_gas")?;
1057        let (blob_versioned_hashes, decoder) = decoder.decode_field("blob_versioned_hashes")?;
1058        let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
1059        let (signature_r, decoder) = decoder.decode_field("signature_r")?;
1060        let (signature_s, decoder) = decoder.decode_field("signature_s")?;
1061        let inner_hash = OnceCell::new();
1062        let sender_cache = OnceCell::new();
1063        let cached_canonical = OnceCell::new();
1064
1065        let tx = EIP4844Transaction {
1066            chain_id,
1067            nonce,
1068            max_priority_fee_per_gas,
1069            max_fee_per_gas,
1070            gas,
1071            to,
1072            value,
1073            data,
1074            access_list,
1075            max_fee_per_blob_gas,
1076            blob_versioned_hashes,
1077            signature_y_parity,
1078            signature_r,
1079            signature_s,
1080            inner_hash,
1081            sender_cache,
1082            cached_canonical,
1083        };
1084        Ok((tx, decoder.finish()?))
1085    }
1086}
1087
1088impl RLPDecode for EIP7702Transaction {
1089    fn decode_unfinished(rlp: &[u8]) -> Result<(EIP7702Transaction, &[u8]), RLPDecodeError> {
1090        let decoder = Decoder::new(rlp)?;
1091        let (chain_id, decoder) = decoder.decode_field("chain_id")?;
1092        let (nonce, decoder) = decode_nonce_field(decoder)?;
1093        let (max_priority_fee_per_gas, decoder) =
1094            decoder.decode_field("max_priority_fee_per_gas")?;
1095        let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
1096        let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
1097        let (to, decoder) = decoder.decode_field("to")?;
1098        let (value, decoder) = decoder.decode_field("value")?;
1099        let (data, decoder) = decoder.decode_field("data")?;
1100        let (access_list, decoder) = decoder.decode_field("access_list")?;
1101        let (authorization_list, decoder) = decoder.decode_field("authorization_list")?;
1102        let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
1103        let (signature_r, decoder) = decoder.decode_field("signature_r")?;
1104        let (signature_s, decoder) = decoder.decode_field("signature_s")?;
1105        let inner_hash = OnceCell::new();
1106        let sender_cache = OnceCell::new();
1107        let cached_canonical = OnceCell::new();
1108
1109        let tx = EIP7702Transaction {
1110            chain_id,
1111            nonce,
1112            max_priority_fee_per_gas,
1113            max_fee_per_gas,
1114            gas_limit,
1115            to,
1116            value,
1117            data,
1118            access_list,
1119            authorization_list,
1120            signature_y_parity,
1121            signature_r,
1122            signature_s,
1123            inner_hash,
1124            sender_cache,
1125            cached_canonical,
1126        };
1127        Ok((tx, decoder.finish()?))
1128    }
1129}
1130
1131impl RLPDecode for PrivilegedL2Transaction {
1132    fn decode_unfinished(rlp: &[u8]) -> Result<(PrivilegedL2Transaction, &[u8]), RLPDecodeError> {
1133        let decoder = Decoder::new(rlp)?;
1134        let (chain_id, decoder) = decoder.decode_field("chain_id")?;
1135        let (nonce, decoder) = decode_nonce_field(decoder)?;
1136        let (max_priority_fee_per_gas, decoder) =
1137            decoder.decode_field("max_priority_fee_per_gas")?;
1138        let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
1139        let (gas_limit, decoder) = decoder.decode_field::<u64>("gas_limit")?;
1140        let (to, decoder) = decoder.decode_field("to")?;
1141        let (value, decoder) = decoder.decode_field("value")?;
1142        let (data, decoder) = decoder.decode_field("data")?;
1143        let (access_list, decoder) = decoder.decode_field("access_list")?;
1144        let (from, decoder) = decoder.decode_field("from")?;
1145        let inner_hash = OnceCell::new();
1146        let sender_cache = OnceCell::new();
1147        let cached_canonical = OnceCell::new();
1148
1149        let tx = PrivilegedL2Transaction {
1150            chain_id,
1151            nonce,
1152            max_priority_fee_per_gas,
1153            max_fee_per_gas,
1154            gas_limit,
1155            to,
1156            value,
1157            data,
1158            access_list,
1159            from,
1160            inner_hash,
1161            sender_cache,
1162            cached_canonical,
1163        };
1164        Ok((tx, decoder.finish()?))
1165    }
1166}
1167
1168impl RLPDecode for FeeTokenTransaction {
1169    fn decode_unfinished(rlp: &[u8]) -> Result<(FeeTokenTransaction, &[u8]), RLPDecodeError> {
1170        let decoder = Decoder::new(rlp)?;
1171        let (chain_id, decoder) = decoder.decode_field("chain_id")?;
1172        let (nonce, decoder) = decode_nonce_field(decoder)?;
1173        let (max_priority_fee_per_gas, decoder) =
1174            decoder.decode_field("max_priority_fee_per_gas")?;
1175        let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
1176        let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
1177        let (to, decoder) = decoder.decode_field("to")?;
1178        let (value, decoder) = decoder.decode_field("value")?;
1179        let (data, decoder) = decoder.decode_field("data")?;
1180        let (access_list, decoder) = decoder.decode_field("access_list")?;
1181        let (fee_token, decoder) = decoder.decode_field("fee_token")?;
1182        let (signature_y_parity, decoder) = decoder.decode_field("signature_y_parity")?;
1183        let (signature_r, decoder) = decoder.decode_field("signature_r")?;
1184        let (signature_s, decoder) = decoder.decode_field("signature_s")?;
1185        let inner_hash = OnceCell::new();
1186        let sender_cache = OnceCell::new();
1187        let cached_canonical = OnceCell::new();
1188
1189        let tx = FeeTokenTransaction {
1190            chain_id,
1191            nonce,
1192            max_priority_fee_per_gas,
1193            max_fee_per_gas,
1194            gas_limit,
1195            to,
1196            value,
1197            data,
1198            access_list,
1199            fee_token,
1200            signature_y_parity,
1201            signature_r,
1202            signature_s,
1203            inner_hash,
1204            sender_cache,
1205            cached_canonical,
1206        };
1207        Ok((tx, decoder.finish()?))
1208    }
1209}
1210
1211impl Transaction {
1212    pub fn sender(&self, crypto: &dyn Crypto) -> Result<Address, CryptoError> {
1213        // Frame transactions have explicit sender, no ECDSA recovery
1214        if let Transaction::FrameTransaction(tx) = self {
1215            return Ok(tx.sender);
1216        }
1217        let sender_cache = match self {
1218            Transaction::LegacyTransaction(tx) => &tx.sender_cache,
1219            Transaction::EIP2930Transaction(tx) => &tx.sender_cache,
1220            Transaction::EIP1559Transaction(tx) => &tx.sender_cache,
1221            Transaction::EIP4844Transaction(tx) => &tx.sender_cache,
1222            Transaction::EIP7702Transaction(tx) => &tx.sender_cache,
1223            Transaction::PrivilegedL2Transaction(tx) => &tx.sender_cache,
1224            Transaction::FeeTokenTransaction(tx) => &tx.sender_cache,
1225            Transaction::FrameTransaction(_) => unreachable!(),
1226        };
1227        sender_cache
1228            .get_or_try_init(|| {
1229                let tx_hash = self.hash(crypto);
1230                // Fast path: check process-level signer cache
1231                let mut cache = GLOBAL_SIGNER_CACHE
1232                    .lock()
1233                    .unwrap_or_else(|e| e.into_inner());
1234                if let Some(&addr) = cache.get(&tx_hash) {
1235                    return Ok(addr);
1236                }
1237                drop(cache);
1238                // Slow path: actual secp256k1 recovery
1239                let sender = self.compute_sender(crypto)?;
1240                // Store in global cache for future lookups (LRU evicts oldest on overflow)
1241                GLOBAL_SIGNER_CACHE
1242                    .lock()
1243                    .unwrap_or_else(|e| e.into_inner())
1244                    .put(tx_hash, sender);
1245                Ok(sender)
1246            })
1247            .copied()
1248    }
1249
1250    fn compute_sender(&self, crypto: &dyn Crypto) -> Result<Address, CryptoError> {
1251        let (buf, sig) = match self {
1252            Transaction::LegacyTransaction(tx) => {
1253                let v = u64::try_from(tx.v).map_err(|_| CryptoError::InvalidSignature)?;
1254                // EIP-155: valid legacy `v` is 27/28 (pre-155) or
1255                // {35,36} + chain_id * 2. Any other value is a malformed
1256                // signature and must be rejected rather than coerced into a
1257                // parity bit (which would recover a bogus sender).
1258                let signature_y_parity = match self.chain_id() {
1259                    Some(chain_id) => match v.checked_sub(35 + chain_id * 2) {
1260                        Some(0) => false,
1261                        Some(1) => true,
1262                        _ => return Err(CryptoError::InvalidSignature),
1263                    },
1264                    None => match v {
1265                        27 => false,
1266                        28 => true,
1267                        _ => return Err(CryptoError::InvalidSignature),
1268                    },
1269                };
1270                let mut buf = vec![];
1271                match self.chain_id() {
1272                    None => Encoder::new(&mut buf)
1273                        .encode_field(&tx.nonce)
1274                        .encode_field(&tx.gas_price)
1275                        .encode_field(&tx.gas)
1276                        .encode_field(&tx.to)
1277                        .encode_field(&tx.value)
1278                        .encode_field(&tx.data)
1279                        .finish(),
1280                    Some(chain_id) => Encoder::new(&mut buf)
1281                        .encode_field(&tx.nonce)
1282                        .encode_field(&tx.gas_price)
1283                        .encode_field(&tx.gas)
1284                        .encode_field(&tx.to)
1285                        .encode_field(&tx.value)
1286                        .encode_field(&tx.data)
1287                        .encode_field(&chain_id)
1288                        .encode_field(&0u8)
1289                        .encode_field(&0u8)
1290                        .finish(),
1291                }
1292                let mut sig = [0u8; 65];
1293                sig[..32].copy_from_slice(&tx.r.to_big_endian());
1294                sig[32..64].copy_from_slice(&tx.s.to_big_endian());
1295                sig[64] = signature_y_parity as u8;
1296                (buf, sig)
1297            }
1298            Transaction::EIP2930Transaction(tx) => {
1299                let mut buf = vec![self.tx_type() as u8];
1300                Encoder::new(&mut buf)
1301                    .encode_field(&tx.chain_id)
1302                    .encode_field(&tx.nonce)
1303                    .encode_field(&tx.gas_price)
1304                    .encode_field(&tx.gas_limit)
1305                    .encode_field(&tx.to)
1306                    .encode_field(&tx.value)
1307                    .encode_field(&tx.data)
1308                    .encode_field(&tx.access_list)
1309                    .finish();
1310                let mut sig = [0u8; 65];
1311                sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1312                sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1313                sig[64] = tx.signature_y_parity as u8;
1314                (buf, sig)
1315            }
1316            Transaction::EIP1559Transaction(tx) => {
1317                let mut buf = vec![self.tx_type() as u8];
1318                Encoder::new(&mut buf)
1319                    .encode_field(&tx.chain_id)
1320                    .encode_field(&tx.nonce)
1321                    .encode_field(&tx.max_priority_fee_per_gas)
1322                    .encode_field(&tx.max_fee_per_gas)
1323                    .encode_field(&tx.gas_limit)
1324                    .encode_field(&tx.to)
1325                    .encode_field(&tx.value)
1326                    .encode_field(&tx.data)
1327                    .encode_field(&tx.access_list)
1328                    .finish();
1329                let mut sig = [0u8; 65];
1330                sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1331                sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1332                sig[64] = tx.signature_y_parity as u8;
1333                (buf, sig)
1334            }
1335            Transaction::EIP4844Transaction(tx) => {
1336                let mut buf = vec![self.tx_type() as u8];
1337                Encoder::new(&mut buf)
1338                    .encode_field(&tx.chain_id)
1339                    .encode_field(&tx.nonce)
1340                    .encode_field(&tx.max_priority_fee_per_gas)
1341                    .encode_field(&tx.max_fee_per_gas)
1342                    .encode_field(&tx.gas)
1343                    .encode_field(&tx.to)
1344                    .encode_field(&tx.value)
1345                    .encode_field(&tx.data)
1346                    .encode_field(&tx.access_list)
1347                    .encode_field(&tx.max_fee_per_blob_gas)
1348                    .encode_field(&tx.blob_versioned_hashes)
1349                    .finish();
1350                let mut sig = [0u8; 65];
1351                sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1352                sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1353                sig[64] = tx.signature_y_parity as u8;
1354                (buf, sig)
1355            }
1356            Transaction::EIP7702Transaction(tx) => {
1357                let mut buf = vec![self.tx_type() as u8];
1358                Encoder::new(&mut buf)
1359                    .encode_field(&tx.chain_id)
1360                    .encode_field(&tx.nonce)
1361                    .encode_field(&tx.max_priority_fee_per_gas)
1362                    .encode_field(&tx.max_fee_per_gas)
1363                    .encode_field(&tx.gas_limit)
1364                    .encode_field(&tx.to)
1365                    .encode_field(&tx.value)
1366                    .encode_field(&tx.data)
1367                    .encode_field(&tx.access_list)
1368                    .encode_field(&tx.authorization_list)
1369                    .finish();
1370                let mut sig = [0u8; 65];
1371                sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1372                sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1373                sig[64] = tx.signature_y_parity as u8;
1374                (buf, sig)
1375            }
1376            Transaction::PrivilegedL2Transaction(tx) => return Ok(tx.from),
1377            Transaction::FrameTransaction(tx) => return Ok(tx.sender),
1378            Transaction::FeeTokenTransaction(tx) => {
1379                let mut buf = vec![self.tx_type() as u8];
1380                Encoder::new(&mut buf)
1381                    .encode_field(&tx.chain_id)
1382                    .encode_field(&tx.nonce)
1383                    .encode_field(&tx.max_priority_fee_per_gas)
1384                    .encode_field(&tx.max_fee_per_gas)
1385                    .encode_field(&tx.gas_limit)
1386                    .encode_field(&tx.to)
1387                    .encode_field(&tx.value)
1388                    .encode_field(&tx.data)
1389                    .encode_field(&tx.access_list)
1390                    .encode_field(&tx.fee_token)
1391                    .finish();
1392                let mut sig = [0u8; 65];
1393                sig[..32].copy_from_slice(&tx.signature_r.to_big_endian());
1394                sig[32..64].copy_from_slice(&tx.signature_s.to_big_endian());
1395                sig[64] = tx.signature_y_parity as u8;
1396                (buf, sig)
1397            }
1398        };
1399        let msg = crypto.keccak256(&buf);
1400        crypto.recover_signer(&sig, &msg)
1401    }
1402
1403    pub fn gas_limit(&self) -> u64 {
1404        match self {
1405            Transaction::LegacyTransaction(tx) => tx.gas,
1406            Transaction::EIP2930Transaction(tx) => tx.gas_limit,
1407            Transaction::EIP1559Transaction(tx) => tx.gas_limit,
1408            Transaction::EIP7702Transaction(tx) => tx.gas_limit,
1409            Transaction::EIP4844Transaction(tx) => tx.gas,
1410            Transaction::PrivilegedL2Transaction(tx) => tx.gas_limit,
1411            Transaction::FeeTokenTransaction(tx) => tx.gas_limit,
1412            Transaction::FrameTransaction(tx) => tx.total_gas_limit(),
1413        }
1414    }
1415
1416    //TODO: It's not very correct to return gas price for legacy and eip-2930 txs but return the max fee per gas for the others, make necessary changes for it to be technically correct.
1417    pub fn gas_price(&self) -> U256 {
1418        match self {
1419            Transaction::LegacyTransaction(tx) => tx.gas_price,
1420            Transaction::EIP2930Transaction(tx) => tx.gas_price,
1421            Transaction::EIP1559Transaction(tx) => U256::from(tx.max_fee_per_gas),
1422            Transaction::EIP7702Transaction(tx) => U256::from(tx.max_fee_per_gas),
1423            Transaction::EIP4844Transaction(tx) => U256::from(tx.max_fee_per_gas),
1424            Transaction::PrivilegedL2Transaction(tx) => U256::from(tx.max_fee_per_gas),
1425            Transaction::FeeTokenTransaction(tx) => U256::from(tx.max_fee_per_gas),
1426            Transaction::FrameTransaction(tx) => U256::from(tx.max_fee_per_gas),
1427        }
1428    }
1429
1430    pub fn to(&self) -> TxKind {
1431        match self {
1432            Transaction::LegacyTransaction(tx) => tx.to.clone(),
1433            Transaction::EIP2930Transaction(tx) => tx.to.clone(),
1434            Transaction::EIP1559Transaction(tx) => tx.to.clone(),
1435            Transaction::EIP4844Transaction(tx) => TxKind::Call(tx.to),
1436            Transaction::EIP7702Transaction(tx) => TxKind::Call(tx.to),
1437            Transaction::PrivilegedL2Transaction(tx) => tx.to.clone(),
1438            Transaction::FeeTokenTransaction(tx) => tx.to.clone(),
1439            Transaction::FrameTransaction(tx) => TxKind::Call(tx.sender),
1440        }
1441    }
1442
1443    pub fn value(&self) -> U256 {
1444        match self {
1445            Transaction::LegacyTransaction(tx) => tx.value,
1446            Transaction::EIP2930Transaction(tx) => tx.value,
1447            Transaction::EIP1559Transaction(tx) => tx.value,
1448            Transaction::EIP4844Transaction(tx) => tx.value,
1449            Transaction::EIP7702Transaction(tx) => tx.value,
1450            Transaction::PrivilegedL2Transaction(tx) => tx.value,
1451            Transaction::FeeTokenTransaction(tx) => tx.value,
1452            Transaction::FrameTransaction(_) => U256::zero(),
1453        }
1454    }
1455
1456    pub fn max_priority_fee(&self) -> Option<u64> {
1457        match self {
1458            Transaction::LegacyTransaction(_tx) => None,
1459            Transaction::EIP2930Transaction(_tx) => None,
1460            Transaction::EIP1559Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1461            Transaction::EIP4844Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1462            Transaction::EIP7702Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1463            Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1464            Transaction::FeeTokenTransaction(tx) => Some(tx.max_priority_fee_per_gas),
1465            Transaction::FrameTransaction(tx) => Some(tx.max_priority_fee_per_gas),
1466        }
1467    }
1468
1469    pub fn chain_id(&self) -> Option<u64> {
1470        match self {
1471            Transaction::LegacyTransaction(tx) => derive_legacy_chain_id(tx.v),
1472            Transaction::EIP2930Transaction(tx) => Some(tx.chain_id),
1473            Transaction::EIP1559Transaction(tx) => Some(tx.chain_id),
1474            Transaction::EIP4844Transaction(tx) => Some(tx.chain_id),
1475            Transaction::EIP7702Transaction(tx) => Some(tx.chain_id),
1476            Transaction::PrivilegedL2Transaction(tx) => Some(tx.chain_id),
1477            Transaction::FeeTokenTransaction(tx) => Some(tx.chain_id),
1478            Transaction::FrameTransaction(tx) => Some(tx.chain_id),
1479        }
1480    }
1481
1482    pub fn access_list(&self) -> &AccessList {
1483        static EMPTY_ACCESS_LIST: AccessList = Vec::new();
1484        match self {
1485            Transaction::LegacyTransaction(_tx) => &EMPTY_ACCESS_LIST,
1486            Transaction::EIP2930Transaction(tx) => &tx.access_list,
1487            Transaction::EIP1559Transaction(tx) => &tx.access_list,
1488            Transaction::EIP4844Transaction(tx) => &tx.access_list,
1489            Transaction::EIP7702Transaction(tx) => &tx.access_list,
1490            Transaction::PrivilegedL2Transaction(tx) => &tx.access_list,
1491            Transaction::FeeTokenTransaction(tx) => &tx.access_list,
1492            Transaction::FrameTransaction(_) => &EMPTY_ACCESS_LIST,
1493        }
1494    }
1495    pub fn authorization_list(&self) -> Option<&AuthorizationList> {
1496        match self {
1497            Transaction::LegacyTransaction(_) => None,
1498            Transaction::EIP2930Transaction(_) => None,
1499            Transaction::EIP1559Transaction(_) => None,
1500            Transaction::EIP4844Transaction(_) => None,
1501            Transaction::EIP7702Transaction(tx) => Some(&tx.authorization_list),
1502            Transaction::PrivilegedL2Transaction(_) => None,
1503            Transaction::FeeTokenTransaction(_) => None,
1504            Transaction::FrameTransaction(_) => None,
1505        }
1506    }
1507
1508    pub fn nonce(&self) -> u64 {
1509        match self {
1510            Transaction::LegacyTransaction(tx) => tx.nonce,
1511            Transaction::EIP2930Transaction(tx) => tx.nonce,
1512            Transaction::EIP1559Transaction(tx) => tx.nonce,
1513            Transaction::EIP4844Transaction(tx) => tx.nonce,
1514            Transaction::EIP7702Transaction(tx) => tx.nonce,
1515            Transaction::PrivilegedL2Transaction(tx) => tx.nonce,
1516            Transaction::FeeTokenTransaction(tx) => tx.nonce,
1517            Transaction::FrameTransaction(tx) => tx.nonce,
1518        }
1519    }
1520
1521    pub fn data(&self) -> &Bytes {
1522        match self {
1523            Transaction::LegacyTransaction(tx) => &tx.data,
1524            Transaction::EIP2930Transaction(tx) => &tx.data,
1525            Transaction::EIP1559Transaction(tx) => &tx.data,
1526            Transaction::EIP4844Transaction(tx) => &tx.data,
1527            Transaction::EIP7702Transaction(tx) => &tx.data,
1528            Transaction::PrivilegedL2Transaction(tx) => &tx.data,
1529            Transaction::FeeTokenTransaction(tx) => &tx.data,
1530            Transaction::FrameTransaction(_) => {
1531                static EMPTY_DATA: Bytes = Bytes::new();
1532                &EMPTY_DATA
1533            }
1534        }
1535    }
1536
1537    pub fn blob_versioned_hashes(&self) -> Vec<H256> {
1538        match self {
1539            Transaction::LegacyTransaction(_) => Vec::new(),
1540            Transaction::EIP2930Transaction(_) => Vec::new(),
1541            Transaction::EIP1559Transaction(_) => Vec::new(),
1542            Transaction::EIP4844Transaction(tx) => tx.blob_versioned_hashes.clone(),
1543            Transaction::EIP7702Transaction(_) => Vec::new(),
1544            Transaction::PrivilegedL2Transaction(_) => Vec::new(),
1545            Transaction::FeeTokenTransaction(_) => Vec::new(),
1546            Transaction::FrameTransaction(tx) => tx.blob_versioned_hashes.clone(),
1547        }
1548    }
1549
1550    pub fn max_fee_per_blob_gas(&self) -> Option<U256> {
1551        match self {
1552            Transaction::LegacyTransaction(_) => None,
1553            Transaction::EIP2930Transaction(_) => None,
1554            Transaction::EIP1559Transaction(_) => None,
1555            Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_blob_gas),
1556            Transaction::EIP7702Transaction(_) => None,
1557            Transaction::PrivilegedL2Transaction(_) => None,
1558            Transaction::FeeTokenTransaction(_) => None,
1559            Transaction::FrameTransaction(tx) => {
1560                if tx.blob_versioned_hashes.is_empty() {
1561                    None
1562                } else {
1563                    Some(tx.max_fee_per_blob_gas)
1564                }
1565            }
1566        }
1567    }
1568
1569    pub fn is_contract_creation(&self) -> bool {
1570        match &self {
1571            Transaction::LegacyTransaction(t) => matches!(t.to, TxKind::Create),
1572            Transaction::EIP2930Transaction(t) => matches!(t.to, TxKind::Create),
1573            Transaction::EIP1559Transaction(t) => matches!(t.to, TxKind::Create),
1574            Transaction::EIP4844Transaction(_) => false,
1575            Transaction::EIP7702Transaction(_) => false,
1576            Transaction::PrivilegedL2Transaction(t) => matches!(t.to, TxKind::Create),
1577            Transaction::FeeTokenTransaction(t) => matches!(t.to, TxKind::Create),
1578            Transaction::FrameTransaction(_) => false,
1579        }
1580    }
1581
1582    pub fn is_privileged(&self) -> bool {
1583        matches!(self, Transaction::PrivilegedL2Transaction(_))
1584    }
1585
1586    pub fn max_fee_per_gas(&self) -> Option<u64> {
1587        match self {
1588            Transaction::LegacyTransaction(_tx) => None,
1589            Transaction::EIP2930Transaction(_tx) => None,
1590            Transaction::EIP1559Transaction(tx) => Some(tx.max_fee_per_gas),
1591            Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_gas),
1592            Transaction::EIP7702Transaction(tx) => Some(tx.max_fee_per_gas),
1593            Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_fee_per_gas),
1594            Transaction::FeeTokenTransaction(tx) => Some(tx.max_fee_per_gas),
1595            Transaction::FrameTransaction(tx) => Some(tx.max_fee_per_gas),
1596        }
1597    }
1598
1599    fn compute_hash(&self, crypto: &dyn Crypto) -> H256 {
1600        if let Transaction::PrivilegedL2Transaction(tx) = self {
1601            return tx.get_privileged_hash().unwrap_or_default();
1602        }
1603        H256(crypto.keccak256(&self.encode_canonical_to_vec()))
1604    }
1605
1606    pub fn hash(&self, crypto: &dyn Crypto) -> H256 {
1607        let inner_hash = match self {
1608            Transaction::LegacyTransaction(tx) => &tx.inner_hash,
1609            Transaction::EIP2930Transaction(tx) => &tx.inner_hash,
1610            Transaction::EIP1559Transaction(tx) => &tx.inner_hash,
1611            Transaction::EIP4844Transaction(tx) => &tx.inner_hash,
1612            Transaction::EIP7702Transaction(tx) => &tx.inner_hash,
1613            Transaction::PrivilegedL2Transaction(tx) => &tx.inner_hash,
1614            Transaction::FeeTokenTransaction(tx) => &tx.inner_hash,
1615            Transaction::FrameTransaction(tx) => &tx.inner_hash,
1616        };
1617
1618        *inner_hash.get_or_init(|| self.compute_hash(crypto))
1619    }
1620
1621    pub fn gas_tip_cap(&self) -> U256 {
1622        self.max_priority_fee()
1623            .map(U256::from)
1624            .unwrap_or_else(|| self.gas_price())
1625    }
1626
1627    pub fn gas_fee_cap(&self) -> U256 {
1628        self.max_fee_per_gas()
1629            .map(U256::from)
1630            .unwrap_or_else(|| self.gas_price())
1631    }
1632
1633    /// Returns the effective tip per gas for this transaction.
1634    /// Returns `None` if the transaction's fee cap is below the base fee (i.e. the
1635    /// transaction cannot pay for its inclusion).
1636    pub fn effective_gas_tip(&self, base_fee: Option<u64>) -> Option<U256> {
1637        let tip_cap = self.gas_tip_cap();
1638        let Some(base_fee) = base_fee else {
1639            return Some(tip_cap);
1640        };
1641        let base_fee = U256::from(base_fee);
1642        let fee_cap = self.gas_fee_cap();
1643        let tip = fee_cap.checked_sub(base_fee)?;
1644        Some(min(tip, tip_cap))
1645    }
1646
1647    /// Returns whether the transaction is replay-protected.
1648    /// For more information check out [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
1649    pub fn protected(&self) -> bool {
1650        match self {
1651            Transaction::LegacyTransaction(tx) if tx.v.bits() <= 8 => {
1652                let v = tx.v.low_u64();
1653                v != 27 && v != 28 && v != 1 && v != 0
1654            }
1655            _ => true,
1656        }
1657    }
1658}
1659
1660fn derive_legacy_chain_id(v: U256) -> Option<u64> {
1661    let v = u64::try_from(v).ok()?;
1662    // EIP-155 encodes the chain id as `v = chain_id * 2 + 35` (or 36), so any
1663    // replay-protected `v` is >= 35. Pre-EIP-155 txs use v=27/28, and malformed
1664    // signatures (e.g. v=0 from an unsigned IL transaction) are < 35 too; none
1665    // carry a chain id. Guard the subtraction to avoid an underflow panic.
1666    if v < 35 { None } else { Some((v - 35) / 2) }
1667}
1668
1669impl TxType {
1670    pub fn from_u8(value: u8) -> Option<Self> {
1671        match value {
1672            0x00 => Some(Self::Legacy),
1673            0x01 => Some(Self::EIP2930),
1674            0x02 => Some(Self::EIP1559),
1675            0x03 => Some(Self::EIP4844),
1676            0x04 => Some(Self::EIP7702),
1677            0x06 => Some(Self::Frame),
1678            0x7d => Some(Self::FeeToken),
1679            0x7e => Some(Self::Privileged),
1680            _ => None,
1681        }
1682    }
1683
1684    /// The typed-envelope form of this transaction type, or `None` for [`TxType::Legacy`]
1685    /// (a bare RLP list, not a `0x{type} || payload` envelope). Exhaustive over `TxType`
1686    /// with no wildcard, so adding a new variant won't compile until it's classified here —
1687    /// keeping envelope decoding in step with the transaction-type set.
1688    pub fn as_envelope(self) -> Option<EnvelopeTxType> {
1689        match self {
1690            TxType::EIP2930 => Some(EnvelopeTxType::EIP2930),
1691            TxType::EIP1559 => Some(EnvelopeTxType::EIP1559),
1692            TxType::EIP4844 => Some(EnvelopeTxType::EIP4844),
1693            TxType::EIP7702 => Some(EnvelopeTxType::EIP7702),
1694            TxType::Frame => Some(EnvelopeTxType::Frame),
1695            TxType::FeeToken => Some(EnvelopeTxType::FeeToken),
1696            TxType::Privileged => Some(EnvelopeTxType::Privileged),
1697            TxType::Legacy => None,
1698        }
1699    }
1700
1701    /// Transaction types that only exist on the L2 rollup and must never appear in
1702    /// an L1 block (`FeeToken` 0x7d, `Privileged` 0x7e). Privileged transactions in
1703    /// particular take their sender from an unsigned, caller-chosen `from` field.
1704    ///
1705    /// This match is intentionally exhaustive (no wildcard arm): adding a new
1706    /// `TxType` variant will not compile until it is explicitly classified here,
1707    /// so an L2-only type can never be silently accepted on L1 by omission.
1708    pub fn is_l2_only(self) -> bool {
1709        match self {
1710            Self::Legacy
1711            | Self::EIP2930
1712            | Self::EIP1559
1713            | Self::EIP4844
1714            | Self::EIP7702
1715            | Self::Frame => false,
1716            Self::FeeToken | Self::Privileged => true,
1717        }
1718    }
1719}
1720
1721impl PrivilegedL2Transaction {
1722    /// Returns the formatted hash of the privileged transaction,
1723    /// or None if the transaction is not a privileged transaction.
1724    /// The hash is computed as keccak256(chain_id || from || to || transaction_id  || value || gas_limit || keccak256(calldata))
1725    pub fn get_privileged_hash(&self) -> Option<H256> {
1726        // Should this function be changed?
1727        let to = match self.to {
1728            TxKind::Call(to) => to,
1729            _ => return None,
1730        };
1731
1732        let value = self.value.to_big_endian();
1733
1734        // The nonce should be a U256,
1735        // in solidity the transactionId is a U256.
1736        let u256_nonce = U256::from(self.nonce);
1737        let nonce = u256_nonce.to_big_endian();
1738
1739        Some(crate::utils::keccak(
1740            [
1741                U256::from(self.chain_id).to_big_endian().as_ref(),
1742                self.from.as_bytes(),
1743                to.as_bytes(),
1744                &nonce,
1745                &value,
1746                &U256::from(self.gas_limit).to_big_endian(),
1747                keccak(&self.data).as_bytes(),
1748            ]
1749            .concat(),
1750        ))
1751    }
1752}
1753
1754#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1755pub struct FeeTokenTransaction {
1756    pub chain_id: u64,
1757    pub nonce: u64,
1758    pub max_priority_fee_per_gas: u64,
1759    pub max_fee_per_gas: u64,
1760    pub gas_limit: u64,
1761    pub to: TxKind,
1762    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1763    pub value: U256,
1764    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1765    pub data: Bytes,
1766    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
1767    pub access_list: AccessList,
1768    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
1769    pub fee_token: Address,
1770    pub signature_y_parity: bool,
1771    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1772    pub signature_r: U256,
1773    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1774    pub signature_s: U256,
1775    #[rkyv(with=rkyv::with::Skip)]
1776    pub inner_hash: OnceCell<H256>,
1777    #[rkyv(with=rkyv::with::Skip)]
1778    pub sender_cache: OnceCell<Address>,
1779    #[rkyv(with=rkyv::with::Skip)]
1780    pub cached_canonical: OnceCell<Vec<u8>>,
1781}
1782
1783/// EIP-8141 Frame Transaction mode
1784#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1785#[repr(u8)]
1786pub enum FrameMode {
1787    #[default]
1788    Default = 0,
1789    Verify = 1,
1790    Sender = 2,
1791}
1792
1793impl FrameMode {
1794    /// Convert from the lower 8 bits of the mode field.
1795    /// Returns None for reserved values (3-255).
1796    pub fn from_u8(val: u8) -> Option<Self> {
1797        match val {
1798            0 => Some(FrameMode::Default),
1799            1 => Some(FrameMode::Verify),
1800            2 => Some(FrameMode::Sender),
1801            _ => None,
1802        }
1803    }
1804}
1805
1806impl From<FrameMode> for u8 {
1807    fn from(mode: FrameMode) -> u8 {
1808        match mode {
1809            FrameMode::Default => 0,
1810            FrameMode::Verify => 1,
1811            FrameMode::Sender => 2,
1812        }
1813    }
1814}
1815
1816/// EIP-8141 Frame: a single execution step within a frame transaction.
1817///
1818/// `mode` is the execution mode (0=DEFAULT, 1=VERIFY, 2=SENDER; 3-255 reserved).
1819/// `flags` bits: 0-1 = APPROVE scope restriction, 2 = atomic batch flag (valid
1820/// on DEFAULT and SENDER frames only), 3-7 reserved (must be zero).
1821#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1822pub struct Frame {
1823    pub mode: u8,
1824    pub flags: u8,
1825    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H160Wrapper>)]
1826    pub target: Option<Address>,
1827    pub gas_limit: u64,
1828    // Per EIP-8141 the frame is a 6-tuple [mode, flags, target, gas_limit, value, data].
1829    // Only SENDER frames may carry a non-zero value; see
1830    // `validate_static_constraints`.
1831    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1832    pub value: U256,
1833    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1834    pub data: Bytes,
1835}
1836
1837impl Frame {
1838    /// Extract the execution mode from the `mode` field.
1839    pub fn execution_mode(&self) -> FrameMode {
1840        FrameMode::from_u8(self.mode).unwrap_or_default()
1841    }
1842
1843    /// Extract the APPROVE scope restriction from bits 0-1 of `flags`.
1844    pub fn scope_restriction(&self) -> u8 {
1845        self.flags & 0x03
1846    }
1847
1848    /// Check if the atomic batch flag (bit 2 of `flags`) is set.
1849    pub fn is_atomic_batch(&self) -> bool {
1850        (self.flags >> 2) & 1 == 1
1851    }
1852
1853    /// An expiry verifier frame is a VERIFY frame targeting EXPIRY_VERIFIER
1854    /// (EIP-8141).
1855    pub fn is_expiry_verifier(&self) -> bool {
1856        self.execution_mode() == FrameMode::Verify
1857            && self.target == Some(frame_tx_expiry_verifier())
1858    }
1859}
1860
1861impl RLPEncode for Frame {
1862    fn encode(&self, buf: &mut dyn bytes::BufMut) {
1863        // target: encode as address or RLP null (empty bytes) for None
1864        let target_kind = match self.target {
1865            Some(addr) => TxKind::Call(addr),
1866            None => TxKind::Create, // RLP_NULL encodes "no target" → sender
1867        };
1868        Encoder::new(buf)
1869            .encode_field(&(self.mode as u64))
1870            .encode_field(&(self.flags as u64))
1871            .encode_field(&target_kind)
1872            .encode_field(&self.gas_limit)
1873            .encode_field(&self.value)
1874            .encode_field(&self.data)
1875            .finish();
1876    }
1877}
1878
1879impl RLPDecode for Frame {
1880    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
1881        let decoder = Decoder::new(rlp)?;
1882        let (mode_u64, decoder): (u64, _) = decoder.decode_field("mode")?;
1883        let mode = u8::try_from(mode_u64)
1884            .map_err(|_| RLPDecodeError::Custom(format!("Frame mode too large: {mode_u64}")))?;
1885        let (flags_u64, decoder): (u64, _) = decoder.decode_field("flags")?;
1886        let flags = u8::try_from(flags_u64)
1887            .map_err(|_| RLPDecodeError::Custom(format!("Frame flags too large: {flags_u64}")))?;
1888        let (target_kind, decoder): (TxKind, _) = decoder.decode_field("target")?;
1889        let target = match target_kind {
1890            TxKind::Call(addr) => Some(addr),
1891            TxKind::Create => None,
1892        };
1893        let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
1894        let (value, decoder): (U256, _) = decoder.decode_field("value")?;
1895        let (data, decoder) = decoder.decode_field("data")?;
1896        let frame = Frame {
1897            mode,
1898            flags,
1899            target,
1900            gas_limit,
1901            value,
1902            data,
1903        };
1904        Ok((frame, decoder.finish()?))
1905    }
1906}
1907
1908/// EIP-8141 transaction signature. RLP: `[scheme, signer, msg, signature]`.
1909/// `scheme`: 0 = ARBITRARY (`signer` MUST be empty; `signature` is arbitrary bytes),
1910/// 1 = SECP256K1 (sig = v||r||s, 65 bytes), 2 = P256 (sig = r||s||qx||qy, 128 bytes).
1911/// `signer`: `None` (empty) is required for ARBITRARY and allowed for SECP256K1/P256
1912/// (empty ⇒ the resolved signer is `tx.sender`, for introspection too); `Some(addr)` pins it.
1913/// `msg`: empty = signs compute_sig_hash(tx); 32 bytes = signs that explicit digest.
1914/// Raw `signature` bytes are intentionally not EVM-introspectable.
1915#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1916pub struct FrameSignature {
1917    pub scheme: u8,
1918    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H160Wrapper>)]
1919    pub signer: Option<Address>,
1920    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1921    pub msg: Bytes,
1922    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1923    pub signature: Bytes,
1924}
1925
1926impl RLPEncode for FrameSignature {
1927    fn encode(&self, buf: &mut dyn bytes::BufMut) {
1928        // `signer` encodes as an address or RLP null (empty) for `None`, mirroring
1929        // `Frame.target`; an empty signer is required for ARBITRARY signatures.
1930        let signer_kind = match self.signer {
1931            Some(addr) => TxKind::Call(addr),
1932            None => TxKind::Create,
1933        };
1934        Encoder::new(buf)
1935            .encode_field(&self.scheme)
1936            .encode_field(&signer_kind)
1937            .encode_field(&self.msg)
1938            .encode_field(&self.signature)
1939            .finish();
1940    }
1941}
1942
1943impl RLPDecode for FrameSignature {
1944    fn decode_unfinished(rlp: &[u8]) -> Result<(FrameSignature, &[u8]), RLPDecodeError> {
1945        let decoder = Decoder::new(rlp)?;
1946        let (scheme, decoder) = decoder.decode_field("scheme")?;
1947        let (signer_kind, decoder): (TxKind, _) = decoder.decode_field("signer")?;
1948        let signer = match signer_kind {
1949            TxKind::Call(addr) => Some(addr),
1950            TxKind::Create => None,
1951        };
1952        let (msg, decoder) = decoder.decode_field("msg")?;
1953        let (signature, decoder) = decoder.decode_field("signature")?;
1954        Ok((
1955            FrameSignature {
1956                scheme,
1957                signer,
1958                msg,
1959                signature,
1960            },
1961            decoder.finish()?,
1962        ))
1963    }
1964}
1965
1966/// EIP-8141 Frame Transaction
1967/// A transaction whose validity and gas payment are defined abstractly via frames.
1968/// No ECDSA signature — sender is explicit. Authentication happens via APPROVE opcode.
1969#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1970pub struct FrameTransaction {
1971    pub chain_id: u64,
1972    pub nonce: u64,
1973    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
1974    pub sender: Address,
1975    pub frames: Vec<Frame>,
1976    /// EIP-8141 outer signature list. Validated
1977    /// before any frame executes; referenced by VERIFY frames and SIGPARAM.
1978    pub signatures: Vec<FrameSignature>,
1979    pub max_priority_fee_per_gas: u64,
1980    pub max_fee_per_gas: u64,
1981    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1982    pub max_fee_per_blob_gas: U256,
1983    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H256Wrapper>)]
1984    pub blob_versioned_hashes: Vec<H256>,
1985    #[rkyv(with=rkyv::with::Skip)]
1986    pub inner_hash: OnceCell<H256>,
1987    #[rkyv(with=rkyv::with::Skip)]
1988    pub cached_canonical: OnceCell<Vec<u8>>,
1989}
1990
1991/// Intrinsic gas cost for frame transactions (EIP-8141)
1992pub const FRAME_TX_INTRINSIC_COST: u64 = 15000;
1993/// Per-frame cost (EIP-8141): CALL context overhead (100) + G_log (375)
1994pub const FRAME_TX_PER_FRAME_COST: u64 = 475;
1995/// ENTRY_POINT address used as caller for DEFAULT/VERIFY frames per EIP-8141.
1996pub const FRAME_TX_ENTRY_POINT_U64: u64 = 0xaa;
1997/// Maximum number of frames allowed per EIP-8141 frame transaction.
1998pub const FRAME_TX_MAX_FRAMES: usize = 64;
1999/// EIP-7623 `STANDARD_TOKEN_COST`, and the EIP-7976 floor per token. Frame
2000/// transactions exist only from Hegota onward, which is after Amsterdam, so the
2001/// raised EIP-7976 floor always applies and neither needs a fork parameter.
2002const FRAME_TX_STANDARD_TOKEN_COST: u64 = 4;
2003const FRAME_TX_TOTAL_COST_FLOOR_PER_TOKEN: u64 = 16;
2004/// EIP-8141 signature schemes: ARBITRARY=0, SECP256K1=1, P256=2.
2005pub const FRAME_SIG_SCHEME_ARBITRARY: u8 = 0;
2006pub const FRAME_SIG_SCHEME_SECP256K1: u8 = 1;
2007pub const FRAME_SIG_SCHEME_P256: u8 = 2;
2008/// EIP-8141 §Mempool `MAX_VERIFY_GAS`: the maximum gas
2009/// a public-mempool node should expend validating signatures and simulating the
2010/// validation prefix. Signature validation counts against this budget (rule #6),
2011/// so a frame tx whose `signature_verification_cost()` alone exceeds it can never
2012/// satisfy the prefix budget and must be rejected at admission.
2013pub const FRAME_TX_MAX_VERIFY_GAS: u64 = 100_000;
2014/// EIP-8141 APPROVE scope-restriction values (bits 0-1 of `Frame.flags`).
2015/// Used by VERIFY and PAY frames to declare which capabilities they grant.
2016pub const APPROVE_PAYMENT: u8 = 0x1;
2017pub const APPROVE_EXECUTION: u8 = 0x2;
2018pub const APPROVE_EXECUTION_AND_PAYMENT: u8 = 0x3;
2019/// Maximum number of pending frame txs using a non-canonical paymaster per
2020/// paymaster address. Per OQ1, all paymasters are currently non-canonical
2021/// (FRAME_CANONICAL_PAYMASTER_CODE_HASH is unresolved in the draft EIP), so
2022/// this de-facto limits sponsored frame txs to 1 per paymaster in the pool.
2023pub const FRAME_TX_MAX_PENDING_NONCANONICAL_PAYMASTER: u8 = 1;
2024
2025/// Returns the ENTRY_POINT `Address` (0x…00aa) used as caller for
2026/// DEFAULT/VERIFY frames per EIP-8141.
2027pub fn frame_tx_entry_point() -> Address {
2028    Address::from_low_u64_be(FRAME_TX_ENTRY_POINT_U64)
2029}
2030
2031/// EXPIRY_VERIFIER predeploy address (EIP-8141).
2032pub const FRAME_TX_EXPIRY_VERIFIER_U64: u64 = 0x8141;
2033/// Required `data` length for an expiry verifier frame (8-byte BE deadline).
2034pub const FRAME_TX_EXPIRY_DATA_LENGTH: usize = 8;
2035
2036/// Returns the EXPIRY_VERIFIER `Address` (0x…8141) per EIP-8141.
2037pub fn frame_tx_expiry_verifier() -> Address {
2038    Address::from_low_u64_be(FRAME_TX_EXPIRY_VERIFIER_U64)
2039}
2040
2041impl FrameTransaction {
2042    /// Canonical signature hash (EIP-8141): the raw
2043    /// `signature` bytes of every signature with empty `msg` are elided (a
2044    /// signature over this hash cannot commit to its own bytes). Frame data is
2045    /// NO LONGER elided — it is fully covered. Signatures with an explicit
2046    /// 32-byte `msg` keep their bytes (they sign that digest, not this hash).
2047    pub fn compute_sig_hash(&self) -> H256 {
2048        let mut buf = vec![TxType::Frame as u8];
2049        let elided_signatures: Vec<FrameSignature> = self
2050            .signatures
2051            .iter()
2052            .map(|s| {
2053                if s.msg.is_empty() {
2054                    FrameSignature {
2055                        scheme: s.scheme,
2056                        signer: s.signer,
2057                        msg: s.msg.clone(),
2058                        signature: Bytes::new(),
2059                    }
2060                } else {
2061                    s.clone()
2062                }
2063            })
2064            .collect();
2065        // RLP-encode the tx with elided signature bytes, frames verbatim.
2066        Encoder::new(&mut buf)
2067            .encode_field(&self.chain_id)
2068            .encode_field(&self.nonce)
2069            .encode_field(&self.sender)
2070            .encode_field(&self.frames)
2071            .encode_field(&elided_signatures)
2072            .encode_field(&self.max_priority_fee_per_gas)
2073            .encode_field(&self.max_fee_per_gas)
2074            .encode_field(&self.max_fee_per_blob_gas)
2075            .encode_field(&self.blob_versioned_hashes)
2076            .finish();
2077        keccak(&buf)
2078    }
2079
2080    /// Per EIP-8141: 100 gas per ARBITRARY signature, 2800 per SECP256K1, 6700
2081    /// per P256. Unknown schemes are rejected by static validation, so they are
2082    /// treated as 0 here (validation runs first).
2083    pub fn signature_verification_cost(&self) -> u64 {
2084        self.signatures
2085            .iter()
2086            .map(|s| match s.scheme {
2087                FRAME_SIG_SCHEME_ARBITRARY => 100u64,
2088                FRAME_SIG_SCHEME_SECP256K1 => 2800u64,
2089                FRAME_SIG_SCHEME_P256 => 6700u64,
2090                _ => 0,
2091            })
2092            .sum()
2093    }
2094
2095    /// Iterate the EIP-8141 data fields the calldata cost is charged over:
2096    /// each frame's `data`, and each signature's `signer`, `msg` and `signature`.
2097    /// The RLP framing and the remaining scalar fields are not included.
2098    fn data_fields(&self) -> impl Iterator<Item = &[u8]> {
2099        const EMPTY: &[u8] = &[];
2100        self.frames
2101            .iter()
2102            .map(|f| f.data.as_ref())
2103            .chain(self.signatures.iter().flat_map(|s| {
2104                [
2105                    s.signer.as_ref().map_or(EMPTY, |a| a.as_bytes()),
2106                    s.msg.as_ref(),
2107                    s.signature.as_ref(),
2108                ]
2109            }))
2110    }
2111
2112    /// EIP-7623 calldata cost over the frame and signature data: 4 gas per zero
2113    /// byte, 16 per non-zero byte.
2114    pub fn data_cost(&self) -> u64 {
2115        self.data_fields().flatten().fold(0u64, |acc, byte| {
2116            acc.saturating_add(if *byte == 0 { 4 } else { 16 })
2117        })
2118    }
2119
2120    /// EIP-7623 token count over the frame and signature data, used for the
2121    /// calldata floor. Frame transactions exist only from Hegota onward, which is
2122    /// after Amsterdam, so EIP-7976's unweighted count (every byte costs
2123    /// `STANDARD_TOKEN_COST`) always applies.
2124    pub fn calldata_tokens(&self) -> u64 {
2125        self.data_fields()
2126            .fold(0u64, |acc, field| acc.saturating_add(field.len() as u64))
2127            .saturating_mul(FRAME_TX_STANDARD_TOKEN_COST)
2128    }
2129
2130    /// The EIP-7623 calldata floor: the least the frame and signature data may
2131    /// cost, whatever execution actually consumes.
2132    pub fn calldata_floor_gas(&self) -> u64 {
2133        self.calldata_tokens()
2134            .saturating_mul(FRAME_TX_TOTAL_COST_FLOOR_PER_TOKEN)
2135    }
2136
2137    /// The mandatory costs, always charged in full: the intrinsic cost, the
2138    /// per-frame cost, and signature verification.
2139    pub fn mandatory_gas(&self) -> u64 {
2140        FRAME_TX_INTRINSIC_COST
2141            .saturating_add((self.frames.len() as u64).saturating_mul(FRAME_TX_PER_FRAME_COST))
2142            .saturating_add(self.signature_verification_cost())
2143    }
2144
2145    /// Compute total gas limit: mandatory costs + data cost + sum of frame gas
2146    /// limits.
2147    pub fn total_gas_limit(&self) -> u64 {
2148        self.mandatory_gas()
2149            .saturating_add(self.data_cost())
2150            .saturating_add(
2151                self.frames
2152                    .iter()
2153                    .map(|f| f.gas_limit)
2154                    .fold(0u64, |acc, g| acc.saturating_add(g)),
2155            )
2156    }
2157
2158    /// The expiry deadline (8-byte big-endian) of this transaction's expiry
2159    /// verifier frame, if one exists with well-formed data.
2160    pub fn expiry_deadline(&self) -> Option<u64> {
2161        self.frames
2162            .iter()
2163            .find(|f| f.is_expiry_verifier())
2164            .and_then(|f| {
2165                let bytes: [u8; FRAME_TX_EXPIRY_DATA_LENGTH] = f.data.as_ref().try_into().ok()?;
2166                Some(u64::from_be_bytes(bytes))
2167            })
2168    }
2169
2170    /// Validate static constraints per EIP-8141 spec.
2171    /// Returns an error string if the transaction is invalid.
2172    pub fn validate_static_constraints(&self) -> Result<(), String> {
2173        // tx.sender != zero address
2174        if self.sender == Address::zero() {
2175            return Err("tx.sender must not be zero address".to_string());
2176        }
2177        if self.frames.is_empty() || self.frames.len() > FRAME_TX_MAX_FRAMES {
2178            return Err(format!(
2179                "Frame count must be between 1 and {FRAME_TX_MAX_FRAMES}"
2180            ));
2181        }
2182        // Per EIP-8141, every versioned hash must carry the EIP-4844 KZG version
2183        // byte, and a transaction carrying no blobs must not name a blob fee.
2184        for (i, hash) in self.blob_versioned_hashes.iter().enumerate() {
2185            if hash.0.first() != Some(&VERSIONED_HASH_VERSION_KZG) {
2186                return Err(format!("Blob versioned hash {i}: wrong version byte"));
2187            }
2188        }
2189        if self.blob_versioned_hashes.is_empty() && !self.max_fee_per_blob_gas.is_zero() {
2190            return Err(
2191                "max_fee_per_blob_gas must be zero when the transaction carries no blobs"
2192                    .to_string(),
2193            );
2194        }
2195        // EIP-8141 signature list validation: scheme must be a known value; msg
2196        // must be empty or a non-zero 32-byte digest.
2197        for (i, sig) in self.signatures.iter().enumerate() {
2198            match sig.scheme {
2199                // ARBITRARY (0) carries no recoverable signer; per EIP-8141 the
2200                // signer MUST be empty.
2201                FRAME_SIG_SCHEME_ARBITRARY => {
2202                    if sig.signer.is_some() {
2203                        return Err(format!(
2204                            "Signature {i}: ARBITRARY signatures must not name a signer"
2205                        ));
2206                    }
2207                }
2208                FRAME_SIG_SCHEME_SECP256K1 | FRAME_SIG_SCHEME_P256 => {}
2209                other => {
2210                    return Err(format!("Signature {i}: unsupported scheme {other}"));
2211                }
2212            }
2213            match sig.msg.len() {
2214                0 => {}
2215                32 => {
2216                    if sig.msg.iter().all(|&b| b == 0) {
2217                        return Err(format!(
2218                            "Signature {i}: explicit msg must not be zero digest"
2219                        ));
2220                    }
2221                }
2222                other => {
2223                    return Err(format!(
2224                        "Signature {i}: msg must be empty or 32 bytes, got {other}"
2225                    ));
2226                }
2227            }
2228        }
2229        // Tracked as u128 so the running addition itself cannot overflow; the
2230        // bound below rejects tx-level totals that don't fit in signed i64.
2231        let mut total_frame_gas: u128 = 0;
2232        let mut expiry_frame_count: usize = 0;
2233        for (i, frame) in self.frames.iter().enumerate() {
2234            // Reject reserved execution modes (3-255)
2235            if frame.mode >= 3 {
2236                return Err(format!("Frame {i}: reserved execution mode {}", frame.mode));
2237            }
2238            // Reserved flag bits 3-7 must be zero
2239            if frame.flags >= 8 {
2240                return Err(format!(
2241                    "Frame {i}: reserved flag bits must be zero (flags={:#04x})",
2242                    frame.flags
2243                ));
2244            }
2245            // Expiry verifier frames (EIP-8141): VERIFY
2246            // frames targeting EXPIRY_VERIFIER must have flags == 0, value == 0
2247            // (already enforced by the non-SENDER value rule below), and exactly
2248            // EXPIRY_DATA_LENGTH bytes of data; at most one per transaction.
2249            if frame.is_expiry_verifier() {
2250                expiry_frame_count = expiry_frame_count.saturating_add(1);
2251                if expiry_frame_count > 1 {
2252                    return Err(format!("Frame {i}: more than one expiry verifier frame"));
2253                }
2254                if frame.flags != 0 {
2255                    return Err(format!(
2256                        "Frame {i}: expiry verifier frame must have flags == 0"
2257                    ));
2258                }
2259                if frame.data.len() != FRAME_TX_EXPIRY_DATA_LENGTH {
2260                    return Err(format!(
2261                        "Frame {i}: expiry verifier frame data must be {FRAME_TX_EXPIRY_DATA_LENGTH} bytes"
2262                    ));
2263                }
2264            }
2265            // Per EIP-8141, only SENDER frames may carry a
2266            // non-zero value. DEFAULT and VERIFY frames with a non-zero
2267            // value are statically invalid.
2268            if frame.mode != FrameMode::Sender as u8 && !frame.value.is_zero() {
2269                return Err(format!(
2270                    "Frame {i}: non-zero value only allowed in SENDER mode (mode={}, value={})",
2271                    frame.mode, frame.value
2272                ));
2273            }
2274            // Intentional stricter-than-spec bound. EIP-8141 allows
2275            // gas_limit and cumulative frame gas up to 2**64-1; ethrex caps both
2276            // at i64::MAX (2**63-1) so the state-gas dimension (tracked as i64) can
2277            // never overflow downstream. This is documented as a known divergence
2278            // in docs/eip-8141.md. It is effectively unreachable: any gas_limit
2279            // >= 2**63 dwarfs every real block gas limit and is rejected by the
2280            // gas-limit-vs-block-limit check regardless.
2281            if frame.gas_limit > i64::MAX as u64 {
2282                return Err(format!(
2283                    "Frame {i}: gas_limit {} exceeds 2**63-1",
2284                    frame.gas_limit
2285                ));
2286            }
2287            total_frame_gas = total_frame_gas
2288                .checked_add(frame.gas_limit as u128)
2289                .ok_or_else(|| format!("Frame {i}: cumulative gas_limit overflow"))?;
2290            if total_frame_gas > i64::MAX as u128 {
2291                return Err(format!(
2292                    "Frame {i}: cumulative frame gas_limit {} exceeds 2**63-1",
2293                    total_frame_gas
2294                ));
2295            }
2296            // Per EIP-8141, approval of execution is only allowed when the
2297            // frame's target is empty or `tx.sender`.
2298            if frame.flags & APPROVE_EXECUTION != 0
2299                && frame.target.is_some_and(|target| target != self.sender)
2300            {
2301                return Err(format!(
2302                    "Frame {i}: APPROVE_EXECUTION requires an empty target or tx.sender"
2303                ));
2304            }
2305            // Per EIP-8141, the atomic batch flag (bit 2 of flags) is only valid
2306            // on a non-VERIFY frame, requires a subsequent frame to batch with,
2307            // and that frame must be non-VERIFY too: batches never contain
2308            // VERIFY frames.
2309            if frame.is_atomic_batch() {
2310                if frame.mode == FrameMode::Verify as u8 {
2311                    return Err(format!("Frame {i}: atomic batch flag on a VERIFY frame"));
2312                }
2313                match self.frames.get(i.saturating_add(1)) {
2314                    None => return Err(format!("Frame {i}: atomic batch flag on last frame")),
2315                    Some(next) if next.mode == FrameMode::Verify as u8 => {
2316                        return Err(format!(
2317                            "Frame {i}: atomic batch flag followed by a VERIFY frame"
2318                        ));
2319                    }
2320                    Some(_) => {}
2321                }
2322            }
2323        }
2324        // Per EIP-8141, the EIP-7623 calldata floor must be reserved independently
2325        // of execution: the derived `tx_gas_limit` has to cover the mandatory costs
2326        // plus the floor, or the transaction cannot pay for the data it carries.
2327        let floor_gas = self.calldata_floor_gas();
2328        if self.total_gas_limit() < self.mandatory_gas().saturating_add(floor_gas) {
2329            return Err(format!(
2330                "Total gas limit does not reserve the calldata floor of {floor_gas}"
2331            ));
2332        }
2333        Ok(())
2334    }
2335
2336    /// Identify and return the validation prefix of this frame transaction.
2337    ///
2338    /// The validation prefix is the minimal leading subsequence of frames (ignoring
2339    /// expiry-verifier frames) that establishes who pays for the transaction.
2340    /// Four shapes are recognized (per EIP-8141 §Mempool):
2341    ///
2342    /// - `SelfVerify`: one VERIFY frame targeting `tx.sender` with scope `APPROVE_EXECUTION_AND_PAYMENT`.
2343    /// - `DeploySelfVerify`: one DEFAULT frame (deploy) then one VERIFY frame as above.
2344    /// - `OnlyVerifyPay`: one VERIFY frame (only_verify, scope `APPROVE_EXECUTION`) +
2345    ///   one VERIFY frame (pay, scope `APPROVE_PAYMENT`).
2346    /// - `DeployOnlyVerifyPay`: DEFAULT deploy frame + OnlyVerifyPay pair.
2347    ///
2348    /// Expiry-verifier frames (see `Frame::is_expiry_verifier`) are transparent; they
2349    /// are skipped during shape matching but their indices are NOT included in
2350    /// `frame_indices` (which holds only the semantically meaningful prefix frames).
2351    pub fn validation_prefix(&self) -> Result<ValidationPrefix, FrameValidationError> {
2352        // Collect non-expiry frame indices in order.
2353        let non_expiry: Vec<usize> = self
2354            .frames
2355            .iter()
2356            .enumerate()
2357            .filter(|(_, f)| !f.is_expiry_verifier())
2358            .map(|(i, _)| i)
2359            .collect();
2360
2361        // Helper to get a frame by its position in non_expiry.
2362        let frame = |pos: usize| -> Option<&Frame> {
2363            non_expiry.get(pos).and_then(|&idx| self.frames.get(idx))
2364        };
2365
2366        let is_default = |pos: usize| -> bool {
2367            frame(pos).is_some_and(|f| f.execution_mode() == FrameMode::Default)
2368        };
2369        let is_verify = |pos: usize| -> bool {
2370            frame(pos).is_some_and(|f| f.execution_mode() == FrameMode::Verify)
2371        };
2372        let scope_of = |pos: usize| -> u8 { frame(pos).map_or(0, |f| f.scope_restriction()) };
2373
2374        if non_expiry.is_empty() {
2375            return Err(FrameValidationError::UnrecognizedPrefix);
2376        }
2377
2378        // Attempt to match each of the four shapes.
2379        //
2380        // Shape: DeployOnlyVerifyPay — DEFAULT + VERIFY(exec) + VERIFY(pay)
2381        if is_default(0)
2382            && is_verify(1)
2383            && scope_of(1) == APPROVE_EXECUTION
2384            && is_verify(2)
2385            && scope_of(2) == APPROVE_PAYMENT
2386        {
2387            return Ok(ValidationPrefix {
2388                shape: PrefixShape::DeployOnlyVerifyPay,
2389                frame_indices: vec![non_expiry[0], non_expiry[1], non_expiry[2]],
2390                deploy_index: Some(non_expiry[0]),
2391                pay_index: Some(non_expiry[2]),
2392            });
2393        }
2394
2395        // Shape: DeploySelfVerify — DEFAULT + VERIFY(exec+pay)
2396        if is_default(0) && is_verify(1) && scope_of(1) == APPROVE_EXECUTION_AND_PAYMENT {
2397            return Ok(ValidationPrefix {
2398                shape: PrefixShape::DeploySelfVerify,
2399                frame_indices: vec![non_expiry[0], non_expiry[1]],
2400                deploy_index: Some(non_expiry[0]),
2401                pay_index: Some(non_expiry[1]),
2402            });
2403        }
2404
2405        // Shape: OnlyVerifyPay — VERIFY(exec) + VERIFY(pay)
2406        if is_verify(0)
2407            && scope_of(0) == APPROVE_EXECUTION
2408            && is_verify(1)
2409            && scope_of(1) == APPROVE_PAYMENT
2410        {
2411            return Ok(ValidationPrefix {
2412                shape: PrefixShape::OnlyVerifyPay,
2413                frame_indices: vec![non_expiry[0], non_expiry[1]],
2414                deploy_index: None,
2415                pay_index: Some(non_expiry[1]),
2416            });
2417        }
2418
2419        // Shape: SelfVerify — VERIFY(exec+pay)
2420        if is_verify(0) && scope_of(0) == APPROVE_EXECUTION_AND_PAYMENT {
2421            return Ok(ValidationPrefix {
2422                shape: PrefixShape::SelfVerify,
2423                frame_indices: vec![non_expiry[0]],
2424                deploy_index: None,
2425                pay_index: Some(non_expiry[0]),
2426            });
2427        }
2428
2429        Err(FrameValidationError::UnrecognizedPrefix)
2430    }
2431
2432    /// Validate structural rules for the identified validation prefix.
2433    ///
2434    /// Checks:
2435    /// - Deploy frame (if any) is at index 0 and uses DEFAULT execution mode.
2436    /// - At most one deploy frame exists in the prefix.
2437    /// - self_verify / only_verify / pay frames use VERIFY execution mode.
2438    /// - Resolved target of each VERIFY frame matches `tx.sender` (target == None
2439    ///   means sender; a non-None target must equal sender).
2440    /// - Scope restriction matches the frame's role:
2441    ///   self_verify → `APPROVE_EXECUTION_AND_PAYMENT`, only_verify → `APPROVE_EXECUTION`,
2442    ///   pay → `APPROVE_PAYMENT`.
2443    /// - No frame in the prefix has the atomic-batch flag set.
2444    /// - Total gas budget: Σ(prefix frame gas_limits) + signature_verification_cost() ≤ MAX_VERIFY_GAS.
2445    pub fn validate_prefix_structure(
2446        &self,
2447        prefix: &ValidationPrefix,
2448    ) -> Result<(), FrameValidationError> {
2449        let mut deploy_count = 0usize;
2450
2451        for &idx in &prefix.frame_indices {
2452            let frame = &self.frames[idx];
2453
2454            if frame.is_atomic_batch() {
2455                return Err(FrameValidationError::AtomicBatchInPrefix { frame_index: idx });
2456            }
2457
2458            match prefix.deploy_index {
2459                Some(deploy_idx) if deploy_idx == idx => {
2460                    // This is the deploy frame.
2461                    deploy_count += 1;
2462                    if deploy_count > 1 {
2463                        return Err(FrameValidationError::MultipleDeploys { frame_index: idx });
2464                    }
2465                    // The deploy must be first among prefix frames. `validation_prefix`
2466                    // structurally guarantees this, but the raw frame index can be
2467                    // non-zero when expiry-verifier frames precede the deploy — check
2468                    // against the first element of `frame_indices`, not the raw index.
2469                    if prefix.frame_indices.first() != Some(&idx) {
2470                        return Err(FrameValidationError::DeployNotFirst { frame_index: idx });
2471                    }
2472                    if frame.execution_mode() != FrameMode::Default {
2473                        return Err(FrameValidationError::DeployNotDefaultMode {
2474                            frame_index: idx,
2475                        });
2476                    }
2477                }
2478                _ => {
2479                    // VERIFY frame (self_verify, only_verify, or pay).
2480                    if frame.execution_mode() != FrameMode::Verify {
2481                        return Err(FrameValidationError::VerifyFrameNotVerifyMode {
2482                            frame_index: idx,
2483                        });
2484                    }
2485
2486                    // Resolved target must be tx.sender (None means sender).
2487                    let target_ok = match frame.target {
2488                        None => true,
2489                        Some(addr) => addr == self.sender,
2490                    };
2491                    if !target_ok {
2492                        return Err(FrameValidationError::VerifyTargetNotSender {
2493                            frame_index: idx,
2494                        });
2495                    }
2496
2497                    // Scope restriction must match role.
2498                    let expected_scope = match prefix.shape {
2499                        PrefixShape::SelfVerify | PrefixShape::DeploySelfVerify => {
2500                            APPROVE_EXECUTION_AND_PAYMENT
2501                        }
2502                        PrefixShape::OnlyVerifyPay | PrefixShape::DeployOnlyVerifyPay => {
2503                            // The only_verify frame comes before the pay frame.
2504                            if prefix.pay_index == Some(idx) {
2505                                APPROVE_PAYMENT
2506                            } else {
2507                                APPROVE_EXECUTION
2508                            }
2509                        }
2510                    };
2511                    if frame.scope_restriction() != expected_scope {
2512                        return Err(FrameValidationError::WrongScopeRestriction {
2513                            frame_index: idx,
2514                            expected: expected_scope,
2515                            actual: frame.scope_restriction(),
2516                        });
2517                    }
2518                }
2519            }
2520        }
2521
2522        // Gas budget: prefix frame gas limits + signature cost ≤ MAX_VERIFY_GAS.
2523        let prefix_gas: u64 = prefix
2524            .frame_indices
2525            .iter()
2526            .map(|&i| self.frames[i].gas_limit)
2527            .fold(0u64, |acc, g| acc.saturating_add(g));
2528        let total_verify_gas = prefix_gas.saturating_add(self.signature_verification_cost());
2529        if total_verify_gas > FRAME_TX_MAX_VERIFY_GAS {
2530            return Err(FrameValidationError::VerifyGasBudgetExceeded {
2531                actual: total_verify_gas,
2532                limit: FRAME_TX_MAX_VERIFY_GAS,
2533            });
2534        }
2535
2536        Ok(())
2537    }
2538}
2539
2540/// The four recognized shapes of an EIP-8141 validation prefix (§Mempool).
2541#[derive(Clone, Debug, PartialEq, Eq)]
2542pub enum PrefixShape {
2543    /// Single VERIFY frame: sender verifies + authorizes payment for itself.
2544    SelfVerify,
2545    /// DEFAULT (deploy) frame, then VERIFY frame that verifies + authorizes payment.
2546    DeploySelfVerify,
2547    /// VERIFY(exec) + VERIFY(pay): separate verifier and paymaster.
2548    OnlyVerifyPay,
2549    /// DEFAULT (deploy) + VERIFY(exec) + VERIFY(pay).
2550    DeployOnlyVerifyPay,
2551}
2552
2553/// Identified validation prefix of an EIP-8141 frame transaction.
2554#[derive(Clone, Debug, PartialEq, Eq)]
2555pub struct ValidationPrefix {
2556    /// Recognized shape of this prefix.
2557    pub shape: PrefixShape,
2558    /// Frame indices (into `FrameTransaction.frames`) that form the prefix,
2559    /// in order. Does not include expiry-verifier frames.
2560    pub frame_indices: Vec<usize>,
2561    /// Index of the deploy frame within `frames`, if this shape has one.
2562    pub deploy_index: Option<usize>,
2563    /// Index of the pay (or self_verify) frame within `frames`.
2564    pub pay_index: Option<usize>,
2565}
2566
2567/// Errors produced by `FrameTransaction::validation_prefix` and
2568/// `FrameTransaction::validate_prefix_structure`.
2569#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
2570pub enum FrameValidationError {
2571    #[error("frame transaction validation prefix does not match any recognized shape")]
2572    UnrecognizedPrefix,
2573    #[error("frame {frame_index}: prefix has more than one DEFAULT deploy frame")]
2574    MultipleDeploys { frame_index: usize },
2575    #[error("frame {frame_index}: deploy frame must be at index 0")]
2576    DeployNotFirst { frame_index: usize },
2577    #[error("frame {frame_index}: deploy frame must use DEFAULT execution mode")]
2578    DeployNotDefaultMode { frame_index: usize },
2579    #[error("frame {frame_index}: prefix VERIFY frame does not use VERIFY execution mode")]
2580    VerifyFrameNotVerifyMode { frame_index: usize },
2581    #[error("frame {frame_index}: VERIFY frame target is not tx.sender")]
2582    VerifyTargetNotSender { frame_index: usize },
2583    #[error("frame {frame_index}: scope restriction is {actual:#04x}, expected {expected:#04x}")]
2584    WrongScopeRestriction {
2585        frame_index: usize,
2586        expected: u8,
2587        actual: u8,
2588    },
2589    #[error("frame {frame_index}: prefix frame has atomic-batch flag set")]
2590    AtomicBatchInPrefix { frame_index: usize },
2591    #[error("prefix gas budget exceeded: {actual} > {limit} (MAX_VERIFY_GAS)")]
2592    VerifyGasBudgetExceeded { actual: u64, limit: u64 },
2593}
2594
2595impl RLPEncode for FrameTransaction {
2596    fn encode(&self, buf: &mut dyn bytes::BufMut) {
2597        Encoder::new(buf)
2598            .encode_field(&self.chain_id)
2599            .encode_field(&self.nonce)
2600            .encode_field(&self.sender)
2601            .encode_field(&self.frames)
2602            .encode_field(&self.signatures)
2603            .encode_field(&self.max_priority_fee_per_gas)
2604            .encode_field(&self.max_fee_per_gas)
2605            .encode_field(&self.max_fee_per_blob_gas)
2606            .encode_field(&self.blob_versioned_hashes)
2607            .finish();
2608    }
2609}
2610
2611impl RLPDecode for FrameTransaction {
2612    fn decode_unfinished(rlp: &[u8]) -> Result<(FrameTransaction, &[u8]), RLPDecodeError> {
2613        let decoder = Decoder::new(rlp)?;
2614        let (chain_id, decoder) = decoder.decode_field("chain_id")?;
2615        let (nonce, decoder) = decode_nonce_field(decoder)?;
2616        let (sender, decoder) = decoder.decode_field("sender")?;
2617        let (frames, decoder) = decoder.decode_field("frames")?;
2618        let (signatures, decoder) = decoder.decode_field("signatures")?;
2619        let (max_priority_fee_per_gas, decoder) =
2620            decoder.decode_field("max_priority_fee_per_gas")?;
2621        let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
2622        let (max_fee_per_blob_gas, decoder) = decoder.decode_field("max_fee_per_blob_gas")?;
2623        let (blob_versioned_hashes, decoder) = decoder.decode_field("blob_versioned_hashes")?;
2624        let tx = FrameTransaction {
2625            chain_id,
2626            nonce,
2627            sender,
2628            frames,
2629            signatures,
2630            max_priority_fee_per_gas,
2631            max_fee_per_gas,
2632            max_fee_per_blob_gas,
2633            blob_versioned_hashes,
2634            inner_hash: OnceCell::new(),
2635            cached_canonical: OnceCell::new(),
2636        };
2637        Ok((tx, decoder.finish()?))
2638    }
2639}
2640
2641impl PayloadRLPEncode for FrameTransaction {
2642    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
2643        buf.put_u8(TxType::Frame as u8);
2644        self.encode(buf);
2645    }
2646}
2647
2648/// Canonical Transaction Encoding
2649/// Based on [EIP-2718]
2650/// Transactions can be encoded in the following formats:
2651/// A) `TransactionType || Transaction` (Where Transaction type is an 8-bit number between 0 and 0x7f, and Transaction is an rlp encoded transaction of type TransactionType)
2652/// B) `LegacyTransaction` (An rlp encoded LegacyTransaction)
2653mod canonic_encoding {
2654    use super::*;
2655
2656    impl Transaction {
2657        /// Decodes a single transaction in canonical format
2658        /// Based on [EIP-2718]
2659        /// Transactions can be encoded in the following formats:
2660        /// A) `TransactionType || Transaction` (Where Transaction type is an 8-bit number between 0 and 0x7f, and Transaction is an rlp encoded transaction of type TransactionType)
2661        /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction)
2662        pub fn decode_canonical(bytes: &[u8]) -> Result<Self, RLPDecodeError> {
2663            // Look at the first byte to check if it corresponds to a TransactionType
2664            match bytes.first() {
2665                // First byte is a valid TransactionType
2666                Some(tx_type) if *tx_type < 0x7f => {
2667                    // Decode tx based on type. `from_type_byte` is the single gate for valid
2668                    // envelope types: it rejects 0x00 (legacy is the bare-list branch below) and
2669                    // any unassigned type byte.
2670                    let tx_bytes = &bytes[1..];
2671                    match EnvelopeTxType::from_type_byte(*tx_type)? {
2672                        EnvelopeTxType::EIP2930 => EIP2930Transaction::decode(tx_bytes)
2673                            .map(Transaction::EIP2930Transaction),
2674                        EnvelopeTxType::EIP1559 => EIP1559Transaction::decode(tx_bytes)
2675                            .map(Transaction::EIP1559Transaction),
2676                        EnvelopeTxType::EIP4844 => EIP4844Transaction::decode(tx_bytes)
2677                            .map(Transaction::EIP4844Transaction),
2678                        EnvelopeTxType::EIP7702 => EIP7702Transaction::decode(tx_bytes)
2679                            .map(Transaction::EIP7702Transaction),
2680                        EnvelopeTxType::Frame => {
2681                            FrameTransaction::decode(tx_bytes).map(Transaction::FrameTransaction)
2682                        }
2683                        EnvelopeTxType::FeeToken => FeeTokenTransaction::decode(tx_bytes)
2684                            .map(Transaction::FeeTokenTransaction),
2685                        EnvelopeTxType::Privileged => PrivilegedL2Transaction::decode(tx_bytes)
2686                            .map(Transaction::PrivilegedL2Transaction),
2687                    }
2688                }
2689                // LegacyTransaction
2690                _ => LegacyTransaction::decode(bytes).map(Transaction::LegacyTransaction),
2691            }
2692        }
2693
2694        /// Encodes a transaction in canonical format
2695        /// Based on [EIP-2718]
2696        /// Transactions can be encoded in the following formats:
2697        /// A) `TransactionType || Transaction` (Where Transaction type is an 8-bit number between 0 and 0x7f, and Transaction is an rlp encoded transaction of type TransactionType)
2698        /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction)
2699        pub fn encode_canonical(&self, buf: &mut dyn bytes::BufMut) {
2700            match self {
2701                // Legacy transactions don't have a prefix
2702                Transaction::LegacyTransaction(_) => {}
2703                _ => buf.put_u8(self.tx_type() as u8),
2704            }
2705            match self {
2706                Transaction::LegacyTransaction(t) => t.encode(buf),
2707                Transaction::EIP2930Transaction(t) => t.encode(buf),
2708                Transaction::EIP1559Transaction(t) => t.encode(buf),
2709                Transaction::EIP4844Transaction(t) => t.encode(buf),
2710                Transaction::EIP7702Transaction(t) => t.encode(buf),
2711                Transaction::FeeTokenTransaction(t) => t.encode(buf),
2712                Transaction::PrivilegedL2Transaction(t) => t.encode(buf),
2713                Transaction::FrameTransaction(t) => t.encode(buf),
2714            };
2715        }
2716
2717        /// Encodes a transaction in canonical format into a newly created buffer
2718        /// Based on [EIP-2718]
2719        /// Transactions can be encoded in the following formats:
2720        /// A) `TransactionType || Transaction` (Where Transaction type is an 8-bit number between 0 and 0x7f, and Transaction is an rlp encoded transaction of type TransactionType)
2721        /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction)
2722        pub fn encode_canonical_to_vec(&self) -> Vec<u8> {
2723            if let Some(cell) = self.cached_canonical_cell() {
2724                return cell
2725                    .get_or_init(|| {
2726                        let mut buf = Vec::new();
2727                        self.encode_canonical(&mut buf);
2728                        buf
2729                    })
2730                    .clone();
2731            }
2732            let mut buf = Vec::new();
2733            self.encode_canonical(&mut buf);
2734            buf
2735        }
2736
2737        /// Canonical-encoded length without allocating a buffer. Counts the
2738        /// 1-byte type prefix for typed txs (EIP-2718) plus the inner RLP
2739        /// payload length. Use this when only the size is needed (e.g.
2740        /// admission-time size caps) to avoid `encode_canonical_to_vec().len()`.
2741        pub fn encode_canonical_len(&self) -> usize {
2742            let prefix_len = match self {
2743                Transaction::LegacyTransaction(_) => 0,
2744                _ => 1,
2745            };
2746            let inner_len = match self {
2747                Transaction::LegacyTransaction(t) => t.length(),
2748                Transaction::EIP2930Transaction(t) => t.length(),
2749                Transaction::EIP1559Transaction(t) => t.length(),
2750                Transaction::EIP4844Transaction(t) => t.length(),
2751                Transaction::EIP7702Transaction(t) => t.length(),
2752                Transaction::FeeTokenTransaction(t) => t.length(),
2753                Transaction::PrivilegedL2Transaction(t) => t.length(),
2754                Transaction::FrameTransaction(t) => t.length(),
2755            };
2756            prefix_len + inner_len
2757        }
2758    }
2759
2760    impl P2PTransaction {
2761        pub fn tx_type(&self) -> TxType {
2762            match self {
2763                P2PTransaction::LegacyTransaction(_) => TxType::Legacy,
2764                P2PTransaction::EIP2930Transaction(_) => TxType::EIP2930,
2765                P2PTransaction::EIP1559Transaction(_) => TxType::EIP1559,
2766                P2PTransaction::EIP4844TransactionWithBlobs(_) => TxType::EIP4844,
2767                P2PTransaction::EIP7702Transaction(_) => TxType::EIP7702,
2768                P2PTransaction::FeeTokenTransaction(_) => TxType::FeeToken,
2769                P2PTransaction::FrameTransaction(_) => TxType::Frame,
2770            }
2771        }
2772
2773        pub fn encode_canonical(&self, buf: &mut dyn bytes::BufMut) {
2774            match self {
2775                // Legacy transactions don't have a prefix
2776                P2PTransaction::LegacyTransaction(_) => {}
2777                _ => buf.put_u8(self.tx_type() as u8),
2778            }
2779            match self {
2780                P2PTransaction::LegacyTransaction(t) => t.encode(buf),
2781                P2PTransaction::EIP2930Transaction(t) => t.encode(buf),
2782                P2PTransaction::EIP1559Transaction(t) => t.encode(buf),
2783                P2PTransaction::EIP4844TransactionWithBlobs(t) => t.encode(buf),
2784                P2PTransaction::EIP7702Transaction(t) => t.encode(buf),
2785                P2PTransaction::FeeTokenTransaction(t) => t.encode(buf),
2786                P2PTransaction::FrameTransaction(t) => t.encode(buf),
2787            };
2788        }
2789
2790        pub fn encode_canonical_to_vec(&self) -> Vec<u8> {
2791            let mut buf = Vec::new();
2792            self.encode_canonical(&mut buf);
2793            buf
2794        }
2795
2796        /// Canonical encoded length without allocating (mirrors
2797        /// [`P2PTransaction::encode_canonical`]); equals
2798        /// `encode_canonical_to_vec().len()`. For the blob variant this includes
2799        /// the full sidecar, since `EIP4844TransactionWithBlobs` encodes it.
2800        pub fn encode_canonical_len(&self) -> usize {
2801            let prefix_len = match self {
2802                P2PTransaction::LegacyTransaction(_) => 0,
2803                _ => 1,
2804            };
2805            let inner_len = match self {
2806                P2PTransaction::LegacyTransaction(t) => t.length(),
2807                P2PTransaction::EIP2930Transaction(t) => t.length(),
2808                P2PTransaction::EIP1559Transaction(t) => t.length(),
2809                P2PTransaction::EIP4844TransactionWithBlobs(t) => t.length(),
2810                P2PTransaction::EIP7702Transaction(t) => t.length(),
2811                P2PTransaction::FeeTokenTransaction(t) => t.length(),
2812                P2PTransaction::FrameTransaction(t) => t.length(),
2813            };
2814            prefix_len + inner_len
2815        }
2816
2817        pub fn compute_hash(&self) -> H256 {
2818            match self {
2819                P2PTransaction::LegacyTransaction(t) => {
2820                    Transaction::LegacyTransaction(t.clone()).compute_hash(&NativeCrypto)
2821                }
2822                P2PTransaction::EIP2930Transaction(t) => {
2823                    Transaction::EIP2930Transaction(t.clone()).compute_hash(&NativeCrypto)
2824                }
2825                P2PTransaction::EIP1559Transaction(t) => {
2826                    Transaction::EIP1559Transaction(t.clone()).compute_hash(&NativeCrypto)
2827                }
2828                P2PTransaction::EIP4844TransactionWithBlobs(t) => {
2829                    Transaction::EIP4844Transaction(t.tx.clone()).compute_hash(&NativeCrypto)
2830                }
2831                P2PTransaction::EIP7702Transaction(t) => {
2832                    Transaction::EIP7702Transaction(t.clone()).compute_hash(&NativeCrypto)
2833                }
2834                P2PTransaction::FeeTokenTransaction(t) => {
2835                    Transaction::FeeTokenTransaction(t.clone()).compute_hash(&NativeCrypto)
2836                }
2837                P2PTransaction::FrameTransaction(t) => {
2838                    Transaction::FrameTransaction(t.clone()).compute_hash(&NativeCrypto)
2839                }
2840            }
2841        }
2842    }
2843}
2844
2845// Serialization
2846// This is used for RPC messaging and passing data into a RISC-V zkVM
2847
2848mod serde_impl {
2849    use ethereum_types::H160;
2850    use serde::Deserialize;
2851    use serde::{Deserializer, de::Error};
2852    use serde_json::Value;
2853    use std::{collections::HashMap, str::FromStr};
2854
2855    #[cfg(feature = "c-kzg")]
2856    use crate::types::BYTES_PER_BLOB;
2857    use crate::types::{AccessListItem, AuthorizationTuple, BlobsBundleError};
2858
2859    use super::*;
2860
2861    impl Serialize for TxKind {
2862        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2863        where
2864            S: serde::Serializer,
2865        {
2866            match self {
2867                TxKind::Call(address) => serializer.serialize_str(&format!("{address:#x}")),
2868                TxKind::Create => serializer.serialize_none(),
2869            }
2870        }
2871    }
2872
2873    impl<'de> Deserialize<'de> for TxKind {
2874        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2875        where
2876            D: serde::Deserializer<'de>,
2877        {
2878            let str_option = Option::<String>::deserialize(deserializer)?;
2879            match str_option {
2880                Some(str) if !str.is_empty() => Ok(TxKind::Call(
2881                    Address::from_str(str.trim_start_matches("0x")).map_err(|_| {
2882                        serde::de::Error::custom(format!("Failed to deserialize hex value {str}"))
2883                    })?,
2884                )),
2885                _ => Ok(TxKind::Create),
2886            }
2887        }
2888    }
2889
2890    impl Serialize for TxType {
2891        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2892        where
2893            S: serde::Serializer,
2894        {
2895            serializer.serialize_str(&format!("{:#x}", *self as u8))
2896        }
2897    }
2898
2899    impl<'de> Deserialize<'de> for TxType {
2900        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2901        where
2902            D: serde::Deserializer<'de>,
2903        {
2904            let str = String::deserialize(deserializer)?;
2905            let tx_num = u8::from_str_radix(str.trim_start_matches("0x"), 16).map_err(|_| {
2906                serde::de::Error::custom(format!("Failed to deserialize hex value {str}"))
2907            })?;
2908            TxType::from_u8(tx_num).ok_or_else(|| {
2909                serde::de::Error::custom(format!("Invalid transaction type {tx_num}"))
2910            })
2911        }
2912    }
2913
2914    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2915    #[serde(rename_all = "camelCase")]
2916    pub struct AccessListEntry {
2917        pub address: Address,
2918        pub storage_keys: Vec<H256>,
2919    }
2920
2921    impl From<&AccessListItem> for AccessListEntry {
2922        fn from(value: &AccessListItem) -> AccessListEntry {
2923            AccessListEntry {
2924                address: value.0,
2925                storage_keys: value.1.clone(),
2926            }
2927        }
2928    }
2929
2930    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2931    #[serde(rename_all = "camelCase")]
2932    pub struct AuthorizationTupleEntry {
2933        pub chain_id: U256,
2934        pub address: Address,
2935        #[serde(default, with = "crate::serde_utils::u64::hex_str")]
2936        pub nonce: u64,
2937        pub y_parity: U256,
2938        pub r: U256,
2939        pub s: U256,
2940    }
2941
2942    impl From<&AuthorizationTuple> for AuthorizationTupleEntry {
2943        fn from(value: &AuthorizationTuple) -> AuthorizationTupleEntry {
2944            AuthorizationTupleEntry {
2945                chain_id: value.chain_id,
2946                address: value.address,
2947                nonce: value.nonce,
2948                y_parity: value.y_parity,
2949                r: value.r_signature,
2950                s: value.s_signature,
2951            }
2952        }
2953    }
2954
2955    impl From<AuthorizationTupleEntry> for AuthorizationTuple {
2956        fn from(entry: AuthorizationTupleEntry) -> AuthorizationTuple {
2957            AuthorizationTuple {
2958                chain_id: entry.chain_id,
2959                address: entry.address,
2960                nonce: entry.nonce,
2961                y_parity: entry.y_parity,
2962                r_signature: entry.r,
2963                s_signature: entry.s,
2964            }
2965        }
2966    }
2967
2968    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2969    #[serde(rename_all = "camelCase")]
2970    pub struct FrameEntry {
2971        #[serde(with = "crate::serde_utils::u64::hex_str")]
2972        pub mode: u64,
2973        #[serde(with = "crate::serde_utils::u64::hex_str")]
2974        pub flags: u64,
2975        pub to: Option<Address>,
2976        #[serde(with = "crate::serde_utils::u64::hex_str")]
2977        pub gas_limit: u64,
2978        #[serde(
2979            default,
2980            serialize_with = "serialize_u256_hex",
2981            deserialize_with = "crate::serde_utils::u256::deser_hex_str"
2982        )]
2983        pub value: U256,
2984        #[serde(with = "crate::serde_utils::bytes")]
2985        pub data: Bytes,
2986    }
2987
2988    /// JSON shape of an EIP-8141 outer signature.
2989    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2990    #[serde(rename_all = "camelCase")]
2991    pub struct SignatureEntry {
2992        #[serde(with = "crate::serde_utils::u64::hex_str")]
2993        pub scheme: u64,
2994        pub signer: Option<Address>,
2995        #[serde(with = "crate::serde_utils::bytes")]
2996        pub msg: Bytes,
2997        #[serde(with = "crate::serde_utils::bytes")]
2998        pub signature: Bytes,
2999    }
3000
3001    impl From<&FrameSignature> for SignatureEntry {
3002        fn from(value: &FrameSignature) -> SignatureEntry {
3003            SignatureEntry {
3004                scheme: value.scheme as u64,
3005                signer: value.signer,
3006                msg: value.msg.clone(),
3007                signature: value.signature.clone(),
3008            }
3009        }
3010    }
3011
3012    fn serialize_u256_hex<S: serde::Serializer>(value: &U256, s: S) -> Result<S::Ok, S::Error> {
3013        s.serialize_str(&format!("{value:#x}"))
3014    }
3015
3016    impl From<&Frame> for FrameEntry {
3017        fn from(value: &Frame) -> FrameEntry {
3018            FrameEntry {
3019                mode: value.mode as u64,
3020                flags: value.flags as u64,
3021                to: value.target,
3022                gas_limit: value.gas_limit,
3023                value: value.value,
3024                data: value.data.clone(),
3025            }
3026        }
3027    }
3028
3029    impl From<FrameEntry> for Frame {
3030        fn from(entry: FrameEntry) -> Frame {
3031            Frame {
3032                mode: entry.mode as u8,
3033                flags: entry.flags as u8,
3034                target: entry.to,
3035                gas_limit: entry.gas_limit,
3036                value: entry.value,
3037                data: entry.data,
3038            }
3039        }
3040    }
3041
3042    impl Serialize for LegacyTransaction {
3043        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3044        where
3045            S: serde::Serializer,
3046        {
3047            let mut struct_serializer = serializer.serialize_struct("LegacyTransaction", 11)?;
3048            struct_serializer.serialize_field("type", &TxType::Legacy)?;
3049            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3050            struct_serializer.serialize_field("to", &self.to)?;
3051            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas))?;
3052            struct_serializer.serialize_field("value", &self.value)?;
3053            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3054            struct_serializer.serialize_field("gasPrice", &format!("{:#x}", self.gas_price))?;
3055            struct_serializer.serialize_field(
3056                "chainId",
3057                &format!("{:#x}", derive_legacy_chain_id(self.v).unwrap_or_default()),
3058            )?;
3059            struct_serializer.serialize_field("v", &self.v)?;
3060            struct_serializer.serialize_field("r", &self.r)?;
3061            struct_serializer.serialize_field("s", &self.s)?;
3062            struct_serializer.end()
3063        }
3064    }
3065
3066    impl Serialize for EIP2930Transaction {
3067        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3068        where
3069            S: serde::Serializer,
3070        {
3071            let mut struct_serializer = serializer.serialize_struct("Eip2930Transaction", 12)?;
3072            struct_serializer.serialize_field("type", &TxType::EIP2930)?;
3073            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3074            struct_serializer.serialize_field("to", &self.to)?;
3075            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3076            struct_serializer.serialize_field("value", &self.value)?;
3077            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3078            struct_serializer.serialize_field("gasPrice", &format!("{:#x}", self.gas_price))?;
3079            struct_serializer.serialize_field(
3080                "accessList",
3081                &self
3082                    .access_list
3083                    .iter()
3084                    .map(AccessListEntry::from)
3085                    .collect::<Vec<_>>(),
3086            )?;
3087            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3088            struct_serializer
3089                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3090            struct_serializer
3091                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests
3092            struct_serializer.serialize_field("r", &self.signature_r)?;
3093            struct_serializer.serialize_field("s", &self.signature_s)?;
3094            struct_serializer.end()
3095        }
3096    }
3097
3098    impl Serialize for EIP1559Transaction {
3099        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3100        where
3101            S: serde::Serializer,
3102        {
3103            let mut struct_serializer = serializer.serialize_struct("Eip1559Transaction", 14)?;
3104            struct_serializer.serialize_field("type", &TxType::EIP1559)?;
3105            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3106            struct_serializer.serialize_field("to", &self.to)?;
3107            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3108            struct_serializer.serialize_field("value", &self.value)?;
3109            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3110            struct_serializer.serialize_field(
3111                "maxPriorityFeePerGas",
3112                &format!("{:#x}", self.max_priority_fee_per_gas),
3113            )?;
3114            struct_serializer
3115                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3116            struct_serializer
3117                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3118            struct_serializer.serialize_field(
3119                "accessList",
3120                &self
3121                    .access_list
3122                    .iter()
3123                    .map(AccessListEntry::from)
3124                    .collect::<Vec<_>>(),
3125            )?;
3126            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3127            struct_serializer
3128                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3129            struct_serializer
3130                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests
3131            struct_serializer.serialize_field("r", &self.signature_r)?;
3132            struct_serializer.serialize_field("s", &self.signature_s)?;
3133            struct_serializer.end()
3134        }
3135    }
3136
3137    impl Serialize for EIP4844Transaction {
3138        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3139        where
3140            S: serde::Serializer,
3141        {
3142            let mut struct_serializer = serializer.serialize_struct("Eip4844Transaction", 15)?;
3143            struct_serializer.serialize_field("type", &TxType::EIP4844)?;
3144            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3145            struct_serializer.serialize_field("to", &self.to)?;
3146            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas))?;
3147            struct_serializer.serialize_field("value", &self.value)?;
3148            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3149            struct_serializer.serialize_field(
3150                "maxPriorityFeePerGas",
3151                &format!("{:#x}", self.max_priority_fee_per_gas),
3152            )?;
3153            struct_serializer
3154                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3155            struct_serializer
3156                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3157            struct_serializer.serialize_field(
3158                "maxFeePerBlobGas",
3159                &format!("{:#x}", self.max_fee_per_blob_gas),
3160            )?;
3161            struct_serializer.serialize_field(
3162                "accessList",
3163                &self
3164                    .access_list
3165                    .iter()
3166                    .map(AccessListEntry::from)
3167                    .collect::<Vec<_>>(),
3168            )?;
3169            struct_serializer
3170                .serialize_field("blobVersionedHashes", &self.blob_versioned_hashes)?;
3171            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3172            struct_serializer
3173                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3174            struct_serializer
3175                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests
3176            struct_serializer.serialize_field("r", &self.signature_r)?;
3177            struct_serializer.serialize_field("s", &self.signature_s)?;
3178            struct_serializer.end()
3179        }
3180    }
3181
3182    impl Serialize for EIP7702Transaction {
3183        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3184        where
3185            S: serde::Serializer,
3186        {
3187            let mut struct_serializer = serializer.serialize_struct("Eip7702Transaction", 15)?;
3188            struct_serializer.serialize_field("type", &TxType::EIP7702)?;
3189            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3190            struct_serializer.serialize_field("to", &self.to)?;
3191            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3192            struct_serializer.serialize_field("value", &self.value)?;
3193            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3194            struct_serializer.serialize_field(
3195                "maxPriorityFeePerGas",
3196                &format!("{:#x}", self.max_priority_fee_per_gas),
3197            )?;
3198            struct_serializer
3199                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3200            struct_serializer
3201                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3202            struct_serializer.serialize_field(
3203                "accessList",
3204                &self
3205                    .access_list
3206                    .iter()
3207                    .map(AccessListEntry::from)
3208                    .collect::<Vec<_>>(),
3209            )?;
3210            struct_serializer.serialize_field(
3211                "authorizationList",
3212                &self
3213                    .authorization_list
3214                    .iter()
3215                    .map(AuthorizationTupleEntry::from)
3216                    .collect::<Vec<_>>(),
3217            )?;
3218            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3219            struct_serializer
3220                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3221            struct_serializer
3222                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests
3223            struct_serializer.serialize_field("r", &self.signature_r)?;
3224            struct_serializer.serialize_field("s", &self.signature_s)?;
3225            struct_serializer.end()
3226        }
3227    }
3228
3229    impl Serialize for PrivilegedL2Transaction {
3230        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3231        where
3232            S: serde::Serializer,
3233        {
3234            let mut struct_serializer = serializer.serialize_struct("Eip1559Transaction", 14)?;
3235            struct_serializer.serialize_field("type", &TxType::Privileged)?;
3236            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3237            struct_serializer.serialize_field("to", &self.to)?;
3238            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3239            struct_serializer.serialize_field("value", &self.value)?;
3240            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3241            struct_serializer.serialize_field(
3242                "maxPriorityFeePerGas",
3243                &format!("{:#x}", self.max_priority_fee_per_gas),
3244            )?;
3245            struct_serializer
3246                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3247            struct_serializer
3248                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3249            struct_serializer.serialize_field(
3250                "accessList",
3251                &self
3252                    .access_list
3253                    .iter()
3254                    .map(AccessListEntry::from)
3255                    .collect::<Vec<_>>(),
3256            )?;
3257            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3258            struct_serializer.serialize_field("sender", &self.from)?;
3259            struct_serializer.end()
3260        }
3261    }
3262
3263    impl Serialize for FeeTokenTransaction {
3264        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3265        where
3266            S: serde::Serializer,
3267        {
3268            let mut struct_serializer = serializer.serialize_struct("FeeTokenTransaction", 15)?;
3269            struct_serializer.serialize_field("type", &TxType::FeeToken)?;
3270            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3271            struct_serializer.serialize_field("to", &self.to)?;
3272            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3273            struct_serializer.serialize_field("value", &self.value)?;
3274            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3275            struct_serializer.serialize_field(
3276                "maxPriorityFeePerGas",
3277                &format!("{:#x}", self.max_priority_fee_per_gas),
3278            )?;
3279            struct_serializer
3280                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3281            struct_serializer
3282                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3283            struct_serializer.serialize_field(
3284                "accessList",
3285                &self
3286                    .access_list
3287                    .iter()
3288                    .map(AccessListEntry::from)
3289                    .collect::<Vec<_>>(),
3290            )?;
3291            struct_serializer.serialize_field("feeToken", &format!("{:#x}", self.fee_token))?;
3292            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3293            struct_serializer
3294                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3295            struct_serializer
3296                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?;
3297            struct_serializer.serialize_field("r", &self.signature_r)?;
3298            struct_serializer.serialize_field("s", &self.signature_s)?;
3299            struct_serializer.end()
3300        }
3301    }
3302
3303    impl Serialize for FrameTransaction {
3304        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3305        where
3306            S: serde::Serializer,
3307        {
3308            let mut s = serializer.serialize_struct("FrameTransaction", 10)?;
3309            s.serialize_field("type", &TxType::Frame)?;
3310            s.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3311            s.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3312            s.serialize_field("sender", &format!("{:#x}", self.sender))?;
3313            s.serialize_field(
3314                "frames",
3315                &self.frames.iter().map(FrameEntry::from).collect::<Vec<_>>(),
3316            )?;
3317            s.serialize_field(
3318                "signatures",
3319                &self
3320                    .signatures
3321                    .iter()
3322                    .map(SignatureEntry::from)
3323                    .collect::<Vec<_>>(),
3324            )?;
3325            s.serialize_field(
3326                "maxPriorityFeePerGas",
3327                &format!("{:#x}", self.max_priority_fee_per_gas),
3328            )?;
3329            s.serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3330            s.serialize_field("maxFeePerBlobGas", &self.max_fee_per_blob_gas)?;
3331            s.serialize_field("blobVersionedHashes", &self.blob_versioned_hashes)?;
3332            s.end()
3333        }
3334    }
3335
3336    impl<'de> Deserialize<'de> for Transaction {
3337        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3338        where
3339            D: serde::Deserializer<'de>,
3340        {
3341            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3342            let tx_type =
3343                serde_json::from_value::<TxType>(map.remove("type").unwrap_or(Value::default()))
3344                    .unwrap_or_else(|_| {
3345                        if map.contains_key("tx_type") {
3346                            return TxType::Privileged;
3347                        }
3348                        TxType::EIP1559
3349                    });
3350
3351            let iter = map.into_iter();
3352            match tx_type {
3353                TxType::Legacy => {
3354                    LegacyTransaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3355                        .map(Transaction::LegacyTransaction)
3356                        .map_err(|e| {
3357                            serde::de::Error::custom(format!("Couldn't Deserialize Legacy {e}"))
3358                        })
3359                }
3360                TxType::EIP2930 => {
3361                    EIP2930Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3362                        .map(Transaction::EIP2930Transaction)
3363                        .map_err(|e| {
3364                            serde::de::Error::custom(format!("Couldn't Deserialize EIP2930 {e}"))
3365                        })
3366                }
3367                TxType::EIP1559 => {
3368                    EIP1559Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3369                        .map(Transaction::EIP1559Transaction)
3370                        .map_err(|e| {
3371                            serde::de::Error::custom(format!("Couldn't Deserialize EIP1559 {e}"))
3372                        })
3373                }
3374                TxType::EIP4844 => {
3375                    EIP4844Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3376                        .map(Transaction::EIP4844Transaction)
3377                        .map_err(|e| {
3378                            serde::de::Error::custom(format!("Couldn't Deserialize EIP4844 {e}"))
3379                        })
3380                }
3381                TxType::EIP7702 => {
3382                    EIP7702Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3383                        .map(Transaction::EIP7702Transaction)
3384                        .map_err(|e| {
3385                            serde::de::Error::custom(format!("Couldn't Deserialize EIP7702 {e}"))
3386                        })
3387                }
3388                TxType::Privileged => PrivilegedL2Transaction::deserialize(
3389                    serde::de::value::MapDeserializer::new(iter),
3390                )
3391                .map(Transaction::PrivilegedL2Transaction)
3392                .map_err(|e| {
3393                    serde::de::Error::custom(format!("Couldn't Deserialize Privileged: {e}"))
3394                }),
3395                TxType::FeeToken => {
3396                    FeeTokenTransaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3397                        .map(Transaction::FeeTokenTransaction)
3398                        .map_err(|e| {
3399                            serde::de::Error::custom(format!("Couldn't Deserialize FeeToken {e}"))
3400                        })
3401                }
3402                TxType::Frame => Err(serde::de::Error::custom(
3403                    "Frame transaction JSON deserialization not supported",
3404                )),
3405            }
3406        }
3407    }
3408
3409    fn deserialize_input_field(
3410        map: &mut std::collections::HashMap<String, Value>,
3411    ) -> Result<Bytes, serde_json::Error> {
3412        let data_str: String = serde_json::from_value(
3413            map.remove("input")
3414                .ok_or_else(|| serde::de::Error::missing_field("input"))?,
3415        )
3416        .map_err(serde::de::Error::custom)?;
3417        if let Some(stripped) = data_str.strip_prefix("0x") {
3418            match hex::decode(stripped) {
3419                Ok(decoded_bytes) => Ok(Bytes::from(decoded_bytes)),
3420                Err(_) => Err(serde::de::Error::custom(
3421                    "Invalid hex format in 'input' field",
3422                ))?,
3423            }
3424        } else {
3425            Err(serde::de::Error::custom(
3426                "'input' field must start with '0x'",
3427            ))?
3428        }
3429    }
3430
3431    fn deserialize_field<'de, T, D>(
3432        map: &mut HashMap<String, serde_json::Value>,
3433        key: &str,
3434    ) -> Result<T, D::Error>
3435    where
3436        D: serde::Deserializer<'de>,
3437        T: serde::de::DeserializeOwned,
3438    {
3439        map.remove(key)
3440            .ok_or_else(|| D::Error::custom(format!("Missing field: {key}")))
3441            .and_then(|value| {
3442                serde_json::from_value(value).map_err(|err| D::Error::custom(err.to_string()))
3443            })
3444    }
3445
3446    fn deserialize_u64_field<'de, D>(
3447        map: &mut HashMap<String, serde_json::Value>,
3448        key: &str,
3449    ) -> Result<u64, D::Error>
3450    where
3451        D: serde::Deserializer<'de>,
3452    {
3453        let value = deserialize_field::<U256, D>(map, key)?;
3454        u64::try_from(value).map_err(|_| D::Error::custom(format!("{key} value overflows u64")))
3455    }
3456
3457    impl<'de> Deserialize<'de> for LegacyTransaction {
3458        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3459        where
3460            D: serde::Deserializer<'de>,
3461        {
3462            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3463
3464            Ok(LegacyTransaction {
3465                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3466                gas_price: deserialize_field::<U256, D>(&mut map, "gasPrice")?,
3467                gas: deserialize_u64_field::<D>(&mut map, "gas")?,
3468                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3469                value: deserialize_field::<U256, D>(&mut map, "value")?,
3470                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3471                v: deserialize_field::<U256, D>(&mut map, "v")?,
3472                r: deserialize_field::<U256, D>(&mut map, "r")?,
3473                s: deserialize_field::<U256, D>(&mut map, "s")?,
3474                ..Default::default()
3475            })
3476        }
3477    }
3478
3479    impl<'de> Deserialize<'de> for EIP2930Transaction {
3480        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3481        where
3482            D: serde::Deserializer<'de>,
3483        {
3484            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3485
3486            Ok(EIP2930Transaction {
3487                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3488                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3489                gas_price: deserialize_field::<U256, D>(&mut map, "gasPrice")?,
3490                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3491                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3492                value: deserialize_field::<U256, D>(&mut map, "value")?,
3493                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3494                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3495                    .into_iter()
3496                    .map(|v| (v.address, v.storage_keys))
3497                    .collect::<Vec<_>>(),
3498                signature_y_parity: u8::from_str_radix(
3499                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3500                    16,
3501                )
3502                .map_err(serde::de::Error::custom)?
3503                    != 0,
3504                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3505                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3506                ..Default::default()
3507            })
3508        }
3509    }
3510
3511    impl<'de> Deserialize<'de> for EIP1559Transaction {
3512        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3513        where
3514            D: serde::Deserializer<'de>,
3515        {
3516            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3517            Ok(EIP1559Transaction {
3518                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3519                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3520                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3521                    &mut map,
3522                    "maxPriorityFeePerGas",
3523                )?,
3524                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3525                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3526                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3527                value: deserialize_field::<U256, D>(&mut map, "value")?,
3528                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3529                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3530                    .into_iter()
3531                    .map(|v| (v.address, v.storage_keys))
3532                    .collect::<Vec<_>>(),
3533                signature_y_parity: u8::from_str_radix(
3534                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3535                    16,
3536                )
3537                .map_err(serde::de::Error::custom)?
3538                    != 0,
3539                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3540                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3541                ..Default::default()
3542            })
3543        }
3544    }
3545
3546    impl<'de> Deserialize<'de> for EIP4844Transaction {
3547        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3548        where
3549            D: serde::Deserializer<'de>,
3550        {
3551            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3552
3553            Ok(EIP4844Transaction {
3554                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3555                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3556                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3557                    &mut map,
3558                    "maxPriorityFeePerGas",
3559                )?,
3560                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3561                gas: deserialize_u64_field::<D>(&mut map, "gas")?,
3562                to: deserialize_field::<Address, D>(&mut map, "to")?,
3563                value: deserialize_field::<U256, D>(&mut map, "value")?,
3564                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3565                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3566                    .into_iter()
3567                    .map(|v| (v.address, v.storage_keys))
3568                    .collect::<Vec<_>>(),
3569                max_fee_per_blob_gas: deserialize_field::<U256, D>(&mut map, "maxFeePerBlobGas")?,
3570                blob_versioned_hashes: deserialize_field::<Vec<H256>, D>(
3571                    &mut map,
3572                    "blobVersionedHashes",
3573                )?,
3574                signature_y_parity: u8::from_str_radix(
3575                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3576                    16,
3577                )
3578                .map_err(serde::de::Error::custom)?
3579                    != 0,
3580                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3581                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3582                ..Default::default()
3583            })
3584        }
3585    }
3586
3587    impl<'de> Deserialize<'de> for EIP7702Transaction {
3588        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3589        where
3590            D: serde::Deserializer<'de>,
3591        {
3592            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3593
3594            Ok(EIP7702Transaction {
3595                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3596                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3597                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3598                    &mut map,
3599                    "maxPriorityFeePerGas",
3600                )?,
3601                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3602                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3603                to: deserialize_field::<Address, D>(&mut map, "to")?,
3604                value: deserialize_field::<U256, D>(&mut map, "value")?,
3605                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3606                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3607                    .into_iter()
3608                    .map(|v| (v.address, v.storage_keys))
3609                    .collect::<Vec<_>>(),
3610                authorization_list: deserialize_field::<Vec<AuthorizationTupleEntry>, D>(
3611                    &mut map,
3612                    "authorizationList",
3613                )?
3614                .into_iter()
3615                .map(AuthorizationTuple::from)
3616                .collect::<Vec<_>>(),
3617                signature_y_parity: u8::from_str_radix(
3618                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3619                    16,
3620                )
3621                .map_err(serde::de::Error::custom)?
3622                    != 0,
3623                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3624                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3625                ..Default::default()
3626            })
3627        }
3628    }
3629
3630    impl<'de> Deserialize<'de> for PrivilegedL2Transaction {
3631        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3632        where
3633            D: serde::Deserializer<'de>,
3634        {
3635            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3636
3637            Ok(PrivilegedL2Transaction {
3638                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3639                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3640                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3641                    &mut map,
3642                    "maxPriorityFeePerGas",
3643                )?,
3644                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3645                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3646                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3647                value: deserialize_field::<U256, D>(&mut map, "value")?,
3648                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3649                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3650                    .into_iter()
3651                    .map(|v| (v.address, v.storage_keys))
3652                    .collect::<Vec<_>>(),
3653                from: deserialize_field::<Address, D>(&mut map, "sender")?,
3654                ..Default::default()
3655            })
3656        }
3657    }
3658
3659    impl<'de> Deserialize<'de> for FeeTokenTransaction {
3660        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3661        where
3662            D: serde::Deserializer<'de>,
3663        {
3664            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3665
3666            Ok(FeeTokenTransaction {
3667                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3668                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3669                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3670                    &mut map,
3671                    "maxPriorityFeePerGas",
3672                )?,
3673                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3674                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3675                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3676                value: deserialize_field::<U256, D>(&mut map, "value")?,
3677                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3678                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3679                    .into_iter()
3680                    .map(|v| (v.address, v.storage_keys))
3681                    .collect::<Vec<_>>(),
3682                fee_token: deserialize_field::<Address, D>(&mut map, "feeToken")?,
3683                signature_y_parity: u8::from_str_radix(
3684                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3685                    16,
3686                )
3687                .map_err(serde::de::Error::custom)?
3688                    != 0,
3689                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3690                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3691                ..Default::default()
3692            })
3693        }
3694    }
3695
3696    #[derive(Debug, thiserror::Error)]
3697    pub enum GenericTransactionError {
3698        #[error("Invalid transaction type: {0}")]
3699        InvalidTxType(TxType),
3700        #[error("Blob bundle error: {0}")]
3701        BlobBundleError(#[from] BlobsBundleError),
3702        #[error("Missing field: {0}")]
3703        MissingField(String),
3704        #[error("Invalid field: {0}")]
3705        InvalidField(String),
3706    }
3707
3708    /// Unsigned Transaction struct generic to all types which may not contain all required transaction fields
3709    /// Used to perform gas estimations and access list creation
3710    #[derive(Deserialize, Debug, PartialEq, Clone, Default)]
3711    #[serde(rename_all = "camelCase")]
3712    pub struct GenericTransaction {
3713        #[serde(default)]
3714        pub r#type: TxType,
3715        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3716        pub nonce: Option<u64>,
3717        pub to: TxKind,
3718        #[serde(default)]
3719        pub from: Address,
3720        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3721        pub gas: Option<u64>,
3722        #[serde(default)]
3723        pub value: U256,
3724        #[serde(default)]
3725        pub gas_price: U256,
3726        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3727        pub max_priority_fee_per_gas: Option<u64>,
3728        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3729        pub max_fee_per_gas: Option<u64>,
3730        pub max_fee_per_blob_gas: Option<U256>,
3731        #[serde(default)]
3732        pub access_list: Vec<AccessListEntry>,
3733        #[serde(default)]
3734        pub fee_token: Option<Address>,
3735        #[serde(default)]
3736        pub authorization_list: Option<Vec<AuthorizationTupleEntry>>,
3737        #[serde(default)]
3738        pub blob_versioned_hashes: Vec<H256>,
3739        pub wrapper_version: Option<u8>,
3740        #[serde(default, with = "crate::serde_utils::bytes::vec")]
3741        pub blobs: Vec<Bytes>,
3742        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3743        pub chain_id: Option<u64>,
3744        // rename is needed here so we dont attempt to deserialize the `input` field rather than the remainder of the fields
3745        #[serde(
3746            flatten,
3747            rename = "input_or_data",
3748            deserialize_with = "deserialize_input",
3749            serialize_with = "crate::serde_utils::bytes::serialize"
3750        )]
3751        pub input: Bytes,
3752    }
3753    /// Custom deserialization function to parse either `data` or `input` fields, or both as long as they have the same value
3754    pub fn deserialize_input<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
3755    where
3756        D: Deserializer<'de>,
3757    {
3758        // The input field can be named either input or data
3759        // In case we have both fields both should be named the same
3760        let variables = HashMap::<String, Value>::deserialize(deserializer)?;
3761        let data = variables.get("data");
3762        let input = variables.get("input");
3763        let value = match (data, input) {
3764            // This replaces `default` attribute for this custom implementation
3765            (None, None) => return Ok(Bytes::new()),
3766            (None, Some(val)) => val,
3767            (Some(val), None) => val,
3768            (Some(val_a), Some(val_b)) => {
3769                if val_a == val_b {
3770                    val_a
3771                } else {
3772                    return Err(D::Error::custom(
3773                        "Transaction has both `data` and `input` fields with different values",
3774                    ));
3775                }
3776            }
3777        };
3778        let value = String::deserialize(value).map_err(D::Error::custom)?;
3779        let bytes = hex::decode(value.trim_start_matches("0x"))
3780            .map_err(|e| D::Error::custom(e.to_string()))?;
3781        Ok(Bytes::from(bytes))
3782    }
3783
3784    impl From<EIP1559Transaction> for GenericTransaction {
3785        fn from(value: EIP1559Transaction) -> Self {
3786            Self {
3787                r#type: TxType::EIP1559,
3788                nonce: Some(value.nonce),
3789                to: value.to,
3790                gas: Some(value.gas_limit),
3791                value: value.value,
3792                input: value.data.clone(),
3793                gas_price: U256::from(value.max_fee_per_gas),
3794                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3795                max_fee_per_gas: Some(value.max_fee_per_gas),
3796                max_fee_per_blob_gas: None,
3797                access_list: value
3798                    .access_list
3799                    .iter()
3800                    .map(AccessListEntry::from)
3801                    .collect(),
3802                fee_token: None,
3803                authorization_list: None,
3804                blob_versioned_hashes: vec![],
3805                blobs: vec![],
3806                wrapper_version: None,
3807                chain_id: Some(value.chain_id),
3808                from: Address::default(),
3809            }
3810        }
3811    }
3812
3813    impl TryFrom<GenericTransaction> for EIP1559Transaction {
3814        type Error = GenericTransactionError;
3815
3816        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3817            if value.r#type != TxType::EIP1559 {
3818                return Err(GenericTransactionError::InvalidTxType(value.r#type));
3819            }
3820
3821            Ok(Self {
3822                nonce: value.nonce.unwrap_or_default(),
3823                to: value.to,
3824                gas_limit: value.gas.unwrap_or_default(),
3825                value: value.value,
3826                data: value.input.clone(),
3827                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3828                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3829                    u64::try_from(value.gas_price).map_err(|_| {
3830                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3831                    })?,
3832                ),
3833                access_list: value
3834                    .access_list
3835                    .into_iter()
3836                    .map(|v| (v.address, v.storage_keys))
3837                    .collect::<Vec<_>>(),
3838                chain_id: value.chain_id.unwrap_or_default(),
3839                ..Default::default()
3840            })
3841        }
3842    }
3843
3844    impl From<EIP4844Transaction> for GenericTransaction {
3845        fn from(value: EIP4844Transaction) -> Self {
3846            Self {
3847                r#type: TxType::EIP4844,
3848                nonce: Some(value.nonce),
3849                to: TxKind::Call(value.to),
3850                gas: Some(value.gas),
3851                value: value.value,
3852                input: value.data.clone(),
3853                gas_price: U256::from(value.max_fee_per_gas),
3854                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3855                max_fee_per_gas: Some(value.max_fee_per_gas),
3856                max_fee_per_blob_gas: Some(value.max_fee_per_blob_gas),
3857                access_list: value
3858                    .access_list
3859                    .iter()
3860                    .map(AccessListEntry::from)
3861                    .collect(),
3862                fee_token: None,
3863                authorization_list: None,
3864                blob_versioned_hashes: value.blob_versioned_hashes,
3865                blobs: vec![],
3866                wrapper_version: None,
3867                chain_id: Some(value.chain_id),
3868                from: Address::default(),
3869            }
3870        }
3871    }
3872
3873    #[cfg(feature = "c-kzg")]
3874    impl TryFrom<GenericTransaction> for WrappedEIP4844Transaction {
3875        type Error = GenericTransactionError;
3876
3877        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3878            let blobs = value
3879                .blobs
3880                .iter()
3881                .map(|bytes| {
3882                    let slice = bytes.as_ref();
3883                    let mut blob = [0u8; BYTES_PER_BLOB];
3884                    blob.copy_from_slice(slice);
3885                    blob
3886                })
3887                .collect();
3888
3889            let wrapper_version = value.wrapper_version;
3890            Ok(Self {
3891                tx: value.try_into()?,
3892                wrapper_version,
3893                blobs_bundle: BlobsBundle::create_from_blobs(&blobs, wrapper_version)?,
3894            })
3895        }
3896    }
3897
3898    impl TryFrom<GenericTransaction> for EIP4844Transaction {
3899        type Error = GenericTransactionError;
3900
3901        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3902            if value.r#type != TxType::EIP4844 {
3903                return Err(GenericTransactionError::InvalidTxType(value.r#type));
3904            }
3905            Ok(Self {
3906                nonce: value.nonce.unwrap_or_default(),
3907                to: match value.to {
3908                    TxKind::Call(to) => to,
3909                    _ => H160::default(),
3910                },
3911                gas: value.gas.unwrap_or_default(),
3912                value: value.value,
3913                data: value.input.clone(),
3914                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3915                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3916                    u64::try_from(value.gas_price).map_err(|_| {
3917                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3918                    })?,
3919                ),
3920                max_fee_per_blob_gas: value.max_fee_per_blob_gas.unwrap_or_default(),
3921                access_list: value
3922                    .access_list
3923                    .into_iter()
3924                    .map(|v| (v.address, v.storage_keys))
3925                    .collect::<Vec<_>>(),
3926                blob_versioned_hashes: value.blob_versioned_hashes,
3927                chain_id: value.chain_id.unwrap_or_default(),
3928                ..Default::default()
3929            })
3930        }
3931    }
3932
3933    impl From<EIP7702Transaction> for GenericTransaction {
3934        fn from(value: EIP7702Transaction) -> Self {
3935            Self {
3936                r#type: TxType::EIP7702,
3937                nonce: Some(value.nonce),
3938                to: TxKind::Call(value.to),
3939                gas: Some(value.gas_limit),
3940                value: value.value,
3941                input: value.data.clone(),
3942                gas_price: U256::from(value.max_fee_per_gas),
3943                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3944                max_fee_per_gas: Some(value.max_fee_per_gas),
3945                max_fee_per_blob_gas: None,
3946                access_list: value
3947                    .access_list
3948                    .iter()
3949                    .map(AccessListEntry::from)
3950                    .collect(),
3951                fee_token: None,
3952                authorization_list: Some(
3953                    value
3954                        .authorization_list
3955                        .iter()
3956                        .map(AuthorizationTupleEntry::from)
3957                        .collect(),
3958                ),
3959                blob_versioned_hashes: vec![],
3960                blobs: vec![],
3961                wrapper_version: None,
3962                chain_id: Some(value.chain_id),
3963                from: Address::default(),
3964            }
3965        }
3966    }
3967
3968    impl TryFrom<GenericTransaction> for EIP7702Transaction {
3969        type Error = GenericTransactionError;
3970
3971        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3972            if value.r#type != TxType::EIP7702 {
3973                return Err(GenericTransactionError::InvalidTxType(value.r#type));
3974            }
3975            let TxKind::Call(to) = value.to else {
3976                return Err(GenericTransactionError::MissingField("to".to_owned()));
3977            };
3978            Ok(Self {
3979                chain_id: value.chain_id.unwrap_or_default(),
3980                nonce: value.nonce.unwrap_or_default(),
3981                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3982                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3983                    u64::try_from(value.gas_price).map_err(|_| {
3984                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3985                    })?,
3986                ),
3987                gas_limit: value.gas.unwrap_or_default(),
3988                to,
3989                value: value.value,
3990                data: value.input,
3991                access_list: value
3992                    .access_list
3993                    .into_iter()
3994                    .map(|v| (v.address, v.storage_keys))
3995                    .collect::<Vec<_>>(),
3996                authorization_list: value
3997                    .authorization_list
3998                    .unwrap_or_default()
3999                    .into_iter()
4000                    .map(AuthorizationTuple::from)
4001                    .collect(),
4002                ..Default::default()
4003            })
4004        }
4005    }
4006
4007    impl From<PrivilegedL2Transaction> for GenericTransaction {
4008        fn from(value: PrivilegedL2Transaction) -> Self {
4009            Self {
4010                r#type: TxType::Privileged,
4011                nonce: Some(value.nonce),
4012                to: value.to,
4013                gas: Some(value.gas_limit),
4014                value: value.value,
4015                input: value.data.clone(),
4016                gas_price: U256::from(value.max_fee_per_gas),
4017                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
4018                max_fee_per_gas: Some(value.max_fee_per_gas),
4019                max_fee_per_blob_gas: None,
4020                access_list: value
4021                    .access_list
4022                    .iter()
4023                    .map(AccessListEntry::from)
4024                    .collect(),
4025                fee_token: None,
4026                authorization_list: None,
4027                blob_versioned_hashes: vec![],
4028                blobs: vec![],
4029                wrapper_version: None,
4030                chain_id: Some(value.chain_id),
4031                from: value.from,
4032            }
4033        }
4034    }
4035
4036    impl TryFrom<GenericTransaction> for PrivilegedL2Transaction {
4037        type Error = GenericTransactionError;
4038
4039        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
4040            if value.r#type != TxType::Privileged {
4041                return Err(GenericTransactionError::InvalidTxType(value.r#type));
4042            }
4043            Ok(Self {
4044                nonce: value.nonce.unwrap_or_default(),
4045                to: value.to,
4046                gas_limit: value.gas.unwrap_or_default(),
4047                value: value.value,
4048                data: value.input.clone(),
4049                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
4050                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
4051                    u64::try_from(value.gas_price).map_err(|_| {
4052                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
4053                    })?,
4054                ),
4055                access_list: value
4056                    .access_list
4057                    .into_iter()
4058                    .map(|v| (v.address, v.storage_keys))
4059                    .collect::<Vec<_>>(),
4060                chain_id: value.chain_id.unwrap_or_default(),
4061                from: value.from,
4062                ..Default::default()
4063            })
4064        }
4065    }
4066
4067    impl From<FeeTokenTransaction> for GenericTransaction {
4068        fn from(value: FeeTokenTransaction) -> Self {
4069            Self {
4070                r#type: TxType::FeeToken,
4071                nonce: Some(value.nonce),
4072                to: value.to,
4073                gas: Some(value.gas_limit),
4074                value: value.value,
4075                input: value.data.clone(),
4076                gas_price: U256::from(value.max_fee_per_gas),
4077                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
4078                max_fee_per_gas: Some(value.max_fee_per_gas),
4079                max_fee_per_blob_gas: None,
4080                access_list: value
4081                    .access_list
4082                    .iter()
4083                    .map(AccessListEntry::from)
4084                    .collect(),
4085                fee_token: Some(value.fee_token),
4086                authorization_list: None,
4087                blob_versioned_hashes: vec![],
4088                blobs: vec![],
4089                chain_id: Some(value.chain_id),
4090                from: Address::default(),
4091                wrapper_version: None,
4092            }
4093        }
4094    }
4095
4096    impl TryFrom<GenericTransaction> for FeeTokenTransaction {
4097        type Error = GenericTransactionError;
4098
4099        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
4100            if value.r#type != TxType::FeeToken {
4101                return Err(GenericTransactionError::InvalidTxType(value.r#type));
4102            }
4103
4104            Ok(Self {
4105                nonce: value.nonce.unwrap_or_default(),
4106                to: value.to,
4107                gas_limit: value.gas.unwrap_or_default(),
4108                value: value.value,
4109                data: value.input.clone(),
4110                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
4111                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
4112                    u64::try_from(value.gas_price).map_err(|_| {
4113                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
4114                    })?,
4115                ),
4116                access_list: value
4117                    .access_list
4118                    .into_iter()
4119                    .map(|v| (v.address, v.storage_keys))
4120                    .collect::<Vec<_>>(),
4121                fee_token: value
4122                    .fee_token
4123                    .ok_or(GenericTransactionError::MissingField(
4124                        "fee token".to_owned(),
4125                    ))?,
4126                chain_id: value.chain_id.unwrap_or_default(),
4127                ..Default::default()
4128            })
4129        }
4130    }
4131
4132    impl From<LegacyTransaction> for GenericTransaction {
4133        fn from(value: LegacyTransaction) -> Self {
4134            Self {
4135                r#type: TxType::Legacy,
4136                nonce: Some(value.nonce),
4137                to: value.to,
4138                from: Address::default(),
4139                gas: Some(value.gas),
4140                value: value.value,
4141                gas_price: value.gas_price,
4142                max_priority_fee_per_gas: None,
4143                max_fee_per_gas: None,
4144                max_fee_per_blob_gas: None,
4145                access_list: vec![],
4146                fee_token: None,
4147                authorization_list: None,
4148                blob_versioned_hashes: vec![],
4149                blobs: vec![],
4150                wrapper_version: None,
4151                chain_id: None,
4152                input: value.data,
4153            }
4154        }
4155    }
4156
4157    impl From<EIP2930Transaction> for GenericTransaction {
4158        fn from(value: EIP2930Transaction) -> Self {
4159            Self {
4160                r#type: TxType::EIP2930,
4161                nonce: Some(value.nonce),
4162                to: value.to,
4163                from: Address::default(),
4164                gas: Some(value.gas_limit),
4165                value: value.value,
4166                gas_price: value.gas_price,
4167                max_priority_fee_per_gas: None,
4168                max_fee_per_gas: None,
4169                max_fee_per_blob_gas: None,
4170                access_list: value
4171                    .access_list
4172                    .into_iter()
4173                    .map(|(address, storage_keys)| AccessListEntry {
4174                        address,
4175                        storage_keys,
4176                    })
4177                    .collect(),
4178                fee_token: None,
4179                authorization_list: None,
4180                blob_versioned_hashes: vec![],
4181                blobs: vec![],
4182                wrapper_version: None,
4183                chain_id: Some(value.chain_id),
4184                input: value.data,
4185            }
4186        }
4187    }
4188
4189    impl From<Transaction> for GenericTransaction {
4190        fn from(value: Transaction) -> Self {
4191            match value {
4192                Transaction::LegacyTransaction(tx) => tx.into(),
4193                Transaction::EIP2930Transaction(tx) => tx.into(),
4194                Transaction::EIP1559Transaction(tx) => tx.into(),
4195                Transaction::EIP4844Transaction(tx) => tx.into(),
4196                Transaction::EIP7702Transaction(tx) => tx.into(),
4197                Transaction::PrivilegedL2Transaction(tx) => tx.into(),
4198                Transaction::FeeTokenTransaction(tx) => tx.into(),
4199                Transaction::FrameTransaction(tx) => tx.into(),
4200            }
4201        }
4202    }
4203
4204    impl From<FrameTransaction> for GenericTransaction {
4205        fn from(value: FrameTransaction) -> Self {
4206            Self {
4207                r#type: TxType::Frame,
4208                nonce: Some(value.nonce),
4209                to: TxKind::Call(value.sender),
4210                from: value.sender,
4211                gas: Some(value.total_gas_limit()),
4212                value: U256::zero(),
4213                gas_price: value.max_fee_per_gas.into(),
4214                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
4215                max_fee_per_gas: Some(value.max_fee_per_gas),
4216                max_fee_per_blob_gas: if value.blob_versioned_hashes.is_empty() {
4217                    None
4218                } else {
4219                    Some(value.max_fee_per_blob_gas)
4220                },
4221                access_list: vec![],
4222                fee_token: None,
4223                authorization_list: None,
4224                blob_versioned_hashes: value.blob_versioned_hashes,
4225                blobs: vec![],
4226                wrapper_version: None,
4227                chain_id: Some(value.chain_id),
4228                input: Bytes::new(),
4229            }
4230        }
4231    }
4232}
4233
4234mod mempool {
4235    use super::*;
4236    use std::{
4237        cmp::Ordering,
4238        sync::Arc,
4239        time::{SystemTime, UNIX_EPOCH},
4240    };
4241
4242    #[derive(Debug, Clone, PartialEq, Eq)]
4243    pub struct MempoolTransaction {
4244        // Unix timestamp (in microseconds) created once the transaction reached the MemPool
4245        timestamp: u128,
4246        sender: Address,
4247        inner: Arc<Transaction>,
4248    }
4249
4250    impl MempoolTransaction {
4251        pub fn new(tx: Transaction, sender: Address) -> Self {
4252            Self {
4253                timestamp: SystemTime::now()
4254                    .duration_since(UNIX_EPOCH)
4255                    .expect("Invalid system time")
4256                    .as_micros(),
4257                sender,
4258                inner: Arc::new(tx),
4259            }
4260        }
4261        pub fn time(&self) -> u128 {
4262            self.timestamp
4263        }
4264
4265        pub fn sender(&self) -> Address {
4266            self.sender
4267        }
4268
4269        pub fn transaction(&self) -> &Transaction {
4270            &self.inner
4271        }
4272    }
4273
4274    impl RLPEncode for MempoolTransaction {
4275        fn encode(&self, buf: &mut dyn bytes::BufMut) {
4276            Encoder::new(buf)
4277                .encode_field(&self.timestamp)
4278                .encode_field(&*self.inner)
4279                .finish();
4280        }
4281    }
4282
4283    impl RLPDecode for MempoolTransaction {
4284        fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
4285            let decoder = Decoder::new(rlp)?;
4286            let (timestamp, decoder) = decoder.decode_field("timestamp")?;
4287            let (sender, decoder) = decoder.decode_field("sender")?;
4288            let (inner, decoder) = decoder.decode_field("inner")?;
4289            Ok((
4290                Self {
4291                    timestamp,
4292                    sender,
4293                    inner: Arc::new(inner),
4294                },
4295                decoder.finish()?,
4296            ))
4297        }
4298    }
4299
4300    impl std::ops::Deref for MempoolTransaction {
4301        type Target = Transaction;
4302
4303        fn deref(&self) -> &Self::Target {
4304            &self.inner
4305        }
4306    }
4307
4308    // Orders transactions by lowest nonce, if the nonce is equal, orders by highest timestamp
4309    impl Ord for MempoolTransaction {
4310        fn cmp(&self, other: &Self) -> Ordering {
4311            match (self.tx_type(), other.tx_type()) {
4312                (TxType::Privileged, TxType::Privileged) => {
4313                    return self.nonce().cmp(&other.nonce());
4314                }
4315                (TxType::Privileged, _) => return Ordering::Less,
4316                (_, TxType::Privileged) => return Ordering::Greater,
4317                _ => (),
4318            };
4319            match self.nonce().cmp(&other.nonce()) {
4320                Ordering::Equal => other.time().cmp(&self.time()),
4321                ordering => ordering,
4322            }
4323        }
4324    }
4325
4326    impl PartialOrd for MempoolTransaction {
4327        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
4328            Some(self.cmp(other))
4329        }
4330    }
4331}
4332
4333#[cfg(test)]
4334mod tests {
4335
4336    use super::*;
4337    use crate::types::{
4338        AuthorizationTuple, BlockBody, Receipt, compute_receipts_root, compute_transactions_root,
4339    };
4340    use ethereum_types::H160;
4341    use hex_literal::hex;
4342    use serde_impl::{AccessListEntry, GenericTransaction};
4343    use std::str::FromStr;
4344
4345    #[test]
4346    fn nonce_over_u64_max_maps_to_nonce_is_max() {
4347        // RLP list [ 0x010000000000000000 ] — nonce = 2**64 (9 significant bytes),
4348        // one past u64::MAX. EEST expects TransactionException.NONCE_IS_MAX here.
4349        let rlp = hex!("ca89010000000000000000");
4350        let decoder = Decoder::new(&rlp).unwrap();
4351        let err = decode_nonce_field(decoder).unwrap_err();
4352        assert!(
4353            err.to_string().contains("Nonce is max"),
4354            "unexpected error: {err}"
4355        );
4356    }
4357
4358    #[test]
4359    fn nonce_at_u64_max_decodes() {
4360        // RLP list [ 0xffffffffffffffff ] — nonce = u64::MAX (8 bytes), still valid.
4361        let rlp = hex!("c988ffffffffffffffff");
4362        let decoder = Decoder::new(&rlp).unwrap();
4363        let (nonce, _) = decode_nonce_field(decoder).unwrap();
4364        assert_eq!(nonce, u64::MAX);
4365    }
4366
4367    #[test]
4368    fn legacy_chain_id_handles_out_of_range_v_without_underflow() {
4369        // A legacy transaction decodes `v` as an arbitrary U256, so a malformed tx can
4370        // carry any value. `derive_legacy_chain_id` must return None for the pre-EIP-155
4371        // values (27/28) and any v < 35 rather than underflowing `v - 35`, which panics
4372        // in debug and wraps to a bogus chain id in release. This is reachable on the
4373        // block-import path (every tx's chain id is now checked pre-execution).
4374        let legacy_chain_id = |v: u64| {
4375            Transaction::LegacyTransaction(LegacyTransaction {
4376                v: U256::from(v),
4377                ..Default::default()
4378            })
4379            .chain_id()
4380        };
4381        for v in [0u64, 1, 26, 27, 28, 34] {
4382            assert_eq!(legacy_chain_id(v), None, "v={v} must yield no chain id");
4383        }
4384        // EIP-155 values (v = chain_id * 2 + 35/36) still derive correctly.
4385        assert_eq!(legacy_chain_id(35), Some(0));
4386        assert_eq!(legacy_chain_id(36), Some(0));
4387        assert_eq!(legacy_chain_id(37), Some(1));
4388    }
4389
4390    #[test]
4391    fn test_compute_transactions_root() {
4392        let mut body = BlockBody::empty();
4393        let tx = LegacyTransaction {
4394            nonce: 0,
4395            gas_price: U256::from(0x0a),
4396            gas: 0x05f5e100,
4397            to: TxKind::Call(hex!("1000000000000000000000000000000000000000").into()),
4398            value: 0.into(),
4399            data: Default::default(),
4400            v: U256::from(0x1b),
4401            r: U256::from_big_endian(&hex!(
4402                "7e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37"
4403            )),
4404            s: U256::from_big_endian(&hex!(
4405                "5f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509b"
4406            )),
4407            ..Default::default()
4408        };
4409        body.transactions.push(Transaction::LegacyTransaction(tx));
4410        let expected_root =
4411            hex!("8151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcb");
4412        let result = compute_transactions_root(&body.transactions, &ethrex_crypto::NativeCrypto);
4413
4414        assert_eq!(result, expected_root.into());
4415    }
4416    #[test]
4417    fn test_compute_hash() {
4418        // taken from Hive
4419        let tx_eip2930 = EIP2930Transaction {
4420            chain_id: 3503995874084926u64,
4421            nonce: 7,
4422            gas_price: U256::from(0x2dbf1f9a_u64),
4423            gas_limit: 0x186A0,
4424            to: TxKind::Call(hex!("7dcd17433742f4c0ca53122ab541d0ba67fc27df").into()),
4425            value: 2.into(),
4426            data: Bytes::from(&b"\xdbS\x06$\x8e\x03\x13\xe7emit"[..]),
4427            access_list: vec![(
4428                hex!("7dcd17433742f4c0ca53122ab541d0ba67fc27df").into(),
4429                vec![
4430                    hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
4431                    hex!("a3d07a7d68fbd49ec2f8e6befdd86c885f86c272819f6f345f365dec35ae6707").into(),
4432                ],
4433            )],
4434            signature_y_parity: false,
4435            signature_r: U256::from_dec_str(
4436                "75813812796588349127366022588733264074091236448495248199152066031778895768879",
4437            )
4438            .unwrap(),
4439            signature_s: U256::from_dec_str(
4440                "25476208226281085290728123165613764315157904411823916642262684106502155457829",
4441            )
4442            .unwrap(),
4443            ..Default::default()
4444        };
4445        let tx = Transaction::EIP2930Transaction(tx_eip2930);
4446
4447        let expected_hash =
4448            hex!("a0762610d794acddd2dca15fb7c437ada3611c886f3bea675d53d8da8a6c41b2");
4449        let hash = tx.compute_hash(&NativeCrypto);
4450        assert_eq!(hash, expected_hash.into());
4451    }
4452
4453    #[test]
4454    fn test_compute_receipts_root() {
4455        // example taken from
4456        // https://github.com/ethereum/go-ethereum/blob/f8aa62353666a6368fb3f1a378bd0a82d1542052/cmd/evm/testdata/1/exp.json#L18
4457        let tx_type = TxType::Legacy;
4458        let succeeded = true;
4459        let cumulative_gas_used = 0x5208;
4460        let logs = vec![];
4461        let receipt = Receipt::new(tx_type, succeeded, cumulative_gas_used, logs);
4462
4463        let result = compute_receipts_root(&[receipt], &ethrex_crypto::NativeCrypto);
4464        let expected_root =
4465            hex!("056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2");
4466        assert_eq!(result, expected_root.into());
4467    }
4468
4469    #[test]
4470    fn legacy_tx_rlp_decode() {
4471        let encoded_tx = "f86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4";
4472        let encoded_tx_bytes = hex::decode(encoded_tx).unwrap();
4473        let tx = LegacyTransaction::decode(&encoded_tx_bytes).unwrap();
4474        let expected_tx = LegacyTransaction {
4475            nonce: 0,
4476            gas_price: U256::from(1001000000u64),
4477            gas: 63000,
4478            to: TxKind::Call(Address::from_slice(
4479                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4480            )),
4481            value: 3000000000000000_u64.into(),
4482            data: Bytes::new(),
4483            r: U256::from_str_radix(
4484                "151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d",
4485                16,
4486            )
4487            .unwrap(),
4488            s: U256::from_str_radix(
4489                "64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4",
4490                16,
4491            )
4492            .unwrap(),
4493            v: 6303851.into(),
4494            ..Default::default()
4495        };
4496        assert_eq!(tx, expected_tx);
4497    }
4498
4499    #[test]
4500    fn eip1559_tx_rlp_decode() {
4501        let encoded_tx = "f86c8330182480114e82f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee53800080c080a0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4";
4502        let encoded_tx_bytes = hex::decode(encoded_tx).unwrap();
4503        let tx = EIP1559Transaction::decode(&encoded_tx_bytes).unwrap();
4504        let expected_tx = EIP1559Transaction {
4505            nonce: 0,
4506            max_fee_per_gas: 78,
4507            max_priority_fee_per_gas: 17,
4508            to: TxKind::Call(Address::from_slice(
4509                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4510            )),
4511            value: 3000000000000000_u64.into(),
4512            data: Bytes::new(),
4513            signature_r: U256::from_str_radix(
4514                "151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d",
4515                16,
4516            )
4517            .unwrap(),
4518            signature_s: U256::from_str_radix(
4519                "64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4",
4520                16,
4521            )
4522            .unwrap(),
4523            signature_y_parity: false,
4524            chain_id: 3151908,
4525            gas_limit: 63000,
4526            access_list: vec![],
4527            ..Default::default()
4528        };
4529        assert_eq!(tx, expected_tx);
4530    }
4531
4532    #[test]
4533    fn deserialize_tx_kind() {
4534        let tx_kind_create = r#""""#;
4535        let tx_kind_call = r#""0x6177843db3138ae69679A54b95cf345ED759450d""#;
4536        let deserialized_tx_kind_create = TxKind::Create;
4537        let deserialized_tx_kind_call = TxKind::Call(Address::from_slice(
4538            &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4539        ));
4540        assert_eq!(
4541            deserialized_tx_kind_create,
4542            serde_json::from_str(tx_kind_create).unwrap()
4543        );
4544        assert_eq!(
4545            deserialized_tx_kind_call,
4546            serde_json::from_str(tx_kind_call).unwrap()
4547        )
4548    }
4549
4550    #[test]
4551    fn deserialize_tx_type() {
4552        let tx_type_eip2930 = r#""0x01""#;
4553        let tx_type_eip1559 = r#""0x02""#;
4554        let deserialized_tx_type_eip2930 = TxType::EIP2930;
4555        let deserialized_tx_type_eip1559 = TxType::EIP1559;
4556        assert_eq!(
4557            deserialized_tx_type_eip2930,
4558            serde_json::from_str(tx_type_eip2930).unwrap()
4559        );
4560        assert_eq!(
4561            deserialized_tx_type_eip1559,
4562            serde_json::from_str(tx_type_eip1559).unwrap()
4563        )
4564    }
4565
4566    #[test]
4567    fn deserialize_generic_transaction() {
4568        let generic_transaction = r#"{
4569            "type":"0x01",
4570            "nonce":"0x02",
4571            "to":"",
4572            "from":"0x6177843db3138ae69679A54b95cf345ED759450d",
4573            "gas":"0x5208",
4574            "value":"0x01",
4575            "input":"0x010203040506",
4576            "gasPrice":"0x07",
4577            "accessList": [
4578                {
4579                    "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4580                    "storageKeys": [
4581                        "0x000000000000000000000000000000000000000000000000000000000000000c",
4582                        "0x000000000000000000000000000000000000000000000000000000000000200b"
4583                    ]
4584                }
4585            ]
4586        }"#;
4587        let deserialized_generic_transaction = GenericTransaction {
4588            r#type: TxType::EIP2930,
4589            nonce: Some(2),
4590            to: TxKind::Create,
4591            from: Address::from_slice(
4592                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4593            ),
4594            gas: Some(0x5208),
4595            value: U256::from(1),
4596            input: Bytes::from(hex::decode("010203040506").unwrap()),
4597            gas_price: U256::from(7),
4598            max_priority_fee_per_gas: Default::default(),
4599            max_fee_per_gas: Default::default(),
4600            max_fee_per_blob_gas: Default::default(),
4601            access_list: vec![AccessListEntry {
4602                address: Address::from_slice(
4603                    &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4604                ),
4605                storage_keys: vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4606            }],
4607            fee_token: None,
4608            blob_versioned_hashes: Default::default(),
4609            blobs: Default::default(),
4610            wrapper_version: None,
4611            chain_id: Default::default(),
4612            authorization_list: None,
4613        };
4614        assert_eq!(
4615            deserialized_generic_transaction,
4616            serde_json::from_str(generic_transaction).unwrap()
4617        )
4618    }
4619
4620    #[test]
4621    fn deserialize_generic_transaction_with_data_and_input_fields() {
4622        let generic_transaction = r#"{
4623            "type":"0x01",
4624            "nonce":"0x02",
4625            "to":"",
4626            "from":"0x6177843db3138ae69679A54b95cf345ED759450d",
4627            "gas":"0x5208",
4628            "value":"0x01",
4629            "input":"0x010203040506",
4630            "data":"0x010203040506",
4631            "gasPrice":"0x07",
4632            "accessList": [
4633                {
4634                    "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4635                    "storageKeys": [
4636                        "0x000000000000000000000000000000000000000000000000000000000000000c",
4637                        "0x000000000000000000000000000000000000000000000000000000000000200b"
4638                    ]
4639                }
4640            ]
4641        }"#;
4642        let deserialized_generic_transaction = GenericTransaction {
4643            r#type: TxType::EIP2930,
4644            nonce: Some(2),
4645            to: TxKind::Create,
4646            from: Address::from_slice(
4647                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4648            ),
4649            gas: Some(0x5208),
4650            value: U256::from(1),
4651            input: Bytes::from(hex::decode("010203040506").unwrap()),
4652            gas_price: U256::from(7),
4653            max_priority_fee_per_gas: Default::default(),
4654            max_fee_per_gas: Default::default(),
4655            max_fee_per_blob_gas: Default::default(),
4656            access_list: vec![AccessListEntry {
4657                address: Address::from_slice(
4658                    &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4659                ),
4660                storage_keys: vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4661            }],
4662            fee_token: None,
4663            blob_versioned_hashes: Default::default(),
4664            blobs: Default::default(),
4665            wrapper_version: None,
4666            chain_id: Default::default(),
4667            authorization_list: None,
4668        };
4669        assert_eq!(
4670            deserialized_generic_transaction,
4671            serde_json::from_str(generic_transaction).unwrap()
4672        )
4673    }
4674
4675    #[test]
4676    fn deserialize_eip4844_transaction() {
4677        let eip4844_transaction = r#"{
4678            "chainId":"0x01",
4679            "nonce":"0x02",
4680            "maxPriorityFeePerGas":"0x01",
4681            "maxFeePerGas":"0x01",
4682            "gas":"0x5208",
4683            "to":"0x6177843db3138ae69679A54b95cf345ED759450d",
4684            "value":"0x01",
4685            "input":"0x3033",
4686            "accessList": [
4687                {
4688                    "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4689                    "storageKeys": [
4690                        "0x000000000000000000000000000000000000000000000000000000000000000c",
4691                        "0x000000000000000000000000000000000000000000000000000000000000200b"
4692                    ]
4693                }
4694            ],
4695            "maxFeePerBlobGas":"0x03",
4696            "blobVersionedHashes": [
4697                    "0x0000000000000000000000000000000000000000000000000000000000000001",
4698                    "0x0000000000000000000000000000000000000000000000000000000000000002"
4699            ],
4700            "yParity":"0x0",
4701            "r": "0x01",
4702            "s": "0x02"
4703        }"#;
4704        let deserialized_eip4844_transaction = EIP4844Transaction {
4705            chain_id: 0x01,
4706            nonce: 0x02,
4707            to: Address::from_slice(
4708                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4709            ),
4710            max_priority_fee_per_gas: 1,
4711            max_fee_per_gas: 1,
4712            max_fee_per_blob_gas: U256::from(0x03),
4713            gas: 0x5208,
4714            value: U256::from(0x01),
4715            // 03 in hex is 0x3033, that's why the 'input' has that number.
4716            data: Bytes::from_static(b"03"),
4717            access_list: vec![(
4718                Address::from_slice(
4719                    &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4720                ),
4721                vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4722            )],
4723            blob_versioned_hashes: vec![H256::from_low_u64_be(1), H256::from_low_u64_be(2)],
4724            signature_y_parity: false,
4725            signature_r: U256::from(0x01),
4726            signature_s: U256::from(0x02),
4727            ..Default::default()
4728        };
4729
4730        assert_eq!(
4731            deserialized_eip4844_transaction,
4732            serde_json::from_str(eip4844_transaction).unwrap()
4733        )
4734    }
4735
4736    #[test]
4737    fn serialize_deserialize_transaction() {
4738        let eip1559 = EIP1559Transaction {
4739            chain_id: 65536999,
4740            nonce: 1,
4741            max_priority_fee_per_gas: 1000,
4742            max_fee_per_gas: 2000,
4743            gas_limit: 21000,
4744            to: TxKind::Call(H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B0").unwrap()),
4745            value: U256::from(100000),
4746            data: Bytes::from_static(b"03"),
4747            access_list: vec![(
4748                H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B3").unwrap(),
4749                vec![H256::zero()],
4750            )],
4751            signature_y_parity: true,
4752            signature_r: U256::one(),
4753            signature_s: U256::zero(),
4754            ..Default::default()
4755        };
4756        let tx_to_serialize = Transaction::EIP1559Transaction(eip1559.clone());
4757        let serialized = serde_json::to_string(&tx_to_serialize).expect("Failed to serialize");
4758
4759        let deserialized_tx: Transaction =
4760            serde_json::from_str(&serialized).expect("Failed to deserialize");
4761
4762        assert!(deserialized_tx.tx_type() == TxType::EIP1559);
4763
4764        if let Transaction::EIP1559Transaction(tx) = deserialized_tx {
4765            assert_eq!(tx, eip1559);
4766        }
4767    }
4768
4769    #[test]
4770    fn serialize_deserialize_eip7702transaction() {
4771        let eip7702 = EIP7702Transaction {
4772            chain_id: 65536999,
4773            nonce: 1,
4774            max_priority_fee_per_gas: 1000,
4775            max_fee_per_gas: 2000,
4776            gas_limit: 21000,
4777            to: Address::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B0").unwrap(),
4778            value: U256::from(100000),
4779            data: Bytes::from_static(b"03"),
4780            access_list: vec![],
4781            signature_y_parity: true,
4782            signature_r: U256::one(),
4783            signature_s: U256::zero(),
4784            authorization_list: vec![AuthorizationTuple {
4785                chain_id: U256::from(65536999),
4786                address: H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B1").unwrap(),
4787                nonce: 2,
4788                y_parity: U256::one(),
4789                r_signature: U256::from(22),
4790                s_signature: U256::from(37),
4791            }],
4792            ..Default::default()
4793        };
4794        let tx_to_serialize = Transaction::EIP7702Transaction(eip7702.clone());
4795        let serialized = serde_json::to_string(&tx_to_serialize).expect("Failed to serialize");
4796
4797        let deserialized_tx: Transaction =
4798            serde_json::from_str(&serialized).expect("Failed to deserialize");
4799
4800        assert!(deserialized_tx.tx_type() == TxType::EIP7702);
4801
4802        if let Transaction::EIP7702Transaction(tx) = deserialized_tx {
4803            assert_eq!(tx, eip7702);
4804        }
4805    }
4806
4807    #[test]
4808    fn serialize_deserialize_privileged_l2_transaction() -> Result<(), RLPDecodeError> {
4809        let privileged_l2 = PrivilegedL2Transaction {
4810            chain_id: 65536999,
4811            nonce: 0,
4812            max_priority_fee_per_gas: 875000000,
4813            max_fee_per_gas: 875000000,
4814            gas_limit: 42000u64,
4815            to: TxKind::Call(
4816                Address::from_str("0x8943545177806ed17b9f23f0a21ee5948ecaa776").unwrap(),
4817            ),
4818            value: U256::from(500000000000000000000000000u128),
4819            data: Bytes::new(),
4820            access_list: vec![],
4821            from: Address::from_str("0x8943545177806ed17b9f23f0a21ee5948ecaa776").unwrap(),
4822            ..Default::default()
4823        };
4824
4825        let encoded = PrivilegedL2Transaction::encode_to_vec(&privileged_l2);
4826        println!("encoded length: {}", encoded.len());
4827        assert_eq!(encoded.len(), privileged_l2.length());
4828
4829        let deserialized_tx = PrivilegedL2Transaction::decode(&encoded)?;
4830
4831        assert_eq!(deserialized_tx, privileged_l2);
4832
4833        Ok(())
4834    }
4835
4836    #[test]
4837    fn test_legacy_transaction_into_generic() {
4838        let legacy_tx = LegacyTransaction {
4839            nonce: 1,
4840            gas_price: U256::from(20_000_000_000u64),
4841            gas: 21000,
4842            to: TxKind::Call(
4843                Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4844            ),
4845            value: U256::from(1_000_000_000_000_000_000u64),
4846            data: Bytes::default(),
4847            v: U256::from(27),
4848            r: U256::from(1),
4849            s: U256::from(1),
4850            ..Default::default()
4851        };
4852
4853        let generic_tx: GenericTransaction = legacy_tx.into();
4854        assert_eq!(generic_tx.r#type, TxType::Legacy);
4855        assert_eq!(generic_tx.nonce, Some(1));
4856        assert_eq!(generic_tx.gas_price, U256::from(20_000_000_000u64));
4857        assert_eq!(generic_tx.gas, Some(21000));
4858        assert_eq!(generic_tx.max_priority_fee_per_gas, None);
4859        assert_eq!(generic_tx.max_fee_per_gas, None);
4860        assert_eq!(generic_tx.access_list.len(), 0);
4861        assert_eq!(generic_tx.chain_id, None);
4862    }
4863
4864    #[test]
4865    fn test_eip2930_transaction_into_generic() {
4866        let access_list = vec![(
4867            Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4868            vec![
4869                H256::from_str(
4870                    "0x1234567890123456789012345678901234567890123456789012345678901234",
4871                )
4872                .unwrap(),
4873            ],
4874        )];
4875
4876        let eip2930_tx = EIP2930Transaction {
4877            chain_id: 1,
4878            nonce: 1,
4879            gas_price: U256::from(20_000_000_000u64),
4880            gas_limit: 21000,
4881            to: TxKind::Call(
4882                Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4883            ),
4884            value: U256::from(1_000_000_000_000_000_000u64),
4885            data: Bytes::default(),
4886            access_list: access_list.clone(),
4887            signature_y_parity: false,
4888            signature_r: U256::from(1),
4889            signature_s: U256::from(1),
4890            ..Default::default()
4891        };
4892
4893        let generic_tx: GenericTransaction = eip2930_tx.into();
4894        assert_eq!(generic_tx.r#type, TxType::EIP2930);
4895        assert_eq!(generic_tx.nonce, Some(1));
4896        assert_eq!(generic_tx.gas_price, U256::from(20_000_000_000u64));
4897        assert_eq!(generic_tx.gas, Some(21000));
4898        assert_eq!(generic_tx.max_priority_fee_per_gas, None);
4899        assert_eq!(generic_tx.max_fee_per_gas, None);
4900        assert_eq!(generic_tx.chain_id, Some(1));
4901        assert_eq!(generic_tx.access_list.len(), 1);
4902        assert_eq!(generic_tx.access_list[0].address, access_list[0].0);
4903        assert_eq!(generic_tx.access_list[0].storage_keys, access_list[0].1);
4904    }
4905
4906    #[test]
4907    fn recover_address_rejects_high_s_signatures() {
4908        use ethrex_crypto::NativeCrypto;
4909        use k256::ecdsa::SigningKey;
4910
4911        let crypto = NativeCrypto;
4912
4913        // 1. Setup: Create a signer and a message
4914        // A random private key for testing
4915        let private_key = hex!("4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318");
4916        let signing_key = SigningKey::from_bytes(&private_key.into()).expect("Valid private key");
4917
4918        // The message we want to sign
4919        let msg = b"Test message for high-s signature rejection";
4920        // Calculate the Keccak256 hash of the message (the payload)
4921        let payload = keccak(msg).to_fixed_bytes();
4922
4923        // 2. Generate a valid low-s signature
4924        // k256's sign_prehash_recoverable produces canonical low-s signatures by default.
4925        let (signature, recovery_id) = signing_key
4926            .sign_prehash_recoverable(&payload)
4927            .expect("Signing failed");
4928
4929        // 3. Construct the signature bytes (r||s||v, 65 bytes)
4930        let mut sig_bytes = [0u8; 65];
4931        sig_bytes[..64].copy_from_slice(&signature.to_bytes());
4932        sig_bytes[64] = recovery_id.to_byte();
4933
4934        // 4. Verify that the valid low-s signature recovers the correct address
4935        // Calculate the expected address from the public key
4936        let uncompressed_pub = signing_key.verifying_key().to_encoded_point(false);
4937        let pub_hash = ethrex_crypto::keccak::keccak_hash(&uncompressed_pub.as_bytes()[1..]);
4938        let expected_address = Address::from_slice(&pub_hash[12..]);
4939
4940        let recovered = crypto
4941            .recover_signer(&sig_bytes, &payload)
4942            .expect("Valid low-s signature should recover successfully");
4943        assert_eq!(recovered, expected_address, "Recovered address mismatch");
4944
4945        // 5. Create a high-s signature: s' = N - s
4946        // The curve order N for secp256k1
4947        let n = U256::from_big_endian(&hex!(
4948            "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"
4949        ));
4950        let s = U256::from_big_endian(&sig_bytes[32..64]);
4951
4952        // Ensure the generated signature was indeed low-s (standard requirement)
4953        let half_n = n / 2;
4954        assert!(
4955            s <= half_n,
4956            "Generated signature was not low-s, cannot test high-s rejection"
4957        );
4958
4959        // Calculate high-s
4960        let s_high = n - s;
4961
4962        let mut sig_high_bytes = sig_bytes;
4963        // Replace s with s_high
4964        sig_high_bytes[32..64].copy_from_slice(&s_high.to_big_endian());
4965        // When flipping s to -s mod N, we must also flip the recovery ID (v) to maintain validity of the point R
4966        sig_high_bytes[64] ^= 1;
4967
4968        // 6. Verify that the high-s signature is rejected
4969        // EIP-2 requires rejecting s > N/2 to prevent malleability
4970        assert!(
4971            crypto.recover_signer(&sig_high_bytes, &payload).is_err(),
4972            "High-s signature should be rejected (EIP-2 compliance)"
4973        );
4974    }
4975
4976    #[test]
4977    fn encode_decode_low_size_tx() {
4978        let tx = Transaction::EIP2930Transaction(EIP2930Transaction::default());
4979        // Encode a separate copy so the original's cached_canonical stays uninit,
4980        // avoiding a false PartialEq mismatch with the decoded (uncached) tx.
4981        let tx_to_encode = tx.clone();
4982        let encoded = tx_to_encode.encode_to_vec();
4983        let decoded_tx = Transaction::decode(&encoded).unwrap();
4984        assert_eq!(tx, decoded_tx);
4985    }
4986
4987    #[test]
4988    fn test_eip1559_simple_transfer_size() {
4989        let tx = Transaction::EIP1559Transaction(EIP1559Transaction::default());
4990        assert_eq!(tx.encode_to_vec().len(), EIP1559_DEFAULT_SERIALIZED_LENGTH);
4991    }
4992
4993    // ── Frame Transaction (EIP-8141) tests ──
4994
4995    fn make_test_frame_tx() -> FrameTransaction {
4996        FrameTransaction {
4997            chain_id: 1,
4998            nonce: 42,
4999            sender: Address::from_low_u64_be(0xABCD),
5000            frames: vec![
5001                Frame {
5002                    mode: FrameMode::Verify as u8,
5003                    flags: 0x03, // APPROVE_PAYMENT_AND_EXECUTION
5004                    target: Some(Address::from_low_u64_be(0xABCD)),
5005                    gas_limit: 100_000,
5006                    value: U256::zero(),
5007                    data: Bytes::from_static(b"verify_data"),
5008                },
5009                Frame {
5010                    mode: FrameMode::Sender as u8,
5011                    flags: 0x00,
5012                    target: Some(Address::from_low_u64_be(0x1234)),
5013                    gas_limit: 200_000,
5014                    value: U256::zero(),
5015                    data: Bytes::from_static(b"call_data"),
5016                },
5017            ],
5018            signatures: vec![FrameSignature {
5019                scheme: FRAME_SIG_SCHEME_SECP256K1,
5020                signer: Some(Address::from_low_u64_be(0xABCD)),
5021                msg: Bytes::new(),
5022                signature: Bytes::from(vec![0u8; 65]),
5023            }],
5024            max_priority_fee_per_gas: 1_000_000_000,
5025            max_fee_per_gas: 30_000_000_000,
5026            max_fee_per_blob_gas: U256::zero(),
5027            blob_versioned_hashes: vec![],
5028            inner_hash: OnceCell::new(),
5029            cached_canonical: OnceCell::new(),
5030        }
5031    }
5032
5033    #[test]
5034    fn atomic_batch_flag_valid_on_default_and_sender_frames() {
5035        // Per EIP-8141 the atomic batch flag is valid on DEFAULT and SENDER
5036        // frames, and a batch never contains a VERIFY frame.
5037        let mut tx = make_test_frame_tx();
5038        tx.frames = vec![
5039            Frame {
5040                mode: FrameMode::Default as u8,
5041                flags: 0x04, // atomic batch
5042                target: Some(Address::from_low_u64_be(0xB0B)),
5043                gas_limit: 21_000,
5044                value: U256::zero(),
5045                data: Bytes::new(),
5046            },
5047            Frame {
5048                mode: FrameMode::Sender as u8,
5049                flags: 0x04, // atomic batch
5050                target: Some(Address::from_low_u64_be(0xB0B)),
5051                gas_limit: 21_000,
5052                value: U256::zero(),
5053                data: Bytes::new(),
5054            },
5055            Frame {
5056                mode: FrameMode::Sender as u8,
5057                flags: 0x00, // terminator: no flag
5058                target: Some(Address::from_low_u64_be(0xCAFE)),
5059                gas_limit: 21_000,
5060                value: U256::zero(),
5061                data: Bytes::new(),
5062            },
5063        ];
5064        assert!(tx.validate_static_constraints().is_ok());
5065    }
5066
5067    #[test]
5068    fn atomic_batch_flag_on_last_frame_still_invalid() {
5069        let mut tx = make_test_frame_tx();
5070        tx.frames = vec![Frame {
5071            mode: FrameMode::Sender as u8,
5072            flags: 0x04,
5073            target: Some(Address::from_low_u64_be(0xCAFE)),
5074            gas_limit: 21_000,
5075            value: U256::zero(),
5076            data: Bytes::new(),
5077        }];
5078        let err = tx.validate_static_constraints().unwrap_err();
5079        assert!(err.contains("atomic batch flag on last frame"), "{err}");
5080    }
5081
5082    #[test]
5083    fn test_frame_rlp_roundtrip() {
5084        let frame = Frame {
5085            mode: FrameMode::Verify as u8,
5086            flags: 0x03,
5087            target: Some(Address::from_low_u64_be(0x1234)),
5088            gas_limit: 50_000,
5089            value: U256::zero(),
5090            data: Bytes::from_static(b"hello"),
5091        };
5092        let encoded = frame.encode_to_vec();
5093        let decoded = Frame::decode(&encoded).unwrap();
5094        assert_eq!(frame, decoded);
5095    }
5096
5097    #[test]
5098    fn test_frame_null_target_rlp_roundtrip() {
5099        let frame = Frame {
5100            mode: FrameMode::Default as u8,
5101            flags: 0x00,
5102            target: None,
5103            gas_limit: 10_000,
5104            value: U256::zero(),
5105            data: Bytes::from_static(b"deploy"),
5106        };
5107        let encoded = frame.encode_to_vec();
5108        let decoded = Frame::decode(&encoded).unwrap();
5109        assert_eq!(frame, decoded);
5110    }
5111
5112    #[test]
5113    fn test_frame_mode_with_flags_rlp_roundtrip() {
5114        // Test basic modes
5115        for mode_val in [0u8, 1, 2] {
5116            let frame = Frame {
5117                mode: mode_val,
5118                flags: if mode_val == 1 { 0x03 } else { 0x00 },
5119                target: Some(Address::from_low_u64_be(0x1234)),
5120                gas_limit: 50_000,
5121                value: U256::zero(),
5122                data: Bytes::new(),
5123            };
5124            let encoded = frame.encode_to_vec();
5125            let decoded = Frame::decode(&encoded).unwrap();
5126            assert_eq!(frame, decoded);
5127        }
5128        // Test mode with scope restriction (bits 0-1 of flags) and atomic batch (bit 2 of flags)
5129        let frame = Frame {
5130            mode: 2,            // SENDER
5131            flags: 0x01 | 0x04, // scope=1 + atomic_batch
5132            target: Some(Address::from_low_u64_be(0x1234)),
5133            gas_limit: 50_000,
5134            value: U256::zero(),
5135            data: Bytes::new(),
5136        };
5137        assert_eq!(frame.execution_mode(), FrameMode::Sender);
5138        assert_eq!(frame.scope_restriction(), 1);
5139        assert!(frame.is_atomic_batch());
5140        let encoded = frame.encode_to_vec();
5141        let decoded = Frame::decode(&encoded).unwrap();
5142        assert_eq!(frame, decoded);
5143    }
5144
5145    #[test]
5146    fn frame_rlp_roundtrip_preserves_value() {
5147        let frame = Frame {
5148            mode: FrameMode::Sender as u8,
5149            flags: 0x00,
5150            target: Some(Address::from_low_u64_be(0xCAFE)),
5151            gas_limit: 100_000,
5152            value: U256::from(1_000_000_000_000_000u64), // 0.001 ETH
5153            data: Bytes::from_static(b"hello"),
5154        };
5155        let encoded = frame.encode_to_vec();
5156        let decoded = Frame::decode(&encoded).unwrap();
5157        assert_eq!(frame, decoded);
5158        assert_eq!(decoded.value, U256::from(1_000_000_000_000_000u64));
5159    }
5160
5161    #[test]
5162    fn test_frame_transaction_rlp_roundtrip() {
5163        let tx = make_test_frame_tx();
5164        let encoded = tx.encode_to_vec();
5165        let decoded = FrameTransaction::decode(&encoded).unwrap();
5166        assert_eq!(tx, decoded);
5167    }
5168
5169    #[test]
5170    fn test_frame_transaction_canonical_roundtrip() {
5171        let tx = Transaction::FrameTransaction(make_test_frame_tx());
5172        let encoded = tx.encode_canonical_to_vec();
5173        let decoded = Transaction::decode_canonical(&encoded).unwrap();
5174        // Compare hashes since OnceCell cached state differs after encode
5175        assert_eq!(
5176            tx.hash(&ethrex_crypto::NativeCrypto),
5177            decoded.hash(&ethrex_crypto::NativeCrypto)
5178        );
5179        // Also verify core fields match
5180        assert_eq!(tx.tx_type(), decoded.tx_type());
5181        assert_eq!(tx.nonce(), decoded.nonce());
5182        assert_eq!(tx.chain_id(), decoded.chain_id());
5183    }
5184
5185    #[test]
5186    fn test_frame_transaction_sig_hash_covers_all_frame_data() {
5187        // Updated for frame data is NO LONGER elided.
5188        let tx = make_test_frame_tx();
5189        let hash1 = tx.compute_sig_hash();
5190
5191        // Changing VERIFY frame data now DOES change the sig_hash.
5192        let mut tx2 = tx.clone();
5193        tx2.frames[0].data = Bytes::from_static(b"completely_different_verify_data");
5194        let hash2 = tx2.compute_sig_hash();
5195        assert_ne!(hash1, hash2);
5196
5197        // Changing SENDER frame data also produces a different hash.
5198        let mut tx3 = tx;
5199        tx3.frames[1].data = Bytes::from_static(b"different_call_data");
5200        let hash3 = tx3.compute_sig_hash();
5201        assert_ne!(hash1, hash3);
5202    }
5203
5204    #[test]
5205    fn test_frame_transaction_accessor_methods() {
5206        let frame_tx = make_test_frame_tx();
5207        let tx = Transaction::FrameTransaction(frame_tx);
5208
5209        assert_eq!(tx.tx_type(), TxType::Frame);
5210        assert_eq!(tx.nonce(), 42);
5211        assert_eq!(tx.chain_id(), Some(1));
5212        assert_eq!(tx.to(), TxKind::Call(Address::from_low_u64_be(0xABCD)));
5213        assert_eq!(tx.value(), U256::zero());
5214        assert_eq!(tx.data(), &Bytes::new());
5215        assert!(tx.access_list().is_empty());
5216        assert!(tx.authorization_list().is_none());
5217        assert_eq!(tx.max_priority_fee(), Some(1_000_000_000));
5218        assert_eq!(tx.max_fee_per_gas(), Some(30_000_000_000));
5219        assert_eq!(tx.max_fee_per_blob_gas(), None); // no blobs
5220        assert!(!tx.is_contract_creation());
5221        // sender returns explicit sender, no ECDSA
5222        assert_eq!(
5223            tx.sender(&ethrex_crypto::NativeCrypto).unwrap(),
5224            Address::from_low_u64_be(0xABCD)
5225        );
5226        // gas_limit = intrinsic + calldata_cost + sum(frame.gas_limit)
5227        assert!(tx.gas_limit() >= 300_000); // at least sum of frame gas limits
5228    }
5229
5230    #[test]
5231    fn test_frame_transaction_hash_uses_oncecell() {
5232        let tx = Transaction::FrameTransaction(make_test_frame_tx());
5233        let h1 = tx.hash(&ethrex_crypto::NativeCrypto);
5234        let h2 = tx.hash(&ethrex_crypto::NativeCrypto);
5235        assert_eq!(h1, h2);
5236    }
5237
5238    #[test]
5239    fn frame_transaction_rlp_roundtrip_preserves_fields() {
5240        let tx = make_test_frame_tx();
5241        let mut buf = Vec::new();
5242        tx.encode(&mut buf);
5243        let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5244        assert!(rest.is_empty());
5245        assert_eq!(decoded.chain_id, tx.chain_id);
5246        assert_eq!(decoded.nonce, tx.nonce);
5247        assert_eq!(decoded.sender, tx.sender);
5248    }
5249
5250    #[test]
5251    fn frame_transaction_variant_is_exposed_on_transaction_enum() {
5252        let tx = Transaction::FrameTransaction(make_test_frame_tx());
5253        assert!(matches!(tx, Transaction::FrameTransaction(_)));
5254    }
5255
5256    #[test]
5257    fn frame_transaction_serializes_signatures_as_array() {
5258        // RPC must expose the full signature objects, not just a count.
5259        let tx = make_test_frame_tx();
5260        let json = serde_json::to_value(&tx).unwrap();
5261        let sigs = json
5262            .get("signatures")
5263            .expect("signatures field present")
5264            .as_array()
5265            .expect("signatures serialized as an array");
5266        assert_eq!(sigs.len(), tx.signatures.len());
5267        assert_eq!(sigs[0].get("scheme").unwrap(), "0x1");
5268        assert!(sigs[0].get("signer").is_some());
5269        assert!(sigs[0].get("signature").is_some());
5270        assert!(sigs[0].get("msg").is_some());
5271    }
5272
5273    fn make_frame_tx_with_gas_limits(limits: Vec<u64>) -> FrameTransaction {
5274        let frames = limits
5275            .into_iter()
5276            .map(|gl| Frame {
5277                mode: FrameMode::Sender as u8,
5278                flags: 0x00,
5279                target: Some(Address::from_low_u64_be(0x1234)),
5280                gas_limit: gl,
5281                value: U256::zero(),
5282                data: Bytes::new(),
5283            })
5284            .collect();
5285        FrameTransaction {
5286            chain_id: 1,
5287            nonce: 0,
5288            sender: Address::from_low_u64_be(0xABCD),
5289            frames,
5290            signatures: vec![],
5291            max_priority_fee_per_gas: 1_000_000_000,
5292            max_fee_per_gas: 30_000_000_000,
5293            max_fee_per_blob_gas: U256::zero(),
5294            blob_versioned_hashes: vec![],
5295            inner_hash: OnceCell::new(),
5296            cached_canonical: OnceCell::new(),
5297        }
5298    }
5299
5300    #[test]
5301    fn per_frame_gas_limit_above_i64_max_is_rejected() {
5302        let tx = make_frame_tx_with_gas_limits(vec![(i64::MAX as u64) + 1]);
5303        let err = tx.validate_static_constraints().unwrap_err();
5304        assert!(err.contains("exceeds 2**63-1"), "unexpected error: {err}");
5305    }
5306
5307    #[test]
5308    fn cumulative_frame_gas_limit_above_i64_max_is_rejected() {
5309        let half = (i64::MAX as u64) / 2 + 1;
5310        let tx = make_frame_tx_with_gas_limits(vec![half, half]);
5311        let err = tx.validate_static_constraints().unwrap_err();
5312        assert!(err.contains("cumulative"), "unexpected error: {err}");
5313    }
5314
5315    #[test]
5316    fn cumulative_frame_gas_limit_equal_to_i64_max_is_accepted() {
5317        let a = (i64::MAX as u64) / 2;
5318        let b = i64::MAX as u64 - a;
5319        let tx = make_frame_tx_with_gas_limits(vec![a, b]);
5320        tx.validate_static_constraints()
5321            .expect("exact i64::MAX total should be accepted");
5322    }
5323
5324    #[test]
5325    fn empty_frames_list_is_rejected_by_count_check_not_gas_check() {
5326        // The frame-count check fires before the gas-limit accumulator runs,
5327        // so an empty frame list surfaces the count error, not a gas error.
5328        let tx = make_frame_tx_with_gas_limits(vec![]);
5329        let err = tx.validate_static_constraints().unwrap_err();
5330        assert!(err.contains("between 1 and"), "unexpected error: {err}");
5331    }
5332
5333    #[test]
5334    fn sig_hash_covers_frame_value() {
5335        // Changing `value` on any frame (SENDER or VERIFY) must change the
5336        // canonical signature hash (all frame data covered).
5337        let tx = make_test_frame_tx();
5338        let baseline = tx.compute_sig_hash();
5339
5340        let mut with_sender_value = tx.clone();
5341        with_sender_value.frames[1].value = U256::from(1u64);
5342        assert_ne!(
5343            baseline,
5344            with_sender_value.compute_sig_hash(),
5345            "sig_hash must change when a SENDER frame's value changes"
5346        );
5347
5348        let mut with_verify_value = tx.clone();
5349        with_verify_value.frames[0].value = U256::from(1u64);
5350        assert_ne!(
5351            baseline,
5352            with_verify_value.compute_sig_hash(),
5353            "sig_hash must change when a VERIFY frame's value changes"
5354        );
5355
5356        // VERIFY.data is now covered too (EIP-8141 removed elision).
5357        let mut with_verify_data = tx;
5358        with_verify_data.frames[0].data = Bytes::from_static(b"different_verify_data");
5359        assert_ne!(
5360            baseline,
5361            with_verify_data.compute_sig_hash(),
5362            "VERIFY.data is now covered by sig_hash (no longer elided)"
5363        );
5364    }
5365
5366    #[test]
5367    fn validate_static_constraints_rejects_nonzero_value_on_non_sender_frames() {
5368        // VERIFY frame with non-zero value must be rejected.
5369        let verify_tx = FrameTransaction {
5370            chain_id: 1,
5371            nonce: 0,
5372            sender: Address::from_low_u64_be(0xABCD),
5373            frames: vec![Frame {
5374                mode: FrameMode::Verify as u8,
5375                flags: 0x01,
5376                target: None,
5377                gas_limit: 50_000,
5378                value: U256::from(1u64),
5379                data: Bytes::new(),
5380            }],
5381            signatures: vec![],
5382            max_priority_fee_per_gas: 1_000_000_000,
5383            max_fee_per_gas: 30_000_000_000,
5384            max_fee_per_blob_gas: U256::zero(),
5385            blob_versioned_hashes: vec![],
5386            inner_hash: OnceCell::new(),
5387            cached_canonical: OnceCell::new(),
5388        };
5389        let err = verify_tx.validate_static_constraints().unwrap_err();
5390        assert!(
5391            err.contains("non-zero value only allowed in SENDER mode"),
5392            "unexpected error for VERIFY: {err}"
5393        );
5394
5395        // DEFAULT frame with non-zero value must be rejected.
5396        let default_tx = FrameTransaction {
5397            frames: vec![Frame {
5398                mode: FrameMode::Default as u8,
5399                flags: 0x00,
5400                target: Some(Address::from_low_u64_be(0x1234)),
5401                gas_limit: 50_000,
5402                value: U256::from(1u64),
5403                data: Bytes::new(),
5404            }],
5405            ..verify_tx
5406        };
5407        let err = default_tx.validate_static_constraints().unwrap_err();
5408        assert!(
5409            err.contains("non-zero value only allowed in SENDER mode"),
5410            "unexpected error for DEFAULT: {err}"
5411        );
5412
5413        // SENDER frame with a non-zero value must remain valid.
5414        let sender_tx = FrameTransaction {
5415            frames: vec![Frame {
5416                mode: FrameMode::Sender as u8,
5417                flags: 0x00,
5418                target: Some(Address::from_low_u64_be(0x1234)),
5419                gas_limit: 50_000,
5420                value: U256::from(1u64),
5421                data: Bytes::new(),
5422            }],
5423            ..default_tx
5424        };
5425        sender_tx
5426            .validate_static_constraints()
5427            .expect("SENDER frames may carry non-zero value");
5428    }
5429
5430    // ── EIP-8141 fork-gate predicate tests ──
5431
5432    fn chain_config_with_hegota(hegota_time: Option<u64>) -> crate::types::ChainConfig {
5433        crate::types::ChainConfig {
5434            hegota_time,
5435            ..Default::default()
5436        }
5437    }
5438
5439    #[test]
5440    fn test_frame_tx_pre_fork_chain_config_rejects() {
5441        let cfg = chain_config_with_hegota(None);
5442        assert!(!cfg.is_hegota_activated(0));
5443        assert!(!cfg.is_hegota_activated(u64::MAX));
5444    }
5445
5446    #[test]
5447    fn test_frame_tx_post_fork_admits() {
5448        let cfg = chain_config_with_hegota(Some(1000));
5449        assert!(cfg.is_hegota_activated(2000));
5450    }
5451
5452    #[test]
5453    fn test_frame_tx_fork_boundary_admits() {
5454        let activation_time = 1_700_000_000u64;
5455        let cfg = chain_config_with_hegota(Some(activation_time));
5456        assert!(cfg.is_hegota_activated(activation_time));
5457        assert!(!cfg.is_hegota_activated(activation_time - 1));
5458    }
5459
5460    #[test]
5461    fn test_frame_tx_devnet_fork_epoch_zero_admits() {
5462        let cfg = chain_config_with_hegota(Some(0));
5463        assert!(cfg.is_hegota_activated(0));
5464        assert!(cfg.is_hegota_activated(1));
5465        assert!(cfg.is_hegota_activated(u64::MAX));
5466    }
5467
5468    #[test]
5469    fn test_frame_tx_pre_fork_rlp_roundtrip_still_decodes() {
5470        // RLP decoding is fork-unaware and must stay lossless so validators surface
5471        // a FrameTxPreFork error downstream rather than a corrupt-RLP error.
5472        let tx = make_test_frame_tx();
5473        let mut buf = Vec::new();
5474        tx.encode(&mut buf);
5475        let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5476        assert!(rest.is_empty());
5477        assert_eq!(decoded.chain_id, tx.chain_id);
5478        assert_eq!(decoded.nonce, tx.nonce);
5479        assert_eq!(decoded.sender, tx.sender);
5480    }
5481
5482    // ── EIP-8141 expiry verifier frame tests ──
5483
5484    fn expiry_frame(deadline: u64) -> Frame {
5485        Frame {
5486            mode: FrameMode::Verify as u8,
5487            flags: 0x00,
5488            target: Some(frame_tx_expiry_verifier()),
5489            gas_limit: 30_000,
5490            value: U256::zero(),
5491            data: Bytes::copy_from_slice(&deadline.to_be_bytes()),
5492        }
5493    }
5494
5495    #[test]
5496    fn expiry_verifier_frame_passes_static_validation() {
5497        let mut tx = make_test_frame_tx();
5498        tx.frames.insert(0, expiry_frame(1_700_000_000));
5499        assert!(tx.validate_static_constraints().is_ok());
5500    }
5501
5502    #[test]
5503    fn expiry_verifier_frame_rejects_bad_data_length() {
5504        let mut tx = make_test_frame_tx();
5505        let mut f = expiry_frame(0);
5506        f.data = Bytes::from_static(b"short");
5507        tx.frames.insert(0, f);
5508        let err = tx.validate_static_constraints().unwrap_err();
5509        assert!(err.contains("8 bytes"), "{err}");
5510    }
5511
5512    #[test]
5513    fn expiry_verifier_frame_rejects_nonzero_flags() {
5514        let mut tx = make_test_frame_tx();
5515        let mut f = expiry_frame(0);
5516        f.flags = 0x01;
5517        tx.frames.insert(0, f);
5518        let err = tx.validate_static_constraints().unwrap_err();
5519        assert!(err.contains("flags == 0"), "{err}");
5520    }
5521
5522    #[test]
5523    fn at_most_one_expiry_verifier_frame() {
5524        let mut tx = make_test_frame_tx();
5525        tx.frames.insert(0, expiry_frame(1));
5526        tx.frames.insert(0, expiry_frame(2));
5527        let err = tx.validate_static_constraints().unwrap_err();
5528        assert!(err.contains("more than one expiry"), "{err}");
5529    }
5530
5531    #[test]
5532    fn verify_frame_with_zero_scope_is_now_statically_valid() {
5533        // EIP-8141 has no the VERIFY-needs-nonzero-scope rule.
5534        // make_test_frame_tx() frame[0] is VERIFY with flags=0x03 targeting sender;
5535        // setting flags to 0 makes it a zero-scope VERIFY (not an expiry frame,
5536        // since target is the sender address, not EXPIRY_VERIFIER).
5537        let mut tx = make_test_frame_tx();
5538        tx.frames[0].flags = 0x00;
5539        assert!(tx.validate_static_constraints().is_ok());
5540    }
5541
5542    #[test]
5543    fn sig_hash_covers_expiry_deadline_and_all_verify_data() {
5544        // Updated for all frame data is now covered.
5545        let mut tx_a = make_test_frame_tx();
5546        tx_a.frames.insert(0, expiry_frame(100));
5547        let mut tx_b = make_test_frame_tx();
5548        tx_b.frames.insert(0, expiry_frame(200));
5549        // Different deadlines -> different sig hashes (expiry data covered).
5550        assert_ne!(tx_a.compute_sig_hash(), tx_b.compute_sig_hash());
5551
5552        // Different NON-expiry VERIFY data -> different sig hash (no longer elided).
5553        let tx_c = make_test_frame_tx();
5554        let mut tx_d = make_test_frame_tx();
5555        tx_d.frames[0].data = Bytes::from_static(b"different_verify_data");
5556        assert_ne!(tx_c.compute_sig_hash(), tx_d.compute_sig_hash());
5557    }
5558
5559    #[test]
5560    fn expiry_deadline_parses_or_none() {
5561        let mut tx = make_test_frame_tx();
5562        assert_eq!(tx.expiry_deadline(), None);
5563        tx.frames.insert(0, expiry_frame(1_700_000_123));
5564        assert_eq!(tx.expiry_deadline(), Some(1_700_000_123));
5565    }
5566
5567    #[test]
5568    fn signature_rlp_roundtrip() {
5569        let sig = FrameSignature {
5570            scheme: FRAME_SIG_SCHEME_P256,
5571            signer: Some(Address::from_low_u64_be(0x1234)),
5572            msg: Bytes::from(vec![7u8; 32]),
5573            signature: Bytes::from(vec![9u8; 128]),
5574        };
5575        let mut buf = Vec::new();
5576        sig.encode(&mut buf);
5577        let (decoded, rest) = FrameSignature::decode_unfinished(&buf).unwrap();
5578        assert!(rest.is_empty());
5579        assert_eq!(decoded, sig);
5580    }
5581
5582    #[test]
5583    fn frame_tx_with_signatures_rlp_roundtrip() {
5584        let tx = make_test_frame_tx();
5585        assert!(!tx.signatures.is_empty());
5586        let mut buf = Vec::new();
5587        tx.encode(&mut buf);
5588        let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5589        assert!(rest.is_empty());
5590        assert_eq!(decoded.signatures, tx.signatures);
5591        assert_eq!(decoded.frames, tx.frames);
5592        assert_eq!(decoded.nonce, tx.nonce);
5593    }
5594
5595    #[test]
5596    fn p2p_frame_transaction_rlp_roundtrip() {
5597        // A frame tx (EIP-8141) must survive a full P2PTransaction RLP
5598        // encode/decode round-trip so it can be served over the wire on request.
5599        let ft = make_test_frame_tx();
5600        assert!(!ft.signatures.is_empty());
5601        assert!(!ft.frames.is_empty());
5602        let original = P2PTransaction::FrameTransaction(ft);
5603
5604        let encoded = original.encode_to_vec();
5605        let (decoded, rest) = P2PTransaction::decode_unfinished(&encoded).unwrap();
5606        assert!(rest.is_empty());
5607        assert_eq!(decoded, original);
5608        assert_eq!(decoded.tx_type(), TxType::Frame);
5609
5610        // The decoded variant converts cleanly into a regular Transaction
5611        // (frame txs carry no blobs bundle).
5612        let as_tx: Transaction = decoded.try_into().unwrap();
5613        assert!(matches!(as_tx, Transaction::FrameTransaction(_)));
5614    }
5615
5616    #[test]
5617    fn static_validation_rejects_unknown_scheme() {
5618        let mut tx = make_test_frame_tx();
5619        tx.signatures[0].scheme = 3;
5620        assert!(
5621            tx.validate_static_constraints()
5622                .unwrap_err()
5623                .contains("unsupported scheme"),
5624        );
5625    }
5626
5627    #[test]
5628    fn static_validation_accepts_arbitrary_with_empty_signer() {
5629        let mut tx = make_test_frame_tx();
5630        // ARBITRARY (scheme 0) requires an empty signer and costs 100 verify gas.
5631        tx.signatures[0].scheme = FRAME_SIG_SCHEME_ARBITRARY;
5632        tx.signatures[0].signer = None;
5633        assert!(tx.validate_static_constraints().is_ok());
5634        assert_eq!(tx.signature_verification_cost(), 100);
5635    }
5636
5637    #[test]
5638    fn static_validation_rejects_arbitrary_with_signer() {
5639        let mut tx = make_test_frame_tx();
5640        tx.signatures[0].scheme = FRAME_SIG_SCHEME_ARBITRARY;
5641        tx.signatures[0].signer = Some(Address::from_low_u64_be(0xABCD));
5642        assert!(
5643            tx.validate_static_constraints()
5644                .unwrap_err()
5645                .contains("ARBITRARY signatures must not name a signer"),
5646        );
5647    }
5648
5649    #[test]
5650    fn static_validation_rejects_bad_msg_length() {
5651        let mut tx = make_test_frame_tx();
5652        tx.signatures[0].msg = Bytes::from(vec![1u8; 16]);
5653        assert!(
5654            tx.validate_static_constraints()
5655                .unwrap_err()
5656                .contains("32 bytes"),
5657        );
5658    }
5659
5660    #[test]
5661    fn static_validation_rejects_zero_explicit_msg() {
5662        let mut tx = make_test_frame_tx();
5663        tx.signatures[0].msg = Bytes::from(vec![0u8; 32]);
5664        assert!(
5665            tx.validate_static_constraints()
5666                .unwrap_err()
5667                .contains("zero digest"),
5668        );
5669    }
5670
5671    #[test]
5672    fn sig_hash_elides_empty_msg_signature_bytes() {
5673        let mut a = make_test_frame_tx();
5674        let mut b = make_test_frame_tx();
5675        // empty-msg signature: changing raw bytes must NOT change the hash.
5676        assert!(a.signatures[0].msg.is_empty());
5677        a.signatures[0].signature = Bytes::from(vec![1u8; 65]);
5678        b.signatures[0].signature = Bytes::from(vec![2u8; 65]);
5679        assert_eq!(a.compute_sig_hash(), b.compute_sig_hash());
5680    }
5681
5682    #[test]
5683    fn sig_hash_covers_frame_data_now() {
5684        let mut a = make_test_frame_tx();
5685        let mut b = make_test_frame_tx();
5686        // frame 0 is a VERIFY frame; its data is now COVERED (not elided).
5687        a.frames[0].data = Bytes::from_static(b"aaaa");
5688        b.frames[0].data = Bytes::from_static(b"bbbb");
5689        assert_ne!(a.compute_sig_hash(), b.compute_sig_hash());
5690    }
5691
5692    #[test]
5693    fn sig_hash_covers_explicit_msg_signature_bytes() {
5694        let mut a = make_test_frame_tx();
5695        let mut b = make_test_frame_tx();
5696        a.signatures[0].msg = Bytes::from(vec![9u8; 32]);
5697        b.signatures[0].msg = Bytes::from(vec![9u8; 32]);
5698        a.signatures[0].signature = Bytes::from(vec![1u8; 65]);
5699        b.signatures[0].signature = Bytes::from(vec![2u8; 65]);
5700        // explicit-msg signatures keep their bytes -> different hash.
5701        assert_ne!(a.compute_sig_hash(), b.compute_sig_hash());
5702    }
5703
5704    #[test]
5705    fn total_gas_limit_includes_signature_costs() {
5706        let mut tx = make_test_frame_tx();
5707        let base = tx.total_gas_limit();
5708        // Add a P256 signature; cost must rise by at least 6700 + its calldata.
5709        tx.signatures.push(FrameSignature {
5710            scheme: FRAME_SIG_SCHEME_P256,
5711            signer: Some(Address::from_low_u64_be(1)),
5712            msg: Bytes::new(),
5713            signature: Bytes::from(vec![0u8; 128]),
5714        });
5715        assert!(tx.total_gas_limit() >= base + 6700);
5716        assert_eq!(tx.signature_verification_cost(), 2800 + 6700);
5717    }
5718
5719    #[test]
5720    fn golden_frame_tx_rlp_and_sig_hash() {
5721        // Regression lock for the EIP-8141 signatures-list wire format. No
5722        // external EEST reference vectors exist yet;
5723        // these values are the current canonical output and must only change
5724        // with a deliberate, reviewed format change.
5725        let tx = FrameTransaction {
5726            chain_id: 1,
5727            nonce: 7,
5728            sender: Address::from_low_u64_be(0xABCD),
5729            frames: vec![
5730                Frame {
5731                    mode: 1,
5732                    flags: 3,
5733                    target: None,
5734                    gas_limit: 0x5208,
5735                    value: U256::zero(),
5736                    data: Bytes::from_static(&[0x11, 0x22]),
5737                },
5738                Frame {
5739                    mode: 2,
5740                    flags: 0,
5741                    target: Some(Address::from_low_u64_be(0x1234)),
5742                    gas_limit: 0x9c40,
5743                    value: U256::zero(),
5744                    data: Bytes::new(),
5745                },
5746            ],
5747            signatures: vec![FrameSignature {
5748                scheme: FRAME_SIG_SCHEME_SECP256K1,
5749                signer: Some(Address::from_low_u64_be(0xABCD)),
5750                msg: Bytes::new(),
5751                signature: Bytes::from(vec![0x01u8; 65]),
5752            }],
5753            max_priority_fee_per_gas: 0x3b9aca00,
5754            max_fee_per_gas: 0x6fc23ac00,
5755            max_fee_per_blob_gas: U256::zero(),
5756            blob_versioned_hashes: vec![],
5757            inner_hash: OnceCell::new(),
5758            cached_canonical: OnceCell::new(),
5759        };
5760
5761        let mut buf = Vec::new();
5762        tx.encode(&mut buf);
5763        let rlp_hex = hex::encode(&buf);
5764        // GOLDEN_RLP: obtained from first run
5765        assert_eq!(
5766            rlp_hex,
5767            "f8ab010794000000000000000000000000000000000000abcde8ca01038082520880821122dc0280940000000000000000000000000000000000001234829c408080f85cf85a0194000000000000000000000000000000000000abcd80b8410101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101843b9aca008506fc23ac0080c0"
5768        );
5769
5770        // Round-trips losslessly.
5771        let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5772        assert!(rest.is_empty());
5773        assert_eq!(decoded, tx);
5774
5775        let sig_hash = tx.compute_sig_hash();
5776        // GOLDEN_SIG_HASH: obtained from first run
5777        assert_eq!(
5778            format!("{:#x}", sig_hash),
5779            "0xe7dc3f33413fc69c09f9c690be154ded294954e497aeea6ce0010ba513f2f26d",
5780        );
5781
5782        // Elision invariant: changing empty-msg signature bytes must NOT change sig_hash.
5783        let mut tx2 = tx.clone();
5784        tx2.signatures[0].signature = Bytes::from(vec![0x02u8; 65]);
5785        assert_eq!(
5786            tx.compute_sig_hash(),
5787            tx2.compute_sig_hash(),
5788            "sig_hash must be independent of empty-msg signature bytes",
5789        );
5790    }
5791
5792    #[test]
5793    fn test_cost_without_base_fee_eip4844_includes_blob_gas() {
5794        // Regression test for mempool balance check: for EIP-4844 txs,
5795        // cost_without_base_fee() MUST include blob_gas_used * max_fee_per_blob_gas.
5796        // Every peer client (geth, reth, nethermind, erigon, besu) does this.
5797        use crate::constants::GAS_PER_BLOB;
5798
5799        let max_fee_per_gas: u64 = 100;
5800        let gas: u64 = 21_000;
5801        let value = U256::from(7u64);
5802        let max_fee_per_blob_gas = U256::from(50u64);
5803        let blob_count: usize = 1;
5804
5805        let tx = Transaction::EIP4844Transaction(EIP4844Transaction {
5806            max_fee_per_gas,
5807            gas,
5808            value,
5809            max_fee_per_blob_gas,
5810            blob_versioned_hashes: vec![H256::zero(); blob_count],
5811            ..Default::default()
5812        });
5813
5814        let got = tx.cost_without_base_fee().expect("cost is computable");
5815
5816        let gas_cost = U256::from(max_fee_per_gas) * U256::from(gas);
5817        let blob_gas = U256::from(GAS_PER_BLOB) * U256::from(blob_count as u64);
5818        let blob_cost = blob_gas * max_fee_per_blob_gas;
5819        let expected = gas_cost + blob_cost + value;
5820
5821        assert_eq!(
5822            got, expected,
5823            "blob-gas term missing from cost_without_base_fee() for EIP-4844"
5824        );
5825    }
5826}