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