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