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    pub fn gas_limit(&self) -> u64 {
1431        match self {
1432            Transaction::LegacyTransaction(tx) => tx.gas,
1433            Transaction::EIP2930Transaction(tx) => tx.gas_limit,
1434            Transaction::EIP1559Transaction(tx) => tx.gas_limit,
1435            Transaction::EIP7702Transaction(tx) => tx.gas_limit,
1436            Transaction::EIP4844Transaction(tx) => tx.gas,
1437            Transaction::PrivilegedL2Transaction(tx) => tx.gas_limit,
1438            Transaction::FeeTokenTransaction(tx) => tx.gas_limit,
1439            Transaction::FrameTransaction(tx) => tx.max_gas(),
1440        }
1441    }
1442
1443    //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.
1444    pub fn gas_price(&self) -> U256 {
1445        match self {
1446            Transaction::LegacyTransaction(tx) => tx.gas_price,
1447            Transaction::EIP2930Transaction(tx) => tx.gas_price,
1448            Transaction::EIP1559Transaction(tx) => U256::from(tx.max_fee_per_gas),
1449            Transaction::EIP7702Transaction(tx) => U256::from(tx.max_fee_per_gas),
1450            Transaction::EIP4844Transaction(tx) => U256::from(tx.max_fee_per_gas),
1451            Transaction::PrivilegedL2Transaction(tx) => U256::from(tx.max_fee_per_gas),
1452            Transaction::FeeTokenTransaction(tx) => U256::from(tx.max_fee_per_gas),
1453            Transaction::FrameTransaction(tx) => U256::from(tx.max_fee_per_gas),
1454        }
1455    }
1456
1457    pub fn to(&self) -> TxKind {
1458        match self {
1459            Transaction::LegacyTransaction(tx) => tx.to.clone(),
1460            Transaction::EIP2930Transaction(tx) => tx.to.clone(),
1461            Transaction::EIP1559Transaction(tx) => tx.to.clone(),
1462            Transaction::EIP4844Transaction(tx) => TxKind::Call(tx.to),
1463            Transaction::EIP7702Transaction(tx) => TxKind::Call(tx.to),
1464            Transaction::PrivilegedL2Transaction(tx) => tx.to.clone(),
1465            Transaction::FeeTokenTransaction(tx) => tx.to.clone(),
1466            Transaction::FrameTransaction(tx) => TxKind::Call(tx.sender),
1467        }
1468    }
1469
1470    pub fn value(&self) -> U256 {
1471        match self {
1472            Transaction::LegacyTransaction(tx) => tx.value,
1473            Transaction::EIP2930Transaction(tx) => tx.value,
1474            Transaction::EIP1559Transaction(tx) => tx.value,
1475            Transaction::EIP4844Transaction(tx) => tx.value,
1476            Transaction::EIP7702Transaction(tx) => tx.value,
1477            Transaction::PrivilegedL2Transaction(tx) => tx.value,
1478            Transaction::FeeTokenTransaction(tx) => tx.value,
1479            Transaction::FrameTransaction(_) => U256::zero(),
1480        }
1481    }
1482
1483    pub fn max_priority_fee(&self) -> Option<u64> {
1484        match self {
1485            Transaction::LegacyTransaction(_tx) => None,
1486            Transaction::EIP2930Transaction(_tx) => None,
1487            Transaction::EIP1559Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1488            Transaction::EIP4844Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1489            Transaction::EIP7702Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1490            Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_priority_fee_per_gas),
1491            Transaction::FeeTokenTransaction(tx) => Some(tx.max_priority_fee_per_gas),
1492            Transaction::FrameTransaction(tx) => Some(tx.max_priority_fee_per_gas),
1493        }
1494    }
1495
1496    pub fn chain_id(&self) -> Option<u64> {
1497        match self {
1498            Transaction::LegacyTransaction(tx) => derive_legacy_chain_id(tx.v),
1499            Transaction::EIP2930Transaction(tx) => Some(tx.chain_id),
1500            Transaction::EIP1559Transaction(tx) => Some(tx.chain_id),
1501            Transaction::EIP4844Transaction(tx) => Some(tx.chain_id),
1502            Transaction::EIP7702Transaction(tx) => Some(tx.chain_id),
1503            Transaction::PrivilegedL2Transaction(tx) => Some(tx.chain_id),
1504            Transaction::FeeTokenTransaction(tx) => Some(tx.chain_id),
1505            Transaction::FrameTransaction(tx) => Some(tx.chain_id),
1506        }
1507    }
1508
1509    pub fn access_list(&self) -> &AccessList {
1510        static EMPTY_ACCESS_LIST: AccessList = Vec::new();
1511        match self {
1512            Transaction::LegacyTransaction(_tx) => &EMPTY_ACCESS_LIST,
1513            Transaction::EIP2930Transaction(tx) => &tx.access_list,
1514            Transaction::EIP1559Transaction(tx) => &tx.access_list,
1515            Transaction::EIP4844Transaction(tx) => &tx.access_list,
1516            Transaction::EIP7702Transaction(tx) => &tx.access_list,
1517            Transaction::PrivilegedL2Transaction(tx) => &tx.access_list,
1518            Transaction::FeeTokenTransaction(tx) => &tx.access_list,
1519            Transaction::FrameTransaction(_) => &EMPTY_ACCESS_LIST,
1520        }
1521    }
1522    pub fn authorization_list(&self) -> Option<&AuthorizationList> {
1523        match self {
1524            Transaction::LegacyTransaction(_) => None,
1525            Transaction::EIP2930Transaction(_) => None,
1526            Transaction::EIP1559Transaction(_) => None,
1527            Transaction::EIP4844Transaction(_) => None,
1528            Transaction::EIP7702Transaction(tx) => Some(&tx.authorization_list),
1529            Transaction::PrivilegedL2Transaction(_) => None,
1530            Transaction::FeeTokenTransaction(_) => None,
1531            Transaction::FrameTransaction(_) => None,
1532        }
1533    }
1534
1535    pub fn nonce(&self) -> u64 {
1536        match self {
1537            Transaction::LegacyTransaction(tx) => tx.nonce,
1538            Transaction::EIP2930Transaction(tx) => tx.nonce,
1539            Transaction::EIP1559Transaction(tx) => tx.nonce,
1540            Transaction::EIP4844Transaction(tx) => tx.nonce,
1541            Transaction::EIP7702Transaction(tx) => tx.nonce,
1542            Transaction::PrivilegedL2Transaction(tx) => tx.nonce,
1543            Transaction::FeeTokenTransaction(tx) => tx.nonce,
1544            Transaction::FrameTransaction(tx) => tx.nonce,
1545        }
1546    }
1547
1548    pub fn data(&self) -> &Bytes {
1549        match self {
1550            Transaction::LegacyTransaction(tx) => &tx.data,
1551            Transaction::EIP2930Transaction(tx) => &tx.data,
1552            Transaction::EIP1559Transaction(tx) => &tx.data,
1553            Transaction::EIP4844Transaction(tx) => &tx.data,
1554            Transaction::EIP7702Transaction(tx) => &tx.data,
1555            Transaction::PrivilegedL2Transaction(tx) => &tx.data,
1556            Transaction::FeeTokenTransaction(tx) => &tx.data,
1557            Transaction::FrameTransaction(_) => {
1558                static EMPTY_DATA: Bytes = Bytes::new();
1559                &EMPTY_DATA
1560            }
1561        }
1562    }
1563
1564    pub fn blob_versioned_hashes(&self) -> Vec<H256> {
1565        match self {
1566            Transaction::LegacyTransaction(_) => Vec::new(),
1567            Transaction::EIP2930Transaction(_) => Vec::new(),
1568            Transaction::EIP1559Transaction(_) => Vec::new(),
1569            Transaction::EIP4844Transaction(tx) => tx.blob_versioned_hashes.clone(),
1570            Transaction::EIP7702Transaction(_) => Vec::new(),
1571            Transaction::PrivilegedL2Transaction(_) => Vec::new(),
1572            Transaction::FeeTokenTransaction(_) => Vec::new(),
1573            Transaction::FrameTransaction(tx) => tx.blob_versioned_hashes.clone(),
1574        }
1575    }
1576
1577    pub fn max_fee_per_blob_gas(&self) -> Option<U256> {
1578        match self {
1579            Transaction::LegacyTransaction(_) => None,
1580            Transaction::EIP2930Transaction(_) => None,
1581            Transaction::EIP1559Transaction(_) => None,
1582            Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_blob_gas),
1583            Transaction::EIP7702Transaction(_) => None,
1584            Transaction::PrivilegedL2Transaction(_) => None,
1585            Transaction::FeeTokenTransaction(_) => None,
1586            Transaction::FrameTransaction(tx) => {
1587                if tx.blob_versioned_hashes.is_empty() {
1588                    None
1589                } else {
1590                    Some(tx.max_fee_per_blob_gas)
1591                }
1592            }
1593        }
1594    }
1595
1596    /// Whether this transaction carries blob data, and therefore an expensive
1597    /// EIP-4844 sidecar. True for EIP-4844 transactions and for EIP-8141 frame
1598    /// transactions with a non-empty `blob_versioned_hashes` — matching for
1599    /// which types [`Self::max_fee_per_blob_gas`] yields a fee. Prefer this
1600    /// over matching on `EIP4844Transaction` alone when the question is "does
1601    /// this tx have a sidecar to re-propagate".
1602    pub fn is_blob_carrying(&self) -> bool {
1603        self.max_fee_per_blob_gas().is_some()
1604    }
1605
1606    pub fn is_contract_creation(&self) -> bool {
1607        match &self {
1608            Transaction::LegacyTransaction(t) => matches!(t.to, TxKind::Create),
1609            Transaction::EIP2930Transaction(t) => matches!(t.to, TxKind::Create),
1610            Transaction::EIP1559Transaction(t) => matches!(t.to, TxKind::Create),
1611            Transaction::EIP4844Transaction(_) => false,
1612            Transaction::EIP7702Transaction(_) => false,
1613            Transaction::PrivilegedL2Transaction(t) => matches!(t.to, TxKind::Create),
1614            Transaction::FeeTokenTransaction(t) => matches!(t.to, TxKind::Create),
1615            Transaction::FrameTransaction(_) => false,
1616        }
1617    }
1618
1619    pub fn is_privileged(&self) -> bool {
1620        matches!(self, Transaction::PrivilegedL2Transaction(_))
1621    }
1622
1623    pub fn max_fee_per_gas(&self) -> Option<u64> {
1624        match self {
1625            Transaction::LegacyTransaction(_tx) => None,
1626            Transaction::EIP2930Transaction(_tx) => None,
1627            Transaction::EIP1559Transaction(tx) => Some(tx.max_fee_per_gas),
1628            Transaction::EIP4844Transaction(tx) => Some(tx.max_fee_per_gas),
1629            Transaction::EIP7702Transaction(tx) => Some(tx.max_fee_per_gas),
1630            Transaction::PrivilegedL2Transaction(tx) => Some(tx.max_fee_per_gas),
1631            Transaction::FeeTokenTransaction(tx) => Some(tx.max_fee_per_gas),
1632            Transaction::FrameTransaction(tx) => Some(tx.max_fee_per_gas),
1633        }
1634    }
1635
1636    fn compute_hash(&self, crypto: &dyn Crypto) -> H256 {
1637        if let Transaction::PrivilegedL2Transaction(tx) = self {
1638            return tx.get_privileged_hash().unwrap_or_default();
1639        }
1640        H256(crypto.keccak256(&self.encode_canonical_to_vec()))
1641    }
1642
1643    pub fn hash(&self, crypto: &dyn Crypto) -> H256 {
1644        let inner_hash = match self {
1645            Transaction::LegacyTransaction(tx) => &tx.inner_hash,
1646            Transaction::EIP2930Transaction(tx) => &tx.inner_hash,
1647            Transaction::EIP1559Transaction(tx) => &tx.inner_hash,
1648            Transaction::EIP4844Transaction(tx) => &tx.inner_hash,
1649            Transaction::EIP7702Transaction(tx) => &tx.inner_hash,
1650            Transaction::PrivilegedL2Transaction(tx) => &tx.inner_hash,
1651            Transaction::FeeTokenTransaction(tx) => &tx.inner_hash,
1652            Transaction::FrameTransaction(tx) => &tx.inner_hash,
1653        };
1654
1655        *inner_hash.get_or_init(|| self.compute_hash(crypto))
1656    }
1657
1658    pub fn gas_tip_cap(&self) -> U256 {
1659        self.max_priority_fee()
1660            .map(U256::from)
1661            .unwrap_or_else(|| self.gas_price())
1662    }
1663
1664    pub fn gas_fee_cap(&self) -> U256 {
1665        self.max_fee_per_gas()
1666            .map(U256::from)
1667            .unwrap_or_else(|| self.gas_price())
1668    }
1669
1670    /// Returns the effective tip per gas for this transaction.
1671    /// Returns `None` if the transaction's fee cap is below the base fee (i.e. the
1672    /// transaction cannot pay for its inclusion).
1673    pub fn effective_gas_tip(&self, base_fee: Option<u64>) -> Option<U256> {
1674        let tip_cap = self.gas_tip_cap();
1675        let Some(base_fee) = base_fee else {
1676            return Some(tip_cap);
1677        };
1678        let base_fee = U256::from(base_fee);
1679        let fee_cap = self.gas_fee_cap();
1680        let tip = fee_cap.checked_sub(base_fee)?;
1681        Some(min(tip, tip_cap))
1682    }
1683
1684    /// Returns whether the transaction is replay-protected.
1685    /// For more information check out [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md)
1686    pub fn protected(&self) -> bool {
1687        match self {
1688            Transaction::LegacyTransaction(tx) if tx.v.bits() <= 8 => {
1689                let v = tx.v.low_u64();
1690                v != 27 && v != 28 && v != 1 && v != 0
1691            }
1692            _ => true,
1693        }
1694    }
1695}
1696
1697fn derive_legacy_chain_id(v: U256) -> Option<u64> {
1698    let v = u64::try_from(v).ok()?;
1699    // EIP-155 encodes the chain id as `v = chain_id * 2 + 35` (or 36), so any
1700    // replay-protected `v` is >= 35. Pre-EIP-155 txs use v=27/28, and malformed
1701    // signatures (e.g. v=0 from an unsigned IL transaction) are < 35 too; none
1702    // carry a chain id. Guard the subtraction to avoid an underflow panic.
1703    if v < 35 { None } else { Some((v - 35) / 2) }
1704}
1705
1706impl TxType {
1707    pub fn from_u8(value: u8) -> Option<Self> {
1708        match value {
1709            0x00 => Some(Self::Legacy),
1710            0x01 => Some(Self::EIP2930),
1711            0x02 => Some(Self::EIP1559),
1712            0x03 => Some(Self::EIP4844),
1713            0x04 => Some(Self::EIP7702),
1714            0x06 => Some(Self::Frame),
1715            0x7d => Some(Self::FeeToken),
1716            0x7e => Some(Self::Privileged),
1717            _ => None,
1718        }
1719    }
1720
1721    /// The typed-envelope form of this transaction type, or `None` for [`TxType::Legacy`]
1722    /// (a bare RLP list, not a `0x{type} || payload` envelope). Exhaustive over `TxType`
1723    /// with no wildcard, so adding a new variant won't compile until it's classified here —
1724    /// keeping envelope decoding in step with the transaction-type set.
1725    pub fn as_envelope(self) -> Option<EnvelopeTxType> {
1726        match self {
1727            TxType::EIP2930 => Some(EnvelopeTxType::EIP2930),
1728            TxType::EIP1559 => Some(EnvelopeTxType::EIP1559),
1729            TxType::EIP4844 => Some(EnvelopeTxType::EIP4844),
1730            TxType::EIP7702 => Some(EnvelopeTxType::EIP7702),
1731            TxType::Frame => Some(EnvelopeTxType::Frame),
1732            TxType::FeeToken => Some(EnvelopeTxType::FeeToken),
1733            TxType::Privileged => Some(EnvelopeTxType::Privileged),
1734            TxType::Legacy => None,
1735        }
1736    }
1737
1738    /// Transaction types that only exist on the L2 rollup and must never appear in
1739    /// an L1 block (`FeeToken` 0x7d, `Privileged` 0x7e). Privileged transactions in
1740    /// particular take their sender from an unsigned, caller-chosen `from` field.
1741    ///
1742    /// This match is intentionally exhaustive (no wildcard arm): adding a new
1743    /// `TxType` variant will not compile until it is explicitly classified here,
1744    /// so an L2-only type can never be silently accepted on L1 by omission.
1745    pub fn is_l2_only(self) -> bool {
1746        match self {
1747            Self::Legacy
1748            | Self::EIP2930
1749            | Self::EIP1559
1750            | Self::EIP4844
1751            | Self::EIP7702
1752            | Self::Frame => false,
1753            Self::FeeToken | Self::Privileged => true,
1754        }
1755    }
1756}
1757
1758impl PrivilegedL2Transaction {
1759    /// Returns the formatted hash of the privileged transaction,
1760    /// or None if the transaction is not a privileged transaction.
1761    /// The hash is computed as keccak256(chain_id || from || to || transaction_id  || value || gas_limit || keccak256(calldata))
1762    pub fn get_privileged_hash(&self) -> Option<H256> {
1763        // Should this function be changed?
1764        let to = match self.to {
1765            TxKind::Call(to) => to,
1766            _ => return None,
1767        };
1768
1769        let value = self.value.to_big_endian();
1770
1771        // The nonce should be a U256,
1772        // in solidity the transactionId is a U256.
1773        let u256_nonce = U256::from(self.nonce);
1774        let nonce = u256_nonce.to_big_endian();
1775
1776        Some(crate::utils::keccak(
1777            [
1778                U256::from(self.chain_id).to_big_endian().as_ref(),
1779                self.from.as_bytes(),
1780                to.as_bytes(),
1781                &nonce,
1782                &value,
1783                &U256::from(self.gas_limit).to_big_endian(),
1784                keccak(&self.data).as_bytes(),
1785            ]
1786            .concat(),
1787        ))
1788    }
1789}
1790
1791#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1792pub struct FeeTokenTransaction {
1793    pub chain_id: u64,
1794    pub nonce: u64,
1795    pub max_priority_fee_per_gas: u64,
1796    pub max_fee_per_gas: u64,
1797    pub gas_limit: u64,
1798    pub to: TxKind,
1799    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1800    pub value: U256,
1801    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1802    pub data: Bytes,
1803    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::AccessListItemWrapper>)]
1804    pub access_list: AccessList,
1805    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
1806    pub fee_token: Address,
1807    pub signature_y_parity: bool,
1808    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1809    pub signature_r: U256,
1810    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1811    pub signature_s: U256,
1812    #[rkyv(with=rkyv::with::Skip)]
1813    pub inner_hash: OnceCell<H256>,
1814    #[rkyv(with=rkyv::with::Skip)]
1815    pub sender_cache: OnceCell<Address>,
1816    #[rkyv(with=rkyv::with::Skip)]
1817    pub cached_canonical: OnceCell<Vec<u8>>,
1818}
1819
1820/// EIP-8141 Frame Transaction mode
1821#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1822#[repr(u8)]
1823pub enum FrameMode {
1824    #[default]
1825    Default = 0,
1826    Verify = 1,
1827    Sender = 2,
1828}
1829
1830impl FrameMode {
1831    /// Convert from the lower 8 bits of the mode field.
1832    /// Returns None for reserved values (3-255).
1833    pub fn from_u8(val: u8) -> Option<Self> {
1834        match val {
1835            0 => Some(FrameMode::Default),
1836            1 => Some(FrameMode::Verify),
1837            2 => Some(FrameMode::Sender),
1838            _ => None,
1839        }
1840    }
1841}
1842
1843impl From<FrameMode> for u8 {
1844    fn from(mode: FrameMode) -> u8 {
1845        match mode {
1846            FrameMode::Default => 0,
1847            FrameMode::Verify => 1,
1848            FrameMode::Sender => 2,
1849        }
1850    }
1851}
1852
1853/// EIP-8141 Frame: a single execution step within a frame transaction.
1854///
1855/// `mode` is the execution mode (0=DEFAULT, 1=VERIFY, 2=SENDER; 3-255 reserved).
1856/// `flags` bits: 0-1 = APPROVE scope restriction, 2 = atomic batch flag (valid
1857/// on DEFAULT and SENDER frames only), 3-7 reserved (must be zero).
1858#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1859pub struct Frame {
1860    pub mode: u8,
1861    pub flags: u8,
1862    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H160Wrapper>)]
1863    pub target: Option<Address>,
1864    pub gas_limit: u64,
1865    // Per EIP-8141 the frame is a 6-tuple [mode, flags, target, gas_limit, value, data].
1866    // Only SENDER frames may carry a non-zero value; see
1867    // `validate_static_constraints`.
1868    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
1869    pub value: U256,
1870    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1871    pub data: Bytes,
1872}
1873
1874impl Frame {
1875    /// Extract the execution mode from the `mode` field.
1876    pub fn execution_mode(&self) -> FrameMode {
1877        FrameMode::from_u8(self.mode).unwrap_or_default()
1878    }
1879
1880    /// Extract the APPROVE scope restriction from bits 0-1 of `flags`.
1881    pub fn scope_restriction(&self) -> u8 {
1882        self.flags & 0x03
1883    }
1884
1885    /// Check if the atomic batch flag (bit 2 of `flags`) is set.
1886    pub fn is_atomic_batch(&self) -> bool {
1887        (self.flags >> 2) & 1 == 1
1888    }
1889
1890    /// An expiry verifier frame is a VERIFY frame targeting EXPIRY_VERIFIER
1891    /// (EIP-8141).
1892    pub fn is_expiry_verifier(&self) -> bool {
1893        self.execution_mode() == FrameMode::Verify
1894            && self.target == Some(frame_tx_expiry_verifier())
1895    }
1896}
1897
1898impl RLPEncode for Frame {
1899    fn encode(&self, buf: &mut dyn bytes::BufMut) {
1900        // target: encode as address or RLP null (empty bytes) for None
1901        let target_kind = match self.target {
1902            Some(addr) => TxKind::Call(addr),
1903            None => TxKind::Create, // RLP_NULL encodes "no target" → sender
1904        };
1905        Encoder::new(buf)
1906            .encode_field(&(self.mode as u64))
1907            .encode_field(&(self.flags as u64))
1908            .encode_field(&target_kind)
1909            .encode_field(&self.gas_limit)
1910            .encode_field(&self.value)
1911            .encode_field(&self.data)
1912            .finish();
1913    }
1914}
1915
1916impl RLPDecode for Frame {
1917    fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
1918        let decoder = Decoder::new(rlp)?;
1919        let (mode_u64, decoder): (u64, _) = decoder.decode_field("mode")?;
1920        let mode = u8::try_from(mode_u64)
1921            .map_err(|_| RLPDecodeError::Custom(format!("Frame mode too large: {mode_u64}")))?;
1922        let (flags_u64, decoder): (u64, _) = decoder.decode_field("flags")?;
1923        let flags = u8::try_from(flags_u64)
1924            .map_err(|_| RLPDecodeError::Custom(format!("Frame flags too large: {flags_u64}")))?;
1925        let (target_kind, decoder): (TxKind, _) = decoder.decode_field("target")?;
1926        let target = match target_kind {
1927            TxKind::Call(addr) => Some(addr),
1928            TxKind::Create => None,
1929        };
1930        let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
1931        let (value, decoder): (U256, _) = decoder.decode_field("value")?;
1932        let (data, decoder) = decoder.decode_field("data")?;
1933        let frame = Frame {
1934            mode,
1935            flags,
1936            target,
1937            gas_limit,
1938            value,
1939            data,
1940        };
1941        Ok((frame, decoder.finish()?))
1942    }
1943}
1944
1945/// EIP-8141 transaction signature. RLP: `[scheme, signer, msg, signature]`.
1946/// `scheme`: 0 = ARBITRARY (`signer` MUST be empty; `signature` is arbitrary bytes),
1947/// 1 = SECP256K1 (sig = v||r||s, 65 bytes), 2 = P256 (sig = r||s||qx||qy, 128 bytes).
1948/// `signer`: `None` (empty) is required for ARBITRARY and allowed for SECP256K1/P256
1949/// (empty ⇒ the resolved signer is `tx.sender`, for introspection too); `Some(addr)` pins it.
1950/// `msg`: empty = signs compute_sig_hash(tx); 32 bytes = signs that explicit digest.
1951/// Raw `signature` bytes are intentionally not EVM-introspectable.
1952#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
1953pub struct FrameSignature {
1954    pub scheme: u8,
1955    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H160Wrapper>)]
1956    pub signer: Option<Address>,
1957    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1958    pub msg: Bytes,
1959    #[rkyv(with=crate::rkyv_utils::BytesWrapper)]
1960    pub signature: Bytes,
1961}
1962
1963impl RLPEncode for FrameSignature {
1964    fn encode(&self, buf: &mut dyn bytes::BufMut) {
1965        // `signer` encodes as an address or RLP null (empty) for `None`, mirroring
1966        // `Frame.target`; an empty signer is required for ARBITRARY signatures.
1967        let signer_kind = match self.signer {
1968            Some(addr) => TxKind::Call(addr),
1969            None => TxKind::Create,
1970        };
1971        Encoder::new(buf)
1972            .encode_field(&self.scheme)
1973            .encode_field(&signer_kind)
1974            .encode_field(&self.msg)
1975            .encode_field(&self.signature)
1976            .finish();
1977    }
1978}
1979
1980impl RLPDecode for FrameSignature {
1981    fn decode_unfinished(rlp: &[u8]) -> Result<(FrameSignature, &[u8]), RLPDecodeError> {
1982        let decoder = Decoder::new(rlp)?;
1983        let (scheme, decoder) = decoder.decode_field("scheme")?;
1984        let (signer_kind, decoder): (TxKind, _) = decoder.decode_field("signer")?;
1985        let signer = match signer_kind {
1986            TxKind::Call(addr) => Some(addr),
1987            TxKind::Create => None,
1988        };
1989        let (msg, decoder) = decoder.decode_field("msg")?;
1990        let (signature, decoder) = decoder.decode_field("signature")?;
1991        Ok((
1992            FrameSignature {
1993                scheme,
1994                signer,
1995                msg,
1996                signature,
1997            },
1998            decoder.finish()?,
1999        ))
2000    }
2001}
2002
2003/// EIP-8141 Frame Transaction
2004/// A transaction whose validity and gas payment are defined abstractly via frames.
2005/// No ECDSA signature — sender is explicit. Authentication happens via APPROVE opcode.
2006#[derive(Clone, Debug, PartialEq, Eq, Default, RSerialize, RDeserialize, Archive)]
2007pub struct FrameTransaction {
2008    pub chain_id: u64,
2009    pub nonce: u64,
2010    #[rkyv(with=crate::rkyv_utils::H160Wrapper)]
2011    pub sender: Address,
2012    pub frames: Vec<Frame>,
2013    /// EIP-8141 outer signature list. Validated
2014    /// before any frame executes; referenced by VERIFY frames and SIGPARAM.
2015    pub signatures: Vec<FrameSignature>,
2016    pub max_priority_fee_per_gas: u64,
2017    pub max_fee_per_gas: u64,
2018    #[rkyv(with=crate::rkyv_utils::U256Wrapper)]
2019    pub max_fee_per_blob_gas: U256,
2020    #[rkyv(with=rkyv::with::Map<crate::rkyv_utils::H256Wrapper>)]
2021    pub blob_versioned_hashes: Vec<H256>,
2022    #[rkyv(with=rkyv::with::Skip)]
2023    pub inner_hash: OnceCell<H256>,
2024    #[rkyv(with=rkyv::with::Skip)]
2025    pub cached_canonical: OnceCell<Vec<u8>>,
2026}
2027
2028/// Intrinsic gas cost for frame transactions (EIP-8141)
2029pub const FRAME_TX_INTRINSIC_COST: u64 = 15000;
2030/// Per-frame cost (EIP-8141): CALL context overhead (100) + G_log (375)
2031pub const FRAME_TX_PER_FRAME_COST: u64 = 475;
2032/// ENTRY_POINT address used as caller for DEFAULT/VERIFY frames per EIP-8141.
2033pub const FRAME_TX_ENTRY_POINT_U64: u64 = 0xaa;
2034/// Maximum number of frames allowed per EIP-8141 frame transaction.
2035pub const FRAME_TX_MAX_FRAMES: usize = 64;
2036/// EIP-7623 `STANDARD_TOKEN_COST`, and the EIP-7976 floor per token. Frame
2037/// transactions exist only from Hegota onward, which is after Amsterdam, so the
2038/// raised EIP-7976 floor always applies and neither needs a fork parameter.
2039const FRAME_TX_STANDARD_TOKEN_COST: u64 = 4;
2040const FRAME_TX_TOTAL_COST_FLOOR_PER_TOKEN: u64 = 16;
2041/// EIP-8141 signature schemes: ARBITRARY=0, SECP256K1=1, P256=2.
2042pub const FRAME_SIG_SCHEME_ARBITRARY: u8 = 0;
2043pub const FRAME_SIG_SCHEME_SECP256K1: u8 = 1;
2044pub const FRAME_SIG_SCHEME_P256: u8 = 2;
2045/// EIP-8141 §Mempool `MAX_VERIFY_GAS`: the maximum gas
2046/// a public-mempool node should expend validating signatures and simulating the
2047/// validation prefix. Signature validation counts against this budget (rule #6),
2048/// so a frame tx whose `signature_verification_cost()` alone exceeds it can never
2049/// satisfy the prefix budget and must be rejected at admission.
2050pub const FRAME_TX_MAX_VERIFY_GAS: u64 = 100_000;
2051/// EIP-8141 APPROVE scope-restriction values (bits 0-1 of `Frame.flags`).
2052/// Used by VERIFY and PAY frames to declare which capabilities they grant.
2053pub const APPROVE_PAYMENT: u8 = 0x1;
2054pub const APPROVE_EXECUTION: u8 = 0x2;
2055pub const APPROVE_EXECUTION_AND_PAYMENT: u8 = 0x3;
2056/// Maximum number of pending frame txs using a non-canonical paymaster per
2057/// paymaster address. Per OQ1, all paymasters are currently non-canonical
2058/// (FRAME_CANONICAL_PAYMASTER_CODE_HASH is unresolved in the draft EIP), so
2059/// this de-facto limits sponsored frame txs to 1 per paymaster in the pool.
2060pub const FRAME_TX_MAX_PENDING_NONCANONICAL_PAYMASTER: u8 = 1;
2061
2062/// Returns the ENTRY_POINT `Address` (0x…00aa) used as caller for
2063/// DEFAULT/VERIFY frames per EIP-8141.
2064pub fn frame_tx_entry_point() -> Address {
2065    Address::from_low_u64_be(FRAME_TX_ENTRY_POINT_U64)
2066}
2067
2068/// EXPIRY_VERIFIER predeploy address (EIP-8141).
2069pub const FRAME_TX_EXPIRY_VERIFIER_U64: u64 = 0x8141;
2070/// Required `data` length for an expiry verifier frame (8-byte BE deadline).
2071pub const FRAME_TX_EXPIRY_DATA_LENGTH: usize = 8;
2072
2073/// Returns the EXPIRY_VERIFIER `Address` (0x…8141) per EIP-8141.
2074pub fn frame_tx_expiry_verifier() -> Address {
2075    Address::from_low_u64_be(FRAME_TX_EXPIRY_VERIFIER_U64)
2076}
2077
2078impl FrameTransaction {
2079    /// Canonical signature hash (EIP-8141): the raw
2080    /// `signature` bytes of every signature with empty `msg` are elided (a
2081    /// signature over this hash cannot commit to its own bytes). Frame data is
2082    /// NO LONGER elided — it is fully covered. Signatures with an explicit
2083    /// 32-byte `msg` keep their bytes (they sign that digest, not this hash).
2084    pub fn compute_sig_hash(&self) -> H256 {
2085        let mut buf = vec![TxType::Frame as u8];
2086        let elided_signatures: Vec<FrameSignature> = self
2087            .signatures
2088            .iter()
2089            .map(|s| {
2090                if s.msg.is_empty() {
2091                    FrameSignature {
2092                        scheme: s.scheme,
2093                        signer: s.signer,
2094                        msg: s.msg.clone(),
2095                        signature: Bytes::new(),
2096                    }
2097                } else {
2098                    s.clone()
2099                }
2100            })
2101            .collect();
2102        // RLP-encode the tx with elided signature bytes, frames verbatim.
2103        Encoder::new(&mut buf)
2104            .encode_field(&self.chain_id)
2105            .encode_field(&self.nonce)
2106            .encode_field(&self.sender)
2107            .encode_field(&self.frames)
2108            .encode_field(&elided_signatures)
2109            .encode_field(&self.max_priority_fee_per_gas)
2110            .encode_field(&self.max_fee_per_gas)
2111            .encode_field(&self.max_fee_per_blob_gas)
2112            .encode_field(&self.blob_versioned_hashes)
2113            .finish();
2114        keccak(&buf)
2115    }
2116
2117    /// Per EIP-8141: 100 gas per ARBITRARY signature, 2800 per SECP256K1, 6700
2118    /// per P256. Unknown schemes are rejected by static validation, so they are
2119    /// treated as 0 here (validation runs first).
2120    pub fn signature_verification_cost(&self) -> u64 {
2121        self.signatures
2122            .iter()
2123            .map(|s| match s.scheme {
2124                FRAME_SIG_SCHEME_ARBITRARY => 100u64,
2125                FRAME_SIG_SCHEME_SECP256K1 => 2800u64,
2126                FRAME_SIG_SCHEME_P256 => 6700u64,
2127                _ => 0,
2128            })
2129            .sum()
2130    }
2131
2132    /// Iterate the EIP-8141 data fields the calldata cost is charged over:
2133    /// each frame's `data`, and each signature's `signer`, `msg` and `signature`.
2134    /// The RLP framing and the remaining scalar fields are not included.
2135    fn data_fields(&self) -> impl Iterator<Item = &[u8]> {
2136        const EMPTY: &[u8] = &[];
2137        self.frames
2138            .iter()
2139            .map(|f| f.data.as_ref())
2140            .chain(self.signatures.iter().flat_map(|s| {
2141                [
2142                    s.signer.as_ref().map_or(EMPTY, |a| a.as_bytes()),
2143                    s.msg.as_ref(),
2144                    s.signature.as_ref(),
2145                ]
2146            }))
2147    }
2148
2149    /// EIP-7623 calldata cost over the frame and signature data: 4 gas per zero
2150    /// byte, 16 per non-zero byte.
2151    pub fn data_cost(&self) -> u64 {
2152        self.data_fields().flatten().fold(0u64, |acc, byte| {
2153            acc.saturating_add(if *byte == 0 { 4 } else { 16 })
2154        })
2155    }
2156
2157    /// EIP-7623 token count over the frame and signature data, used for the
2158    /// calldata floor. Frame transactions exist only from Hegota onward, which is
2159    /// after Amsterdam, so EIP-7976's unweighted count (every byte costs
2160    /// `STANDARD_TOKEN_COST`) always applies.
2161    pub fn calldata_tokens(&self) -> u64 {
2162        self.data_fields()
2163            .fold(0u64, |acc, field| acc.saturating_add(field.len() as u64))
2164            .saturating_mul(FRAME_TX_STANDARD_TOKEN_COST)
2165    }
2166
2167    /// The EIP-7623 calldata floor: the least the frame and signature data may
2168    /// cost, whatever execution actually consumes.
2169    pub fn calldata_floor_gas(&self) -> u64 {
2170        self.calldata_tokens()
2171            .saturating_mul(FRAME_TX_TOTAL_COST_FLOOR_PER_TOKEN)
2172    }
2173
2174    /// The mandatory costs, always charged in full: the intrinsic cost, the
2175    /// per-frame cost, and signature verification.
2176    pub fn mandatory_gas(&self) -> u64 {
2177        FRAME_TX_INTRINSIC_COST
2178            .saturating_add((self.frames.len() as u64).saturating_mul(FRAME_TX_PER_FRAME_COST))
2179            .saturating_add(self.signature_verification_cost())
2180    }
2181
2182    /// EIP-8141 `standard_gas_limit`: mandatory costs + data cost + sum of frame
2183    /// gas limits.
2184    pub fn standard_gas_limit(&self) -> u64 {
2185        self.mandatory_gas()
2186            .saturating_add(self.data_cost())
2187            .saturating_add(
2188                self.frames
2189                    .iter()
2190                    .map(|f| f.gas_limit)
2191                    .fold(0u64, |acc, g| acc.saturating_add(g)),
2192            )
2193    }
2194
2195    /// EIP-8141 `calldata_floor_gas`: the mandatory costs plus the EIP-7623
2196    /// floor over every byte this transaction carries. The mandatory costs are
2197    /// always charged, so they sit on both sides of the `max_gas` comparison.
2198    pub fn calldata_floor_total(&self) -> u64 {
2199        self.mandatory_gas()
2200            .saturating_add(self.calldata_floor_gas())
2201    }
2202
2203    /// EIP-8141 `max_gas = max(standard_gas_limit, calldata_floor_gas)`: the gas
2204    /// reserved from the block pool before execution and the quantity `max_cost`
2205    /// is charged over. A transaction whose data floor exceeds what it declared
2206    /// for execution reserves the floor rather than being rejected.
2207    pub fn max_gas(&self) -> u64 {
2208        self.standard_gas_limit().max(self.calldata_floor_total())
2209    }
2210
2211    /// The expiry deadline (8-byte big-endian) of this transaction's expiry
2212    /// verifier frame, if one exists with well-formed data.
2213    pub fn expiry_deadline(&self) -> Option<u64> {
2214        self.frames
2215            .iter()
2216            .find(|f| f.is_expiry_verifier())
2217            .and_then(|f| {
2218                let bytes: [u8; FRAME_TX_EXPIRY_DATA_LENGTH] = f.data.as_ref().try_into().ok()?;
2219                Some(u64::from_be_bytes(bytes))
2220            })
2221    }
2222
2223    /// Validate static constraints per EIP-8141 spec.
2224    /// Returns an error string if the transaction is invalid.
2225    pub fn validate_static_constraints(&self) -> Result<(), String> {
2226        // tx.sender != zero address
2227        if self.sender == Address::zero() {
2228            return Err("tx.sender must not be zero address".to_string());
2229        }
2230        if self.frames.is_empty() || self.frames.len() > FRAME_TX_MAX_FRAMES {
2231            return Err(format!(
2232                "Frame count must be between 1 and {FRAME_TX_MAX_FRAMES}"
2233            ));
2234        }
2235        // Per EIP-8141, every versioned hash must carry the EIP-4844 KZG version
2236        // byte, and a transaction carrying no blobs must not name a blob fee.
2237        for (i, hash) in self.blob_versioned_hashes.iter().enumerate() {
2238            if hash.0.first() != Some(&VERSIONED_HASH_VERSION_KZG) {
2239                return Err(format!("Blob versioned hash {i}: wrong version byte"));
2240            }
2241        }
2242        if self.blob_versioned_hashes.is_empty() && !self.max_fee_per_blob_gas.is_zero() {
2243            return Err(
2244                "max_fee_per_blob_gas must be zero when the transaction carries no blobs"
2245                    .to_string(),
2246            );
2247        }
2248        // EIP-8141 signature list validation: scheme must be a known value; msg
2249        // must be empty or a non-zero 32-byte digest.
2250        for (i, sig) in self.signatures.iter().enumerate() {
2251            match sig.scheme {
2252                // ARBITRARY (0) carries no recoverable signer; per EIP-8141 the
2253                // signer MUST be empty.
2254                FRAME_SIG_SCHEME_ARBITRARY => {
2255                    if sig.signer.is_some() {
2256                        return Err(format!(
2257                            "Signature {i}: ARBITRARY signatures must not name a signer"
2258                        ));
2259                    }
2260                }
2261                FRAME_SIG_SCHEME_SECP256K1 | FRAME_SIG_SCHEME_P256 => {}
2262                other => {
2263                    return Err(format!("Signature {i}: unsupported scheme {other}"));
2264                }
2265            }
2266            match sig.msg.len() {
2267                0 => {}
2268                32 => {
2269                    if sig.msg.iter().all(|&b| b == 0) {
2270                        return Err(format!(
2271                            "Signature {i}: explicit msg must not be zero digest"
2272                        ));
2273                    }
2274                }
2275                other => {
2276                    return Err(format!(
2277                        "Signature {i}: msg must be empty or 32 bytes, got {other}"
2278                    ));
2279                }
2280            }
2281        }
2282        // Tracked as u128 so the running addition itself cannot overflow; the
2283        // bound below rejects tx-level totals that don't fit in signed i64.
2284        let mut total_frame_gas: u128 = 0;
2285        let mut expiry_frame_count: usize = 0;
2286        for (i, frame) in self.frames.iter().enumerate() {
2287            // Reject reserved execution modes (3-255)
2288            if frame.mode >= 3 {
2289                return Err(format!("Frame {i}: reserved execution mode {}", frame.mode));
2290            }
2291            // Reserved flag bits 3-7 must be zero
2292            if frame.flags >= 8 {
2293                return Err(format!(
2294                    "Frame {i}: reserved flag bits must be zero (flags={:#04x})",
2295                    frame.flags
2296                ));
2297            }
2298            // Expiry verifier frames (EIP-8141): VERIFY
2299            // frames targeting EXPIRY_VERIFIER must have flags == 0, value == 0
2300            // (already enforced by the non-SENDER value rule below), and exactly
2301            // EXPIRY_DATA_LENGTH bytes of data; at most one per transaction.
2302            if frame.is_expiry_verifier() {
2303                expiry_frame_count = expiry_frame_count.saturating_add(1);
2304                if expiry_frame_count > 1 {
2305                    return Err(format!("Frame {i}: more than one expiry verifier frame"));
2306                }
2307                if frame.flags != 0 {
2308                    return Err(format!(
2309                        "Frame {i}: expiry verifier frame must have flags == 0"
2310                    ));
2311                }
2312                if frame.data.len() != FRAME_TX_EXPIRY_DATA_LENGTH {
2313                    return Err(format!(
2314                        "Frame {i}: expiry verifier frame data must be {FRAME_TX_EXPIRY_DATA_LENGTH} bytes"
2315                    ));
2316                }
2317            }
2318            // Per EIP-8141, only SENDER frames may carry a
2319            // non-zero value. DEFAULT and VERIFY frames with a non-zero
2320            // value are statically invalid.
2321            if frame.mode != FrameMode::Sender as u8 && !frame.value.is_zero() {
2322                return Err(format!(
2323                    "Frame {i}: non-zero value only allowed in SENDER mode (mode={}, value={})",
2324                    frame.mode, frame.value
2325                ));
2326            }
2327            // Intentional stricter-than-spec bound. EIP-8141 allows
2328            // gas_limit and cumulative frame gas up to 2**64-1; ethrex caps both
2329            // at i64::MAX (2**63-1) so the state-gas dimension (tracked as i64) can
2330            // never overflow downstream. This is documented as a known divergence
2331            // in docs/eip-8141.md. It is effectively unreachable: any gas_limit
2332            // >= 2**63 dwarfs every real block gas limit and is rejected by the
2333            // gas-limit-vs-block-limit check regardless.
2334            if frame.gas_limit > i64::MAX as u64 {
2335                return Err(format!(
2336                    "Frame {i}: gas_limit {} exceeds 2**63-1",
2337                    frame.gas_limit
2338                ));
2339            }
2340            total_frame_gas = total_frame_gas
2341                .checked_add(frame.gas_limit as u128)
2342                .ok_or_else(|| format!("Frame {i}: cumulative gas_limit overflow"))?;
2343            if total_frame_gas > i64::MAX as u128 {
2344                return Err(format!(
2345                    "Frame {i}: cumulative frame gas_limit {} exceeds 2**63-1",
2346                    total_frame_gas
2347                ));
2348            }
2349            // Per EIP-8141, approval of execution is only allowed when the
2350            // frame's target is empty or `tx.sender`.
2351            if frame.flags & APPROVE_EXECUTION != 0
2352                && frame.target.is_some_and(|target| target != self.sender)
2353            {
2354                return Err(format!(
2355                    "Frame {i}: APPROVE_EXECUTION requires an empty target or tx.sender"
2356                ));
2357            }
2358            // Per EIP-8141, the atomic batch flag (bit 2 of flags) is only valid
2359            // on a non-VERIFY frame, requires a subsequent frame to batch with,
2360            // and that frame must be non-VERIFY too: batches never contain
2361            // VERIFY frames.
2362            if frame.is_atomic_batch() {
2363                if frame.mode == FrameMode::Verify as u8 {
2364                    return Err(format!("Frame {i}: atomic batch flag on a VERIFY frame"));
2365                }
2366                match self.frames.get(i.saturating_add(1)) {
2367                    None => return Err(format!("Frame {i}: atomic batch flag on last frame")),
2368                    Some(next) if next.mode == FrameMode::Verify as u8 => {
2369                        return Err(format!(
2370                            "Frame {i}: atomic batch flag followed by a VERIFY frame"
2371                        ));
2372                    }
2373                    Some(_) => {}
2374                }
2375            }
2376        }
2377        Ok(())
2378    }
2379
2380    /// Identify and return the validation prefix of this frame transaction.
2381    ///
2382    /// The validation prefix is the minimal leading subsequence of frames (ignoring
2383    /// expiry-verifier frames) that establishes who pays for the transaction.
2384    /// Four shapes are recognized (per EIP-8141 §Mempool):
2385    ///
2386    /// - `SelfVerify`: one VERIFY frame targeting `tx.sender` with scope `APPROVE_EXECUTION_AND_PAYMENT`.
2387    /// - `DeploySelfVerify`: one DEFAULT frame (deploy) then one VERIFY frame as above.
2388    /// - `OnlyVerifyPay`: one VERIFY frame (only_verify, scope `APPROVE_EXECUTION`) +
2389    ///   one VERIFY frame (pay, scope `APPROVE_PAYMENT`).
2390    /// - `DeployOnlyVerifyPay`: DEFAULT deploy frame + OnlyVerifyPay pair.
2391    ///
2392    /// Expiry-verifier frames (see `Frame::is_expiry_verifier`) are transparent; they
2393    /// are skipped during shape matching but their indices are NOT included in
2394    /// `frame_indices` (which holds only the semantically meaningful prefix frames).
2395    pub fn validation_prefix(&self) -> Result<ValidationPrefix, FrameValidationError> {
2396        // Collect non-expiry frame indices in order.
2397        let non_expiry: Vec<usize> = self
2398            .frames
2399            .iter()
2400            .enumerate()
2401            .filter(|(_, f)| !f.is_expiry_verifier())
2402            .map(|(i, _)| i)
2403            .collect();
2404
2405        // Helper to get a frame by its position in non_expiry.
2406        let frame = |pos: usize| -> Option<&Frame> {
2407            non_expiry.get(pos).and_then(|&idx| self.frames.get(idx))
2408        };
2409
2410        let is_default = |pos: usize| -> bool {
2411            frame(pos).is_some_and(|f| f.execution_mode() == FrameMode::Default)
2412        };
2413        let is_verify = |pos: usize| -> bool {
2414            frame(pos).is_some_and(|f| f.execution_mode() == FrameMode::Verify)
2415        };
2416        let scope_of = |pos: usize| -> u8 { frame(pos).map_or(0, |f| f.scope_restriction()) };
2417
2418        if non_expiry.is_empty() {
2419            return Err(FrameValidationError::UnrecognizedPrefix);
2420        }
2421
2422        // Attempt to match each of the four shapes.
2423        //
2424        // Shape: DeployOnlyVerifyPay — DEFAULT + VERIFY(exec) + VERIFY(pay)
2425        if is_default(0)
2426            && is_verify(1)
2427            && scope_of(1) == APPROVE_EXECUTION
2428            && is_verify(2)
2429            && scope_of(2) == APPROVE_PAYMENT
2430        {
2431            return Ok(ValidationPrefix {
2432                shape: PrefixShape::DeployOnlyVerifyPay,
2433                frame_indices: vec![non_expiry[0], non_expiry[1], non_expiry[2]],
2434                deploy_index: Some(non_expiry[0]),
2435                pay_index: Some(non_expiry[2]),
2436            });
2437        }
2438
2439        // Shape: DeploySelfVerify — DEFAULT + VERIFY(exec+pay)
2440        if is_default(0) && is_verify(1) && scope_of(1) == APPROVE_EXECUTION_AND_PAYMENT {
2441            return Ok(ValidationPrefix {
2442                shape: PrefixShape::DeploySelfVerify,
2443                frame_indices: vec![non_expiry[0], non_expiry[1]],
2444                deploy_index: Some(non_expiry[0]),
2445                pay_index: Some(non_expiry[1]),
2446            });
2447        }
2448
2449        // Shape: OnlyVerifyPay — VERIFY(exec) + VERIFY(pay)
2450        if is_verify(0)
2451            && scope_of(0) == APPROVE_EXECUTION
2452            && is_verify(1)
2453            && scope_of(1) == APPROVE_PAYMENT
2454        {
2455            return Ok(ValidationPrefix {
2456                shape: PrefixShape::OnlyVerifyPay,
2457                frame_indices: vec![non_expiry[0], non_expiry[1]],
2458                deploy_index: None,
2459                pay_index: Some(non_expiry[1]),
2460            });
2461        }
2462
2463        // Shape: SelfVerify — VERIFY(exec+pay)
2464        if is_verify(0) && scope_of(0) == APPROVE_EXECUTION_AND_PAYMENT {
2465            return Ok(ValidationPrefix {
2466                shape: PrefixShape::SelfVerify,
2467                frame_indices: vec![non_expiry[0]],
2468                deploy_index: None,
2469                pay_index: Some(non_expiry[0]),
2470            });
2471        }
2472
2473        Err(FrameValidationError::UnrecognizedPrefix)
2474    }
2475
2476    /// Validate structural rules for the identified validation prefix.
2477    ///
2478    /// Checks:
2479    /// - Deploy frame (if any) is at index 0 and uses DEFAULT execution mode.
2480    /// - At most one deploy frame exists in the prefix.
2481    /// - self_verify / only_verify / pay frames use VERIFY execution mode.
2482    /// - Resolved target of each VERIFY frame matches `tx.sender` (target == None
2483    ///   means sender; a non-None target must equal sender).
2484    /// - Scope restriction matches the frame's role:
2485    ///   self_verify → `APPROVE_EXECUTION_AND_PAYMENT`, only_verify → `APPROVE_EXECUTION`,
2486    ///   pay → `APPROVE_PAYMENT`.
2487    /// - No frame in the prefix has the atomic-batch flag set.
2488    /// - Total gas budget: Σ(prefix frame gas_limits) + signature_verification_cost() ≤ MAX_VERIFY_GAS.
2489    pub fn validate_prefix_structure(
2490        &self,
2491        prefix: &ValidationPrefix,
2492    ) -> Result<(), FrameValidationError> {
2493        let mut deploy_count = 0usize;
2494
2495        for &idx in &prefix.frame_indices {
2496            let frame = &self.frames[idx];
2497
2498            if frame.is_atomic_batch() {
2499                return Err(FrameValidationError::AtomicBatchInPrefix { frame_index: idx });
2500            }
2501
2502            match prefix.deploy_index {
2503                Some(deploy_idx) if deploy_idx == idx => {
2504                    // This is the deploy frame.
2505                    deploy_count += 1;
2506                    if deploy_count > 1 {
2507                        return Err(FrameValidationError::MultipleDeploys { frame_index: idx });
2508                    }
2509                    // The deploy must be first among prefix frames. `validation_prefix`
2510                    // structurally guarantees this, but the raw frame index can be
2511                    // non-zero when expiry-verifier frames precede the deploy — check
2512                    // against the first element of `frame_indices`, not the raw index.
2513                    if prefix.frame_indices.first() != Some(&idx) {
2514                        return Err(FrameValidationError::DeployNotFirst { frame_index: idx });
2515                    }
2516                    if frame.execution_mode() != FrameMode::Default {
2517                        return Err(FrameValidationError::DeployNotDefaultMode {
2518                            frame_index: idx,
2519                        });
2520                    }
2521                }
2522                _ => {
2523                    // VERIFY frame (self_verify, only_verify, or pay).
2524                    if frame.execution_mode() != FrameMode::Verify {
2525                        return Err(FrameValidationError::VerifyFrameNotVerifyMode {
2526                            frame_index: idx,
2527                        });
2528                    }
2529
2530                    // Resolved target must be tx.sender (None means sender).
2531                    let target_ok = match frame.target {
2532                        None => true,
2533                        Some(addr) => addr == self.sender,
2534                    };
2535                    if !target_ok {
2536                        return Err(FrameValidationError::VerifyTargetNotSender {
2537                            frame_index: idx,
2538                        });
2539                    }
2540
2541                    // Scope restriction must match role.
2542                    let expected_scope = match prefix.shape {
2543                        PrefixShape::SelfVerify | PrefixShape::DeploySelfVerify => {
2544                            APPROVE_EXECUTION_AND_PAYMENT
2545                        }
2546                        PrefixShape::OnlyVerifyPay | PrefixShape::DeployOnlyVerifyPay => {
2547                            // The only_verify frame comes before the pay frame.
2548                            if prefix.pay_index == Some(idx) {
2549                                APPROVE_PAYMENT
2550                            } else {
2551                                APPROVE_EXECUTION
2552                            }
2553                        }
2554                    };
2555                    if frame.scope_restriction() != expected_scope {
2556                        return Err(FrameValidationError::WrongScopeRestriction {
2557                            frame_index: idx,
2558                            expected: expected_scope,
2559                            actual: frame.scope_restriction(),
2560                        });
2561                    }
2562                }
2563            }
2564        }
2565
2566        // Gas budget: prefix frame gas limits + signature cost ≤ MAX_VERIFY_GAS.
2567        let prefix_gas: u64 = prefix
2568            .frame_indices
2569            .iter()
2570            .map(|&i| self.frames[i].gas_limit)
2571            .fold(0u64, |acc, g| acc.saturating_add(g));
2572        let total_verify_gas = prefix_gas.saturating_add(self.signature_verification_cost());
2573        if total_verify_gas > FRAME_TX_MAX_VERIFY_GAS {
2574            return Err(FrameValidationError::VerifyGasBudgetExceeded {
2575                actual: total_verify_gas,
2576                limit: FRAME_TX_MAX_VERIFY_GAS,
2577            });
2578        }
2579
2580        Ok(())
2581    }
2582}
2583
2584/// The four recognized shapes of an EIP-8141 validation prefix (§Mempool).
2585#[derive(Clone, Debug, PartialEq, Eq)]
2586pub enum PrefixShape {
2587    /// Single VERIFY frame: sender verifies + authorizes payment for itself.
2588    SelfVerify,
2589    /// DEFAULT (deploy) frame, then VERIFY frame that verifies + authorizes payment.
2590    DeploySelfVerify,
2591    /// VERIFY(exec) + VERIFY(pay): separate verifier and paymaster.
2592    OnlyVerifyPay,
2593    /// DEFAULT (deploy) + VERIFY(exec) + VERIFY(pay).
2594    DeployOnlyVerifyPay,
2595}
2596
2597/// Identified validation prefix of an EIP-8141 frame transaction.
2598#[derive(Clone, Debug, PartialEq, Eq)]
2599pub struct ValidationPrefix {
2600    /// Recognized shape of this prefix.
2601    pub shape: PrefixShape,
2602    /// Frame indices (into `FrameTransaction.frames`) that form the prefix,
2603    /// in order. Does not include expiry-verifier frames.
2604    pub frame_indices: Vec<usize>,
2605    /// Index of the deploy frame within `frames`, if this shape has one.
2606    pub deploy_index: Option<usize>,
2607    /// Index of the pay (or self_verify) frame within `frames`.
2608    pub pay_index: Option<usize>,
2609}
2610
2611/// Errors produced by `FrameTransaction::validation_prefix` and
2612/// `FrameTransaction::validate_prefix_structure`.
2613#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
2614pub enum FrameValidationError {
2615    #[error("frame transaction validation prefix does not match any recognized shape")]
2616    UnrecognizedPrefix,
2617    #[error("frame {frame_index}: prefix has more than one DEFAULT deploy frame")]
2618    MultipleDeploys { frame_index: usize },
2619    #[error("frame {frame_index}: deploy frame must be at index 0")]
2620    DeployNotFirst { frame_index: usize },
2621    #[error("frame {frame_index}: deploy frame must use DEFAULT execution mode")]
2622    DeployNotDefaultMode { frame_index: usize },
2623    #[error("frame {frame_index}: prefix VERIFY frame does not use VERIFY execution mode")]
2624    VerifyFrameNotVerifyMode { frame_index: usize },
2625    #[error("frame {frame_index}: VERIFY frame target is not tx.sender")]
2626    VerifyTargetNotSender { frame_index: usize },
2627    #[error("frame {frame_index}: scope restriction is {actual:#04x}, expected {expected:#04x}")]
2628    WrongScopeRestriction {
2629        frame_index: usize,
2630        expected: u8,
2631        actual: u8,
2632    },
2633    #[error("frame {frame_index}: prefix frame has atomic-batch flag set")]
2634    AtomicBatchInPrefix { frame_index: usize },
2635    #[error("prefix gas budget exceeded: {actual} > {limit} (MAX_VERIFY_GAS)")]
2636    VerifyGasBudgetExceeded { actual: u64, limit: u64 },
2637}
2638
2639impl RLPEncode for FrameTransaction {
2640    fn encode(&self, buf: &mut dyn bytes::BufMut) {
2641        Encoder::new(buf)
2642            .encode_field(&self.chain_id)
2643            .encode_field(&self.nonce)
2644            .encode_field(&self.sender)
2645            .encode_field(&self.frames)
2646            .encode_field(&self.signatures)
2647            .encode_field(&self.max_priority_fee_per_gas)
2648            .encode_field(&self.max_fee_per_gas)
2649            .encode_field(&self.max_fee_per_blob_gas)
2650            .encode_field(&self.blob_versioned_hashes)
2651            .finish();
2652    }
2653}
2654
2655impl RLPDecode for FrameTransaction {
2656    fn decode_unfinished(rlp: &[u8]) -> Result<(FrameTransaction, &[u8]), RLPDecodeError> {
2657        let decoder = Decoder::new(rlp)?;
2658        let (chain_id, decoder) = decoder.decode_field("chain_id")?;
2659        let (nonce, decoder) = decode_nonce_field(decoder)?;
2660        let (sender, decoder) = decoder.decode_field("sender")?;
2661        let (frames, decoder) = decoder.decode_field("frames")?;
2662        let (signatures, decoder) = decoder.decode_field("signatures")?;
2663        let (max_priority_fee_per_gas, decoder) =
2664            decoder.decode_field("max_priority_fee_per_gas")?;
2665        let (max_fee_per_gas, decoder) = decoder.decode_field("max_fee_per_gas")?;
2666        let (max_fee_per_blob_gas, decoder) = decoder.decode_field("max_fee_per_blob_gas")?;
2667        let (blob_versioned_hashes, decoder) = decoder.decode_field("blob_versioned_hashes")?;
2668        let tx = FrameTransaction {
2669            chain_id,
2670            nonce,
2671            sender,
2672            frames,
2673            signatures,
2674            max_priority_fee_per_gas,
2675            max_fee_per_gas,
2676            max_fee_per_blob_gas,
2677            blob_versioned_hashes,
2678            inner_hash: OnceCell::new(),
2679            cached_canonical: OnceCell::new(),
2680        };
2681        Ok((tx, decoder.finish()?))
2682    }
2683}
2684
2685impl PayloadRLPEncode for FrameTransaction {
2686    fn encode_payload(&self, buf: &mut dyn bytes::BufMut) {
2687        buf.put_u8(TxType::Frame as u8);
2688        self.encode(buf);
2689    }
2690}
2691
2692/// Canonical Transaction Encoding
2693/// Based on [EIP-2718]
2694/// Transactions can be encoded in the following formats:
2695/// 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)
2696/// B) `LegacyTransaction` (An rlp encoded LegacyTransaction)
2697mod canonic_encoding {
2698    use super::*;
2699
2700    impl Transaction {
2701        /// Decodes a single transaction in canonical format
2702        /// Based on [EIP-2718]
2703        /// Transactions can be encoded in the following formats:
2704        /// 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)
2705        /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction)
2706        pub fn decode_canonical(bytes: &[u8]) -> Result<Self, RLPDecodeError> {
2707            // Look at the first byte to check if it corresponds to a TransactionType
2708            match bytes.first() {
2709                // First byte is a valid TransactionType
2710                Some(tx_type) if *tx_type < 0x7f => {
2711                    // Decode tx based on type. `from_type_byte` is the single gate for valid
2712                    // envelope types: it rejects 0x00 (legacy is the bare-list branch below) and
2713                    // any unassigned type byte.
2714                    let tx_bytes = &bytes[1..];
2715                    match EnvelopeTxType::from_type_byte(*tx_type)? {
2716                        EnvelopeTxType::EIP2930 => EIP2930Transaction::decode(tx_bytes)
2717                            .map(Transaction::EIP2930Transaction),
2718                        EnvelopeTxType::EIP1559 => EIP1559Transaction::decode(tx_bytes)
2719                            .map(Transaction::EIP1559Transaction),
2720                        EnvelopeTxType::EIP4844 => EIP4844Transaction::decode(tx_bytes)
2721                            .map(Transaction::EIP4844Transaction),
2722                        EnvelopeTxType::EIP7702 => EIP7702Transaction::decode(tx_bytes)
2723                            .map(Transaction::EIP7702Transaction),
2724                        EnvelopeTxType::Frame => {
2725                            FrameTransaction::decode(tx_bytes).map(Transaction::FrameTransaction)
2726                        }
2727                        EnvelopeTxType::FeeToken => FeeTokenTransaction::decode(tx_bytes)
2728                            .map(Transaction::FeeTokenTransaction),
2729                        EnvelopeTxType::Privileged => PrivilegedL2Transaction::decode(tx_bytes)
2730                            .map(Transaction::PrivilegedL2Transaction),
2731                    }
2732                }
2733                // LegacyTransaction
2734                _ => LegacyTransaction::decode(bytes).map(Transaction::LegacyTransaction),
2735            }
2736        }
2737
2738        /// Encodes a transaction in canonical format
2739        /// Based on [EIP-2718]
2740        /// Transactions can be encoded in the following formats:
2741        /// 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)
2742        /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction)
2743        pub fn encode_canonical(&self, buf: &mut dyn bytes::BufMut) {
2744            match self {
2745                // Legacy transactions don't have a prefix
2746                Transaction::LegacyTransaction(_) => {}
2747                _ => buf.put_u8(self.tx_type() as u8),
2748            }
2749            match self {
2750                Transaction::LegacyTransaction(t) => t.encode(buf),
2751                Transaction::EIP2930Transaction(t) => t.encode(buf),
2752                Transaction::EIP1559Transaction(t) => t.encode(buf),
2753                Transaction::EIP4844Transaction(t) => t.encode(buf),
2754                Transaction::EIP7702Transaction(t) => t.encode(buf),
2755                Transaction::FeeTokenTransaction(t) => t.encode(buf),
2756                Transaction::PrivilegedL2Transaction(t) => t.encode(buf),
2757                Transaction::FrameTransaction(t) => t.encode(buf),
2758            };
2759        }
2760
2761        /// Encodes a transaction in canonical format into a newly created buffer
2762        /// Based on [EIP-2718]
2763        /// Transactions can be encoded in the following formats:
2764        /// 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)
2765        /// B) `LegacyTransaction` (An rlp encoded LegacyTransaction)
2766        pub fn encode_canonical_to_vec(&self) -> Vec<u8> {
2767            if let Some(cell) = self.cached_canonical_cell() {
2768                return cell
2769                    .get_or_init(|| {
2770                        let mut buf = Vec::new();
2771                        self.encode_canonical(&mut buf);
2772                        buf
2773                    })
2774                    .clone();
2775            }
2776            let mut buf = Vec::new();
2777            self.encode_canonical(&mut buf);
2778            buf
2779        }
2780
2781        /// Canonical-encoded length without allocating a buffer. Counts the
2782        /// 1-byte type prefix for typed txs (EIP-2718) plus the inner RLP
2783        /// payload length. Use this when only the size is needed (e.g.
2784        /// admission-time size caps) to avoid `encode_canonical_to_vec().len()`.
2785        pub fn encode_canonical_len(&self) -> usize {
2786            let prefix_len = match self {
2787                Transaction::LegacyTransaction(_) => 0,
2788                _ => 1,
2789            };
2790            let inner_len = match self {
2791                Transaction::LegacyTransaction(t) => t.length(),
2792                Transaction::EIP2930Transaction(t) => t.length(),
2793                Transaction::EIP1559Transaction(t) => t.length(),
2794                Transaction::EIP4844Transaction(t) => t.length(),
2795                Transaction::EIP7702Transaction(t) => t.length(),
2796                Transaction::FeeTokenTransaction(t) => t.length(),
2797                Transaction::PrivilegedL2Transaction(t) => t.length(),
2798                Transaction::FrameTransaction(t) => t.length(),
2799            };
2800            prefix_len + inner_len
2801        }
2802    }
2803
2804    impl P2PTransaction {
2805        pub fn tx_type(&self) -> TxType {
2806            match self {
2807                P2PTransaction::LegacyTransaction(_) => TxType::Legacy,
2808                P2PTransaction::EIP2930Transaction(_) => TxType::EIP2930,
2809                P2PTransaction::EIP1559Transaction(_) => TxType::EIP1559,
2810                P2PTransaction::EIP4844TransactionWithBlobs(_) => TxType::EIP4844,
2811                P2PTransaction::EIP7702Transaction(_) => TxType::EIP7702,
2812                P2PTransaction::FeeTokenTransaction(_) => TxType::FeeToken,
2813                P2PTransaction::FrameTransaction(_) => TxType::Frame,
2814            }
2815        }
2816
2817        pub fn encode_canonical(&self, buf: &mut dyn bytes::BufMut) {
2818            match self {
2819                // Legacy transactions don't have a prefix
2820                P2PTransaction::LegacyTransaction(_) => {}
2821                _ => buf.put_u8(self.tx_type() as u8),
2822            }
2823            match self {
2824                P2PTransaction::LegacyTransaction(t) => t.encode(buf),
2825                P2PTransaction::EIP2930Transaction(t) => t.encode(buf),
2826                P2PTransaction::EIP1559Transaction(t) => t.encode(buf),
2827                P2PTransaction::EIP4844TransactionWithBlobs(t) => t.encode(buf),
2828                P2PTransaction::EIP7702Transaction(t) => t.encode(buf),
2829                P2PTransaction::FeeTokenTransaction(t) => t.encode(buf),
2830                P2PTransaction::FrameTransaction(t) => t.encode(buf),
2831            };
2832        }
2833
2834        pub fn encode_canonical_to_vec(&self) -> Vec<u8> {
2835            let mut buf = Vec::new();
2836            self.encode_canonical(&mut buf);
2837            buf
2838        }
2839
2840        /// Canonical encoded length without allocating (mirrors
2841        /// [`P2PTransaction::encode_canonical`]); equals
2842        /// `encode_canonical_to_vec().len()`. For the blob variant this includes
2843        /// the full sidecar, since `EIP4844TransactionWithBlobs` encodes it.
2844        pub fn encode_canonical_len(&self) -> usize {
2845            let prefix_len = match self {
2846                P2PTransaction::LegacyTransaction(_) => 0,
2847                _ => 1,
2848            };
2849            let inner_len = match self {
2850                P2PTransaction::LegacyTransaction(t) => t.length(),
2851                P2PTransaction::EIP2930Transaction(t) => t.length(),
2852                P2PTransaction::EIP1559Transaction(t) => t.length(),
2853                P2PTransaction::EIP4844TransactionWithBlobs(t) => t.length(),
2854                P2PTransaction::EIP7702Transaction(t) => t.length(),
2855                P2PTransaction::FeeTokenTransaction(t) => t.length(),
2856                P2PTransaction::FrameTransaction(t) => t.length(),
2857            };
2858            prefix_len + inner_len
2859        }
2860
2861        pub fn compute_hash(&self) -> H256 {
2862            match self {
2863                P2PTransaction::LegacyTransaction(t) => {
2864                    Transaction::LegacyTransaction(t.clone()).compute_hash(&NativeCrypto)
2865                }
2866                P2PTransaction::EIP2930Transaction(t) => {
2867                    Transaction::EIP2930Transaction(t.clone()).compute_hash(&NativeCrypto)
2868                }
2869                P2PTransaction::EIP1559Transaction(t) => {
2870                    Transaction::EIP1559Transaction(t.clone()).compute_hash(&NativeCrypto)
2871                }
2872                P2PTransaction::EIP4844TransactionWithBlobs(t) => {
2873                    Transaction::EIP4844Transaction(t.tx.clone()).compute_hash(&NativeCrypto)
2874                }
2875                P2PTransaction::EIP7702Transaction(t) => {
2876                    Transaction::EIP7702Transaction(t.clone()).compute_hash(&NativeCrypto)
2877                }
2878                P2PTransaction::FeeTokenTransaction(t) => {
2879                    Transaction::FeeTokenTransaction(t.clone()).compute_hash(&NativeCrypto)
2880                }
2881                P2PTransaction::FrameTransaction(t) => {
2882                    Transaction::FrameTransaction(t.clone()).compute_hash(&NativeCrypto)
2883                }
2884            }
2885        }
2886    }
2887}
2888
2889// Serialization
2890// This is used for RPC messaging and passing data into a RISC-V zkVM
2891
2892mod serde_impl {
2893    use ethereum_types::H160;
2894    use serde::Deserialize;
2895    use serde::{Deserializer, de::Error};
2896    use serde_json::Value;
2897    use std::{collections::HashMap, str::FromStr};
2898
2899    #[cfg(feature = "c-kzg")]
2900    use crate::types::BYTES_PER_BLOB;
2901    use crate::types::{AccessListItem, AuthorizationTuple, BlobsBundleError};
2902
2903    use super::*;
2904
2905    impl Serialize for TxKind {
2906        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2907        where
2908            S: serde::Serializer,
2909        {
2910            match self {
2911                TxKind::Call(address) => serializer.serialize_str(&format!("{address:#x}")),
2912                TxKind::Create => serializer.serialize_none(),
2913            }
2914        }
2915    }
2916
2917    impl<'de> Deserialize<'de> for TxKind {
2918        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2919        where
2920            D: serde::Deserializer<'de>,
2921        {
2922            let str_option = Option::<String>::deserialize(deserializer)?;
2923            match str_option {
2924                Some(str) if !str.is_empty() => Ok(TxKind::Call(
2925                    Address::from_str(str.trim_start_matches("0x")).map_err(|_| {
2926                        serde::de::Error::custom(format!("Failed to deserialize hex value {str}"))
2927                    })?,
2928                )),
2929                _ => Ok(TxKind::Create),
2930            }
2931        }
2932    }
2933
2934    impl Serialize for TxType {
2935        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2936        where
2937            S: serde::Serializer,
2938        {
2939            serializer.serialize_str(&format!("{:#x}", *self as u8))
2940        }
2941    }
2942
2943    impl<'de> Deserialize<'de> for TxType {
2944        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2945        where
2946            D: serde::Deserializer<'de>,
2947        {
2948            let str = String::deserialize(deserializer)?;
2949            let tx_num = u8::from_str_radix(str.trim_start_matches("0x"), 16).map_err(|_| {
2950                serde::de::Error::custom(format!("Failed to deserialize hex value {str}"))
2951            })?;
2952            TxType::from_u8(tx_num).ok_or_else(|| {
2953                serde::de::Error::custom(format!("Invalid transaction type {tx_num}"))
2954            })
2955        }
2956    }
2957
2958    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2959    #[serde(rename_all = "camelCase")]
2960    pub struct AccessListEntry {
2961        pub address: Address,
2962        pub storage_keys: Vec<H256>,
2963    }
2964
2965    impl From<&AccessListItem> for AccessListEntry {
2966        fn from(value: &AccessListItem) -> AccessListEntry {
2967            AccessListEntry {
2968                address: value.0,
2969                storage_keys: value.1.clone(),
2970            }
2971        }
2972    }
2973
2974    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
2975    #[serde(rename_all = "camelCase")]
2976    pub struct AuthorizationTupleEntry {
2977        pub chain_id: U256,
2978        pub address: Address,
2979        #[serde(default, with = "crate::serde_utils::u64::hex_str")]
2980        pub nonce: u64,
2981        pub y_parity: U256,
2982        pub r: U256,
2983        pub s: U256,
2984    }
2985
2986    impl From<&AuthorizationTuple> for AuthorizationTupleEntry {
2987        fn from(value: &AuthorizationTuple) -> AuthorizationTupleEntry {
2988            AuthorizationTupleEntry {
2989                chain_id: value.chain_id,
2990                address: value.address,
2991                nonce: value.nonce,
2992                y_parity: value.y_parity,
2993                r: value.r_signature,
2994                s: value.s_signature,
2995            }
2996        }
2997    }
2998
2999    impl From<AuthorizationTupleEntry> for AuthorizationTuple {
3000        fn from(entry: AuthorizationTupleEntry) -> AuthorizationTuple {
3001            AuthorizationTuple {
3002                chain_id: entry.chain_id,
3003                address: entry.address,
3004                nonce: entry.nonce,
3005                y_parity: entry.y_parity,
3006                r_signature: entry.r,
3007                s_signature: entry.s,
3008            }
3009        }
3010    }
3011
3012    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3013    #[serde(rename_all = "camelCase")]
3014    pub struct FrameEntry {
3015        #[serde(with = "crate::serde_utils::u64::hex_str")]
3016        pub mode: u64,
3017        #[serde(with = "crate::serde_utils::u64::hex_str")]
3018        pub flags: u64,
3019        pub to: Option<Address>,
3020        #[serde(with = "crate::serde_utils::u64::hex_str")]
3021        pub gas_limit: u64,
3022        #[serde(
3023            default,
3024            serialize_with = "serialize_u256_hex",
3025            deserialize_with = "crate::serde_utils::u256::deser_hex_str"
3026        )]
3027        pub value: U256,
3028        #[serde(with = "crate::serde_utils::bytes")]
3029        pub data: Bytes,
3030    }
3031
3032    /// JSON shape of an EIP-8141 outer signature.
3033    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
3034    #[serde(rename_all = "camelCase")]
3035    pub struct SignatureEntry {
3036        #[serde(with = "crate::serde_utils::u64::hex_str")]
3037        pub scheme: u64,
3038        pub signer: Option<Address>,
3039        #[serde(with = "crate::serde_utils::bytes")]
3040        pub msg: Bytes,
3041        #[serde(with = "crate::serde_utils::bytes")]
3042        pub signature: Bytes,
3043    }
3044
3045    impl From<&FrameSignature> for SignatureEntry {
3046        fn from(value: &FrameSignature) -> SignatureEntry {
3047            SignatureEntry {
3048                scheme: value.scheme as u64,
3049                signer: value.signer,
3050                msg: value.msg.clone(),
3051                signature: value.signature.clone(),
3052            }
3053        }
3054    }
3055
3056    fn serialize_u256_hex<S: serde::Serializer>(value: &U256, s: S) -> Result<S::Ok, S::Error> {
3057        s.serialize_str(&format!("{value:#x}"))
3058    }
3059
3060    impl From<&Frame> for FrameEntry {
3061        fn from(value: &Frame) -> FrameEntry {
3062            FrameEntry {
3063                mode: value.mode as u64,
3064                flags: value.flags as u64,
3065                to: value.target,
3066                gas_limit: value.gas_limit,
3067                value: value.value,
3068                data: value.data.clone(),
3069            }
3070        }
3071    }
3072
3073    impl From<FrameEntry> for Frame {
3074        fn from(entry: FrameEntry) -> Frame {
3075            Frame {
3076                mode: entry.mode as u8,
3077                flags: entry.flags as u8,
3078                target: entry.to,
3079                gas_limit: entry.gas_limit,
3080                value: entry.value,
3081                data: entry.data,
3082            }
3083        }
3084    }
3085
3086    impl Serialize for LegacyTransaction {
3087        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3088        where
3089            S: serde::Serializer,
3090        {
3091            let mut struct_serializer = serializer.serialize_struct("LegacyTransaction", 11)?;
3092            struct_serializer.serialize_field("type", &TxType::Legacy)?;
3093            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3094            struct_serializer.serialize_field("to", &self.to)?;
3095            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas))?;
3096            struct_serializer.serialize_field("value", &self.value)?;
3097            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3098            struct_serializer.serialize_field("gasPrice", &format!("{:#x}", self.gas_price))?;
3099            struct_serializer.serialize_field(
3100                "chainId",
3101                &format!("{:#x}", derive_legacy_chain_id(self.v).unwrap_or_default()),
3102            )?;
3103            struct_serializer.serialize_field("v", &self.v)?;
3104            struct_serializer.serialize_field("r", &self.r)?;
3105            struct_serializer.serialize_field("s", &self.s)?;
3106            struct_serializer.end()
3107        }
3108    }
3109
3110    impl Serialize for EIP2930Transaction {
3111        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3112        where
3113            S: serde::Serializer,
3114        {
3115            let mut struct_serializer = serializer.serialize_struct("Eip2930Transaction", 12)?;
3116            struct_serializer.serialize_field("type", &TxType::EIP2930)?;
3117            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3118            struct_serializer.serialize_field("to", &self.to)?;
3119            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3120            struct_serializer.serialize_field("value", &self.value)?;
3121            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3122            struct_serializer.serialize_field("gasPrice", &format!("{:#x}", self.gas_price))?;
3123            struct_serializer.serialize_field(
3124                "accessList",
3125                &self
3126                    .access_list
3127                    .iter()
3128                    .map(AccessListEntry::from)
3129                    .collect::<Vec<_>>(),
3130            )?;
3131            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3132            struct_serializer
3133                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3134            struct_serializer
3135                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests
3136            struct_serializer.serialize_field("r", &self.signature_r)?;
3137            struct_serializer.serialize_field("s", &self.signature_s)?;
3138            struct_serializer.end()
3139        }
3140    }
3141
3142    impl Serialize for EIP1559Transaction {
3143        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3144        where
3145            S: serde::Serializer,
3146        {
3147            let mut struct_serializer = serializer.serialize_struct("Eip1559Transaction", 14)?;
3148            struct_serializer.serialize_field("type", &TxType::EIP1559)?;
3149            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3150            struct_serializer.serialize_field("to", &self.to)?;
3151            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3152            struct_serializer.serialize_field("value", &self.value)?;
3153            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3154            struct_serializer.serialize_field(
3155                "maxPriorityFeePerGas",
3156                &format!("{:#x}", self.max_priority_fee_per_gas),
3157            )?;
3158            struct_serializer
3159                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3160            struct_serializer
3161                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3162            struct_serializer.serialize_field(
3163                "accessList",
3164                &self
3165                    .access_list
3166                    .iter()
3167                    .map(AccessListEntry::from)
3168                    .collect::<Vec<_>>(),
3169            )?;
3170            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3171            struct_serializer
3172                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3173            struct_serializer
3174                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests
3175            struct_serializer.serialize_field("r", &self.signature_r)?;
3176            struct_serializer.serialize_field("s", &self.signature_s)?;
3177            struct_serializer.end()
3178        }
3179    }
3180
3181    impl Serialize for EIP4844Transaction {
3182        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3183        where
3184            S: serde::Serializer,
3185        {
3186            let mut struct_serializer = serializer.serialize_struct("Eip4844Transaction", 15)?;
3187            struct_serializer.serialize_field("type", &TxType::EIP4844)?;
3188            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3189            struct_serializer.serialize_field("to", &self.to)?;
3190            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas))?;
3191            struct_serializer.serialize_field("value", &self.value)?;
3192            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3193            struct_serializer.serialize_field(
3194                "maxPriorityFeePerGas",
3195                &format!("{:#x}", self.max_priority_fee_per_gas),
3196            )?;
3197            struct_serializer
3198                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3199            struct_serializer
3200                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3201            struct_serializer.serialize_field(
3202                "maxFeePerBlobGas",
3203                &format!("{:#x}", self.max_fee_per_blob_gas),
3204            )?;
3205            struct_serializer.serialize_field(
3206                "accessList",
3207                &self
3208                    .access_list
3209                    .iter()
3210                    .map(AccessListEntry::from)
3211                    .collect::<Vec<_>>(),
3212            )?;
3213            struct_serializer
3214                .serialize_field("blobVersionedHashes", &self.blob_versioned_hashes)?;
3215            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3216            struct_serializer
3217                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3218            struct_serializer
3219                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests
3220            struct_serializer.serialize_field("r", &self.signature_r)?;
3221            struct_serializer.serialize_field("s", &self.signature_s)?;
3222            struct_serializer.end()
3223        }
3224    }
3225
3226    impl Serialize for EIP7702Transaction {
3227        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3228        where
3229            S: serde::Serializer,
3230        {
3231            let mut struct_serializer = serializer.serialize_struct("Eip7702Transaction", 15)?;
3232            struct_serializer.serialize_field("type", &TxType::EIP7702)?;
3233            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3234            struct_serializer.serialize_field("to", &self.to)?;
3235            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3236            struct_serializer.serialize_field("value", &self.value)?;
3237            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3238            struct_serializer.serialize_field(
3239                "maxPriorityFeePerGas",
3240                &format!("{:#x}", self.max_priority_fee_per_gas),
3241            )?;
3242            struct_serializer
3243                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3244            struct_serializer
3245                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3246            struct_serializer.serialize_field(
3247                "accessList",
3248                &self
3249                    .access_list
3250                    .iter()
3251                    .map(AccessListEntry::from)
3252                    .collect::<Vec<_>>(),
3253            )?;
3254            struct_serializer.serialize_field(
3255                "authorizationList",
3256                &self
3257                    .authorization_list
3258                    .iter()
3259                    .map(AuthorizationTupleEntry::from)
3260                    .collect::<Vec<_>>(),
3261            )?;
3262            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3263            struct_serializer
3264                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3265            struct_serializer
3266                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?; // added to match Hive tests
3267            struct_serializer.serialize_field("r", &self.signature_r)?;
3268            struct_serializer.serialize_field("s", &self.signature_s)?;
3269            struct_serializer.end()
3270        }
3271    }
3272
3273    impl Serialize for PrivilegedL2Transaction {
3274        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3275        where
3276            S: serde::Serializer,
3277        {
3278            let mut struct_serializer = serializer.serialize_struct("Eip1559Transaction", 14)?;
3279            struct_serializer.serialize_field("type", &TxType::Privileged)?;
3280            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3281            struct_serializer.serialize_field("to", &self.to)?;
3282            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3283            struct_serializer.serialize_field("value", &self.value)?;
3284            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3285            struct_serializer.serialize_field(
3286                "maxPriorityFeePerGas",
3287                &format!("{:#x}", self.max_priority_fee_per_gas),
3288            )?;
3289            struct_serializer
3290                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3291            struct_serializer
3292                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3293            struct_serializer.serialize_field(
3294                "accessList",
3295                &self
3296                    .access_list
3297                    .iter()
3298                    .map(AccessListEntry::from)
3299                    .collect::<Vec<_>>(),
3300            )?;
3301            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3302            struct_serializer.serialize_field("sender", &self.from)?;
3303            struct_serializer.end()
3304        }
3305    }
3306
3307    impl Serialize for FeeTokenTransaction {
3308        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3309        where
3310            S: serde::Serializer,
3311        {
3312            let mut struct_serializer = serializer.serialize_struct("FeeTokenTransaction", 15)?;
3313            struct_serializer.serialize_field("type", &TxType::FeeToken)?;
3314            struct_serializer.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3315            struct_serializer.serialize_field("to", &self.to)?;
3316            struct_serializer.serialize_field("gas", &format!("{:#x}", self.gas_limit))?;
3317            struct_serializer.serialize_field("value", &self.value)?;
3318            struct_serializer.serialize_field("input", &format!("0x{:x}", self.data))?;
3319            struct_serializer.serialize_field(
3320                "maxPriorityFeePerGas",
3321                &format!("{:#x}", self.max_priority_fee_per_gas),
3322            )?;
3323            struct_serializer
3324                .serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3325            struct_serializer
3326                .serialize_field("gasPrice", &format!("{:#x}", self.max_fee_per_gas))?;
3327            struct_serializer.serialize_field(
3328                "accessList",
3329                &self
3330                    .access_list
3331                    .iter()
3332                    .map(AccessListEntry::from)
3333                    .collect::<Vec<_>>(),
3334            )?;
3335            struct_serializer.serialize_field("feeToken", &format!("{:#x}", self.fee_token))?;
3336            struct_serializer.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3337            struct_serializer
3338                .serialize_field("yParity", &format!("{:#x}", self.signature_y_parity as u8))?;
3339            struct_serializer
3340                .serialize_field("v", &format!("{:#x}", self.signature_y_parity as u8))?;
3341            struct_serializer.serialize_field("r", &self.signature_r)?;
3342            struct_serializer.serialize_field("s", &self.signature_s)?;
3343            struct_serializer.end()
3344        }
3345    }
3346
3347    impl Serialize for FrameTransaction {
3348        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
3349        where
3350            S: serde::Serializer,
3351        {
3352            let mut s = serializer.serialize_struct("FrameTransaction", 10)?;
3353            s.serialize_field("type", &TxType::Frame)?;
3354            s.serialize_field("chainId", &format!("{:#x}", self.chain_id))?;
3355            s.serialize_field("nonce", &format!("{:#x}", self.nonce))?;
3356            s.serialize_field("sender", &format!("{:#x}", self.sender))?;
3357            s.serialize_field(
3358                "frames",
3359                &self.frames.iter().map(FrameEntry::from).collect::<Vec<_>>(),
3360            )?;
3361            s.serialize_field(
3362                "signatures",
3363                &self
3364                    .signatures
3365                    .iter()
3366                    .map(SignatureEntry::from)
3367                    .collect::<Vec<_>>(),
3368            )?;
3369            s.serialize_field(
3370                "maxPriorityFeePerGas",
3371                &format!("{:#x}", self.max_priority_fee_per_gas),
3372            )?;
3373            s.serialize_field("maxFeePerGas", &format!("{:#x}", self.max_fee_per_gas))?;
3374            s.serialize_field("maxFeePerBlobGas", &self.max_fee_per_blob_gas)?;
3375            s.serialize_field("blobVersionedHashes", &self.blob_versioned_hashes)?;
3376            s.end()
3377        }
3378    }
3379
3380    impl<'de> Deserialize<'de> for Transaction {
3381        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3382        where
3383            D: serde::Deserializer<'de>,
3384        {
3385            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3386            let tx_type =
3387                serde_json::from_value::<TxType>(map.remove("type").unwrap_or(Value::default()))
3388                    .unwrap_or_else(|_| {
3389                        if map.contains_key("tx_type") {
3390                            return TxType::Privileged;
3391                        }
3392                        TxType::EIP1559
3393                    });
3394
3395            let iter = map.into_iter();
3396            match tx_type {
3397                TxType::Legacy => {
3398                    LegacyTransaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3399                        .map(Transaction::LegacyTransaction)
3400                        .map_err(|e| {
3401                            serde::de::Error::custom(format!("Couldn't Deserialize Legacy {e}"))
3402                        })
3403                }
3404                TxType::EIP2930 => {
3405                    EIP2930Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3406                        .map(Transaction::EIP2930Transaction)
3407                        .map_err(|e| {
3408                            serde::de::Error::custom(format!("Couldn't Deserialize EIP2930 {e}"))
3409                        })
3410                }
3411                TxType::EIP1559 => {
3412                    EIP1559Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3413                        .map(Transaction::EIP1559Transaction)
3414                        .map_err(|e| {
3415                            serde::de::Error::custom(format!("Couldn't Deserialize EIP1559 {e}"))
3416                        })
3417                }
3418                TxType::EIP4844 => {
3419                    EIP4844Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3420                        .map(Transaction::EIP4844Transaction)
3421                        .map_err(|e| {
3422                            serde::de::Error::custom(format!("Couldn't Deserialize EIP4844 {e}"))
3423                        })
3424                }
3425                TxType::EIP7702 => {
3426                    EIP7702Transaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3427                        .map(Transaction::EIP7702Transaction)
3428                        .map_err(|e| {
3429                            serde::de::Error::custom(format!("Couldn't Deserialize EIP7702 {e}"))
3430                        })
3431                }
3432                TxType::Privileged => PrivilegedL2Transaction::deserialize(
3433                    serde::de::value::MapDeserializer::new(iter),
3434                )
3435                .map(Transaction::PrivilegedL2Transaction)
3436                .map_err(|e| {
3437                    serde::de::Error::custom(format!("Couldn't Deserialize Privileged: {e}"))
3438                }),
3439                TxType::FeeToken => {
3440                    FeeTokenTransaction::deserialize(serde::de::value::MapDeserializer::new(iter))
3441                        .map(Transaction::FeeTokenTransaction)
3442                        .map_err(|e| {
3443                            serde::de::Error::custom(format!("Couldn't Deserialize FeeToken {e}"))
3444                        })
3445                }
3446                TxType::Frame => Err(serde::de::Error::custom(
3447                    "Frame transaction JSON deserialization not supported",
3448                )),
3449            }
3450        }
3451    }
3452
3453    fn deserialize_input_field(
3454        map: &mut std::collections::HashMap<String, Value>,
3455    ) -> Result<Bytes, serde_json::Error> {
3456        let data_str: String = serde_json::from_value(
3457            map.remove("input")
3458                .ok_or_else(|| serde::de::Error::missing_field("input"))?,
3459        )
3460        .map_err(serde::de::Error::custom)?;
3461        if let Some(stripped) = data_str.strip_prefix("0x") {
3462            match hex::decode(stripped) {
3463                Ok(decoded_bytes) => Ok(Bytes::from(decoded_bytes)),
3464                Err(_) => Err(serde::de::Error::custom(
3465                    "Invalid hex format in 'input' field",
3466                ))?,
3467            }
3468        } else {
3469            Err(serde::de::Error::custom(
3470                "'input' field must start with '0x'",
3471            ))?
3472        }
3473    }
3474
3475    fn deserialize_field<'de, T, D>(
3476        map: &mut HashMap<String, serde_json::Value>,
3477        key: &str,
3478    ) -> Result<T, D::Error>
3479    where
3480        D: serde::Deserializer<'de>,
3481        T: serde::de::DeserializeOwned,
3482    {
3483        map.remove(key)
3484            .ok_or_else(|| D::Error::custom(format!("Missing field: {key}")))
3485            .and_then(|value| {
3486                serde_json::from_value(value).map_err(|err| D::Error::custom(err.to_string()))
3487            })
3488    }
3489
3490    fn deserialize_u64_field<'de, D>(
3491        map: &mut HashMap<String, serde_json::Value>,
3492        key: &str,
3493    ) -> Result<u64, D::Error>
3494    where
3495        D: serde::Deserializer<'de>,
3496    {
3497        let value = deserialize_field::<U256, D>(map, key)?;
3498        u64::try_from(value).map_err(|_| D::Error::custom(format!("{key} value overflows u64")))
3499    }
3500
3501    impl<'de> Deserialize<'de> for LegacyTransaction {
3502        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3503        where
3504            D: serde::Deserializer<'de>,
3505        {
3506            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3507
3508            Ok(LegacyTransaction {
3509                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3510                gas_price: deserialize_field::<U256, D>(&mut map, "gasPrice")?,
3511                gas: deserialize_u64_field::<D>(&mut map, "gas")?,
3512                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3513                value: deserialize_field::<U256, D>(&mut map, "value")?,
3514                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3515                v: deserialize_field::<U256, D>(&mut map, "v")?,
3516                r: deserialize_field::<U256, D>(&mut map, "r")?,
3517                s: deserialize_field::<U256, D>(&mut map, "s")?,
3518                ..Default::default()
3519            })
3520        }
3521    }
3522
3523    impl<'de> Deserialize<'de> for EIP2930Transaction {
3524        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3525        where
3526            D: serde::Deserializer<'de>,
3527        {
3528            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3529
3530            Ok(EIP2930Transaction {
3531                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3532                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3533                gas_price: deserialize_field::<U256, D>(&mut map, "gasPrice")?,
3534                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3535                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3536                value: deserialize_field::<U256, D>(&mut map, "value")?,
3537                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3538                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3539                    .into_iter()
3540                    .map(|v| (v.address, v.storage_keys))
3541                    .collect::<Vec<_>>(),
3542                signature_y_parity: u8::from_str_radix(
3543                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3544                    16,
3545                )
3546                .map_err(serde::de::Error::custom)?
3547                    != 0,
3548                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3549                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3550                ..Default::default()
3551            })
3552        }
3553    }
3554
3555    impl<'de> Deserialize<'de> for EIP1559Transaction {
3556        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3557        where
3558            D: serde::Deserializer<'de>,
3559        {
3560            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3561            Ok(EIP1559Transaction {
3562                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3563                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3564                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3565                    &mut map,
3566                    "maxPriorityFeePerGas",
3567                )?,
3568                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3569                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3570                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3571                value: deserialize_field::<U256, D>(&mut map, "value")?,
3572                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3573                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3574                    .into_iter()
3575                    .map(|v| (v.address, v.storage_keys))
3576                    .collect::<Vec<_>>(),
3577                signature_y_parity: u8::from_str_radix(
3578                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3579                    16,
3580                )
3581                .map_err(serde::de::Error::custom)?
3582                    != 0,
3583                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3584                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3585                ..Default::default()
3586            })
3587        }
3588    }
3589
3590    impl<'de> Deserialize<'de> for EIP4844Transaction {
3591        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3592        where
3593            D: serde::Deserializer<'de>,
3594        {
3595            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3596
3597            Ok(EIP4844Transaction {
3598                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3599                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3600                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3601                    &mut map,
3602                    "maxPriorityFeePerGas",
3603                )?,
3604                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3605                gas: deserialize_u64_field::<D>(&mut map, "gas")?,
3606                to: deserialize_field::<Address, D>(&mut map, "to")?,
3607                value: deserialize_field::<U256, D>(&mut map, "value")?,
3608                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3609                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3610                    .into_iter()
3611                    .map(|v| (v.address, v.storage_keys))
3612                    .collect::<Vec<_>>(),
3613                max_fee_per_blob_gas: deserialize_field::<U256, D>(&mut map, "maxFeePerBlobGas")?,
3614                blob_versioned_hashes: deserialize_field::<Vec<H256>, D>(
3615                    &mut map,
3616                    "blobVersionedHashes",
3617                )?,
3618                signature_y_parity: u8::from_str_radix(
3619                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3620                    16,
3621                )
3622                .map_err(serde::de::Error::custom)?
3623                    != 0,
3624                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3625                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3626                ..Default::default()
3627            })
3628        }
3629    }
3630
3631    impl<'de> Deserialize<'de> for EIP7702Transaction {
3632        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3633        where
3634            D: serde::Deserializer<'de>,
3635        {
3636            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3637
3638            Ok(EIP7702Transaction {
3639                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3640                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3641                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3642                    &mut map,
3643                    "maxPriorityFeePerGas",
3644                )?,
3645                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3646                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3647                to: deserialize_field::<Address, D>(&mut map, "to")?,
3648                value: deserialize_field::<U256, D>(&mut map, "value")?,
3649                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3650                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3651                    .into_iter()
3652                    .map(|v| (v.address, v.storage_keys))
3653                    .collect::<Vec<_>>(),
3654                authorization_list: deserialize_field::<Vec<AuthorizationTupleEntry>, D>(
3655                    &mut map,
3656                    "authorizationList",
3657                )?
3658                .into_iter()
3659                .map(AuthorizationTuple::from)
3660                .collect::<Vec<_>>(),
3661                signature_y_parity: u8::from_str_radix(
3662                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3663                    16,
3664                )
3665                .map_err(serde::de::Error::custom)?
3666                    != 0,
3667                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3668                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3669                ..Default::default()
3670            })
3671        }
3672    }
3673
3674    impl<'de> Deserialize<'de> for PrivilegedL2Transaction {
3675        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3676        where
3677            D: serde::Deserializer<'de>,
3678        {
3679            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3680
3681            Ok(PrivilegedL2Transaction {
3682                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3683                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3684                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3685                    &mut map,
3686                    "maxPriorityFeePerGas",
3687                )?,
3688                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3689                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3690                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3691                value: deserialize_field::<U256, D>(&mut map, "value")?,
3692                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3693                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3694                    .into_iter()
3695                    .map(|v| (v.address, v.storage_keys))
3696                    .collect::<Vec<_>>(),
3697                from: deserialize_field::<Address, D>(&mut map, "sender")?,
3698                ..Default::default()
3699            })
3700        }
3701    }
3702
3703    impl<'de> Deserialize<'de> for FeeTokenTransaction {
3704        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3705        where
3706            D: serde::Deserializer<'de>,
3707        {
3708            let mut map = <HashMap<String, serde_json::Value>>::deserialize(deserializer)?;
3709
3710            Ok(FeeTokenTransaction {
3711                chain_id: deserialize_u64_field::<D>(&mut map, "chainId")?,
3712                nonce: deserialize_u64_field::<D>(&mut map, "nonce")?,
3713                max_priority_fee_per_gas: deserialize_u64_field::<D>(
3714                    &mut map,
3715                    "maxPriorityFeePerGas",
3716                )?,
3717                max_fee_per_gas: deserialize_u64_field::<D>(&mut map, "maxFeePerGas")?,
3718                gas_limit: deserialize_u64_field::<D>(&mut map, "gas")?,
3719                to: deserialize_field::<TxKind, D>(&mut map, "to")?,
3720                value: deserialize_field::<U256, D>(&mut map, "value")?,
3721                data: deserialize_input_field(&mut map).map_err(serde::de::Error::custom)?,
3722                access_list: deserialize_field::<Vec<AccessListEntry>, D>(&mut map, "accessList")?
3723                    .into_iter()
3724                    .map(|v| (v.address, v.storage_keys))
3725                    .collect::<Vec<_>>(),
3726                fee_token: deserialize_field::<Address, D>(&mut map, "feeToken")?,
3727                signature_y_parity: u8::from_str_radix(
3728                    deserialize_field::<String, D>(&mut map, "yParity")?.trim_start_matches("0x"),
3729                    16,
3730                )
3731                .map_err(serde::de::Error::custom)?
3732                    != 0,
3733                signature_r: deserialize_field::<U256, D>(&mut map, "r")?,
3734                signature_s: deserialize_field::<U256, D>(&mut map, "s")?,
3735                ..Default::default()
3736            })
3737        }
3738    }
3739
3740    #[derive(Debug, thiserror::Error)]
3741    pub enum GenericTransactionError {
3742        #[error("Invalid transaction type: {0}")]
3743        InvalidTxType(TxType),
3744        #[error("Blob bundle error: {0}")]
3745        BlobBundleError(#[from] BlobsBundleError),
3746        #[error("Missing field: {0}")]
3747        MissingField(String),
3748        #[error("Invalid field: {0}")]
3749        InvalidField(String),
3750    }
3751
3752    /// Unsigned Transaction struct generic to all types which may not contain all required transaction fields
3753    /// Used to perform gas estimations and access list creation
3754    #[derive(Deserialize, Debug, PartialEq, Clone, Default)]
3755    #[serde(rename_all = "camelCase")]
3756    pub struct GenericTransaction {
3757        #[serde(default)]
3758        pub r#type: TxType,
3759        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3760        pub nonce: Option<u64>,
3761        pub to: TxKind,
3762        #[serde(default)]
3763        pub from: Address,
3764        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3765        pub gas: Option<u64>,
3766        #[serde(default)]
3767        pub value: U256,
3768        #[serde(default)]
3769        pub gas_price: U256,
3770        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3771        pub max_priority_fee_per_gas: Option<u64>,
3772        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3773        pub max_fee_per_gas: Option<u64>,
3774        pub max_fee_per_blob_gas: Option<U256>,
3775        #[serde(default)]
3776        pub access_list: Vec<AccessListEntry>,
3777        #[serde(default)]
3778        pub fee_token: Option<Address>,
3779        #[serde(default)]
3780        pub authorization_list: Option<Vec<AuthorizationTupleEntry>>,
3781        #[serde(default)]
3782        pub blob_versioned_hashes: Vec<H256>,
3783        pub wrapper_version: Option<u8>,
3784        #[serde(default, with = "crate::serde_utils::bytes::vec")]
3785        pub blobs: Vec<Bytes>,
3786        #[serde(default, with = "crate::serde_utils::u64::hex_str_opt")]
3787        pub chain_id: Option<u64>,
3788        // rename is needed here so we dont attempt to deserialize the `input` field rather than the remainder of the fields
3789        #[serde(
3790            flatten,
3791            rename = "input_or_data",
3792            deserialize_with = "deserialize_input",
3793            serialize_with = "crate::serde_utils::bytes::serialize"
3794        )]
3795        pub input: Bytes,
3796    }
3797    /// Custom deserialization function to parse either `data` or `input` fields, or both as long as they have the same value
3798    pub fn deserialize_input<'de, D>(deserializer: D) -> Result<Bytes, D::Error>
3799    where
3800        D: Deserializer<'de>,
3801    {
3802        // The input field can be named either input or data
3803        // In case we have both fields both should be named the same
3804        let variables = HashMap::<String, Value>::deserialize(deserializer)?;
3805        let data = variables.get("data");
3806        let input = variables.get("input");
3807        let value = match (data, input) {
3808            // This replaces `default` attribute for this custom implementation
3809            (None, None) => return Ok(Bytes::new()),
3810            (None, Some(val)) => val,
3811            (Some(val), None) => val,
3812            (Some(val_a), Some(val_b)) => {
3813                if val_a == val_b {
3814                    val_a
3815                } else {
3816                    return Err(D::Error::custom(
3817                        "Transaction has both `data` and `input` fields with different values",
3818                    ));
3819                }
3820            }
3821        };
3822        let value = String::deserialize(value).map_err(D::Error::custom)?;
3823        let bytes = hex::decode(value.trim_start_matches("0x"))
3824            .map_err(|e| D::Error::custom(e.to_string()))?;
3825        Ok(Bytes::from(bytes))
3826    }
3827
3828    impl From<EIP1559Transaction> for GenericTransaction {
3829        fn from(value: EIP1559Transaction) -> Self {
3830            Self {
3831                r#type: TxType::EIP1559,
3832                nonce: Some(value.nonce),
3833                to: value.to,
3834                gas: Some(value.gas_limit),
3835                value: value.value,
3836                input: value.data.clone(),
3837                gas_price: U256::from(value.max_fee_per_gas),
3838                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3839                max_fee_per_gas: Some(value.max_fee_per_gas),
3840                max_fee_per_blob_gas: None,
3841                access_list: value
3842                    .access_list
3843                    .iter()
3844                    .map(AccessListEntry::from)
3845                    .collect(),
3846                fee_token: None,
3847                authorization_list: None,
3848                blob_versioned_hashes: vec![],
3849                blobs: vec![],
3850                wrapper_version: None,
3851                chain_id: Some(value.chain_id),
3852                from: Address::default(),
3853            }
3854        }
3855    }
3856
3857    impl TryFrom<GenericTransaction> for EIP1559Transaction {
3858        type Error = GenericTransactionError;
3859
3860        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3861            if value.r#type != TxType::EIP1559 {
3862                return Err(GenericTransactionError::InvalidTxType(value.r#type));
3863            }
3864
3865            Ok(Self {
3866                nonce: value.nonce.unwrap_or_default(),
3867                to: value.to,
3868                gas_limit: value.gas.unwrap_or_default(),
3869                value: value.value,
3870                data: value.input.clone(),
3871                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3872                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3873                    u64::try_from(value.gas_price).map_err(|_| {
3874                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3875                    })?,
3876                ),
3877                access_list: value
3878                    .access_list
3879                    .into_iter()
3880                    .map(|v| (v.address, v.storage_keys))
3881                    .collect::<Vec<_>>(),
3882                chain_id: value.chain_id.unwrap_or_default(),
3883                ..Default::default()
3884            })
3885        }
3886    }
3887
3888    impl From<EIP4844Transaction> for GenericTransaction {
3889        fn from(value: EIP4844Transaction) -> Self {
3890            Self {
3891                r#type: TxType::EIP4844,
3892                nonce: Some(value.nonce),
3893                to: TxKind::Call(value.to),
3894                gas: Some(value.gas),
3895                value: value.value,
3896                input: value.data.clone(),
3897                gas_price: U256::from(value.max_fee_per_gas),
3898                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3899                max_fee_per_gas: Some(value.max_fee_per_gas),
3900                max_fee_per_blob_gas: Some(value.max_fee_per_blob_gas),
3901                access_list: value
3902                    .access_list
3903                    .iter()
3904                    .map(AccessListEntry::from)
3905                    .collect(),
3906                fee_token: None,
3907                authorization_list: None,
3908                blob_versioned_hashes: value.blob_versioned_hashes,
3909                blobs: vec![],
3910                wrapper_version: None,
3911                chain_id: Some(value.chain_id),
3912                from: Address::default(),
3913            }
3914        }
3915    }
3916
3917    #[cfg(feature = "c-kzg")]
3918    impl TryFrom<GenericTransaction> for WrappedEIP4844Transaction {
3919        type Error = GenericTransactionError;
3920
3921        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3922            let blobs = value
3923                .blobs
3924                .iter()
3925                .map(|bytes| {
3926                    let slice = bytes.as_ref();
3927                    let mut blob = [0u8; BYTES_PER_BLOB];
3928                    blob.copy_from_slice(slice);
3929                    blob
3930                })
3931                .collect();
3932
3933            let wrapper_version = value.wrapper_version;
3934            Ok(Self {
3935                tx: value.try_into()?,
3936                wrapper_version,
3937                blobs_bundle: BlobsBundle::create_from_blobs(&blobs, wrapper_version)?,
3938            })
3939        }
3940    }
3941
3942    impl TryFrom<GenericTransaction> for EIP4844Transaction {
3943        type Error = GenericTransactionError;
3944
3945        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
3946            if value.r#type != TxType::EIP4844 {
3947                return Err(GenericTransactionError::InvalidTxType(value.r#type));
3948            }
3949            Ok(Self {
3950                nonce: value.nonce.unwrap_or_default(),
3951                to: match value.to {
3952                    TxKind::Call(to) => to,
3953                    _ => H160::default(),
3954                },
3955                gas: value.gas.unwrap_or_default(),
3956                value: value.value,
3957                data: value.input.clone(),
3958                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
3959                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
3960                    u64::try_from(value.gas_price).map_err(|_| {
3961                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
3962                    })?,
3963                ),
3964                max_fee_per_blob_gas: value.max_fee_per_blob_gas.unwrap_or_default(),
3965                access_list: value
3966                    .access_list
3967                    .into_iter()
3968                    .map(|v| (v.address, v.storage_keys))
3969                    .collect::<Vec<_>>(),
3970                blob_versioned_hashes: value.blob_versioned_hashes,
3971                chain_id: value.chain_id.unwrap_or_default(),
3972                ..Default::default()
3973            })
3974        }
3975    }
3976
3977    impl From<EIP7702Transaction> for GenericTransaction {
3978        fn from(value: EIP7702Transaction) -> Self {
3979            Self {
3980                r#type: TxType::EIP7702,
3981                nonce: Some(value.nonce),
3982                to: TxKind::Call(value.to),
3983                gas: Some(value.gas_limit),
3984                value: value.value,
3985                input: value.data.clone(),
3986                gas_price: U256::from(value.max_fee_per_gas),
3987                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
3988                max_fee_per_gas: Some(value.max_fee_per_gas),
3989                max_fee_per_blob_gas: None,
3990                access_list: value
3991                    .access_list
3992                    .iter()
3993                    .map(AccessListEntry::from)
3994                    .collect(),
3995                fee_token: None,
3996                authorization_list: Some(
3997                    value
3998                        .authorization_list
3999                        .iter()
4000                        .map(AuthorizationTupleEntry::from)
4001                        .collect(),
4002                ),
4003                blob_versioned_hashes: vec![],
4004                blobs: vec![],
4005                wrapper_version: None,
4006                chain_id: Some(value.chain_id),
4007                from: Address::default(),
4008            }
4009        }
4010    }
4011
4012    impl TryFrom<GenericTransaction> for EIP7702Transaction {
4013        type Error = GenericTransactionError;
4014
4015        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
4016            if value.r#type != TxType::EIP7702 {
4017                return Err(GenericTransactionError::InvalidTxType(value.r#type));
4018            }
4019            let TxKind::Call(to) = value.to else {
4020                return Err(GenericTransactionError::MissingField("to".to_owned()));
4021            };
4022            Ok(Self {
4023                chain_id: value.chain_id.unwrap_or_default(),
4024                nonce: value.nonce.unwrap_or_default(),
4025                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
4026                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
4027                    u64::try_from(value.gas_price).map_err(|_| {
4028                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
4029                    })?,
4030                ),
4031                gas_limit: value.gas.unwrap_or_default(),
4032                to,
4033                value: value.value,
4034                data: value.input,
4035                access_list: value
4036                    .access_list
4037                    .into_iter()
4038                    .map(|v| (v.address, v.storage_keys))
4039                    .collect::<Vec<_>>(),
4040                authorization_list: value
4041                    .authorization_list
4042                    .unwrap_or_default()
4043                    .into_iter()
4044                    .map(AuthorizationTuple::from)
4045                    .collect(),
4046                ..Default::default()
4047            })
4048        }
4049    }
4050
4051    impl From<PrivilegedL2Transaction> for GenericTransaction {
4052        fn from(value: PrivilegedL2Transaction) -> Self {
4053            Self {
4054                r#type: TxType::Privileged,
4055                nonce: Some(value.nonce),
4056                to: value.to,
4057                gas: Some(value.gas_limit),
4058                value: value.value,
4059                input: value.data.clone(),
4060                gas_price: U256::from(value.max_fee_per_gas),
4061                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
4062                max_fee_per_gas: Some(value.max_fee_per_gas),
4063                max_fee_per_blob_gas: None,
4064                access_list: value
4065                    .access_list
4066                    .iter()
4067                    .map(AccessListEntry::from)
4068                    .collect(),
4069                fee_token: None,
4070                authorization_list: None,
4071                blob_versioned_hashes: vec![],
4072                blobs: vec![],
4073                wrapper_version: None,
4074                chain_id: Some(value.chain_id),
4075                from: value.from,
4076            }
4077        }
4078    }
4079
4080    impl TryFrom<GenericTransaction> for PrivilegedL2Transaction {
4081        type Error = GenericTransactionError;
4082
4083        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
4084            if value.r#type != TxType::Privileged {
4085                return Err(GenericTransactionError::InvalidTxType(value.r#type));
4086            }
4087            Ok(Self {
4088                nonce: value.nonce.unwrap_or_default(),
4089                to: value.to,
4090                gas_limit: value.gas.unwrap_or_default(),
4091                value: value.value,
4092                data: value.input.clone(),
4093                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
4094                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
4095                    u64::try_from(value.gas_price).map_err(|_| {
4096                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
4097                    })?,
4098                ),
4099                access_list: value
4100                    .access_list
4101                    .into_iter()
4102                    .map(|v| (v.address, v.storage_keys))
4103                    .collect::<Vec<_>>(),
4104                chain_id: value.chain_id.unwrap_or_default(),
4105                from: value.from,
4106                ..Default::default()
4107            })
4108        }
4109    }
4110
4111    impl From<FeeTokenTransaction> for GenericTransaction {
4112        fn from(value: FeeTokenTransaction) -> Self {
4113            Self {
4114                r#type: TxType::FeeToken,
4115                nonce: Some(value.nonce),
4116                to: value.to,
4117                gas: Some(value.gas_limit),
4118                value: value.value,
4119                input: value.data.clone(),
4120                gas_price: U256::from(value.max_fee_per_gas),
4121                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
4122                max_fee_per_gas: Some(value.max_fee_per_gas),
4123                max_fee_per_blob_gas: None,
4124                access_list: value
4125                    .access_list
4126                    .iter()
4127                    .map(AccessListEntry::from)
4128                    .collect(),
4129                fee_token: Some(value.fee_token),
4130                authorization_list: None,
4131                blob_versioned_hashes: vec![],
4132                blobs: vec![],
4133                chain_id: Some(value.chain_id),
4134                from: Address::default(),
4135                wrapper_version: None,
4136            }
4137        }
4138    }
4139
4140    impl TryFrom<GenericTransaction> for FeeTokenTransaction {
4141        type Error = GenericTransactionError;
4142
4143        fn try_from(value: GenericTransaction) -> Result<Self, Self::Error> {
4144            if value.r#type != TxType::FeeToken {
4145                return Err(GenericTransactionError::InvalidTxType(value.r#type));
4146            }
4147
4148            Ok(Self {
4149                nonce: value.nonce.unwrap_or_default(),
4150                to: value.to,
4151                gas_limit: value.gas.unwrap_or_default(),
4152                value: value.value,
4153                data: value.input.clone(),
4154                max_priority_fee_per_gas: value.max_priority_fee_per_gas.unwrap_or_default(),
4155                max_fee_per_gas: value.max_fee_per_gas.unwrap_or(
4156                    u64::try_from(value.gas_price).map_err(|_| {
4157                        GenericTransactionError::InvalidField("gas_price overflows u64".to_owned())
4158                    })?,
4159                ),
4160                access_list: value
4161                    .access_list
4162                    .into_iter()
4163                    .map(|v| (v.address, v.storage_keys))
4164                    .collect::<Vec<_>>(),
4165                fee_token: value
4166                    .fee_token
4167                    .ok_or(GenericTransactionError::MissingField(
4168                        "fee token".to_owned(),
4169                    ))?,
4170                chain_id: value.chain_id.unwrap_or_default(),
4171                ..Default::default()
4172            })
4173        }
4174    }
4175
4176    impl From<LegacyTransaction> for GenericTransaction {
4177        fn from(value: LegacyTransaction) -> Self {
4178            Self {
4179                r#type: TxType::Legacy,
4180                nonce: Some(value.nonce),
4181                to: value.to,
4182                from: Address::default(),
4183                gas: Some(value.gas),
4184                value: value.value,
4185                gas_price: value.gas_price,
4186                max_priority_fee_per_gas: None,
4187                max_fee_per_gas: None,
4188                max_fee_per_blob_gas: None,
4189                access_list: vec![],
4190                fee_token: None,
4191                authorization_list: None,
4192                blob_versioned_hashes: vec![],
4193                blobs: vec![],
4194                wrapper_version: None,
4195                chain_id: None,
4196                input: value.data,
4197            }
4198        }
4199    }
4200
4201    impl From<EIP2930Transaction> for GenericTransaction {
4202        fn from(value: EIP2930Transaction) -> Self {
4203            Self {
4204                r#type: TxType::EIP2930,
4205                nonce: Some(value.nonce),
4206                to: value.to,
4207                from: Address::default(),
4208                gas: Some(value.gas_limit),
4209                value: value.value,
4210                gas_price: value.gas_price,
4211                max_priority_fee_per_gas: None,
4212                max_fee_per_gas: None,
4213                max_fee_per_blob_gas: None,
4214                access_list: value
4215                    .access_list
4216                    .into_iter()
4217                    .map(|(address, storage_keys)| AccessListEntry {
4218                        address,
4219                        storage_keys,
4220                    })
4221                    .collect(),
4222                fee_token: None,
4223                authorization_list: None,
4224                blob_versioned_hashes: vec![],
4225                blobs: vec![],
4226                wrapper_version: None,
4227                chain_id: Some(value.chain_id),
4228                input: value.data,
4229            }
4230        }
4231    }
4232
4233    impl From<Transaction> for GenericTransaction {
4234        fn from(value: Transaction) -> Self {
4235            match value {
4236                Transaction::LegacyTransaction(tx) => tx.into(),
4237                Transaction::EIP2930Transaction(tx) => tx.into(),
4238                Transaction::EIP1559Transaction(tx) => tx.into(),
4239                Transaction::EIP4844Transaction(tx) => tx.into(),
4240                Transaction::EIP7702Transaction(tx) => tx.into(),
4241                Transaction::PrivilegedL2Transaction(tx) => tx.into(),
4242                Transaction::FeeTokenTransaction(tx) => tx.into(),
4243                Transaction::FrameTransaction(tx) => tx.into(),
4244            }
4245        }
4246    }
4247
4248    impl From<FrameTransaction> for GenericTransaction {
4249        fn from(value: FrameTransaction) -> Self {
4250            Self {
4251                r#type: TxType::Frame,
4252                nonce: Some(value.nonce),
4253                to: TxKind::Call(value.sender),
4254                from: value.sender,
4255                gas: Some(value.max_gas()),
4256                value: U256::zero(),
4257                gas_price: value.max_fee_per_gas.into(),
4258                max_priority_fee_per_gas: Some(value.max_priority_fee_per_gas),
4259                max_fee_per_gas: Some(value.max_fee_per_gas),
4260                max_fee_per_blob_gas: if value.blob_versioned_hashes.is_empty() {
4261                    None
4262                } else {
4263                    Some(value.max_fee_per_blob_gas)
4264                },
4265                access_list: vec![],
4266                fee_token: None,
4267                authorization_list: None,
4268                blob_versioned_hashes: value.blob_versioned_hashes,
4269                blobs: vec![],
4270                wrapper_version: None,
4271                chain_id: Some(value.chain_id),
4272                input: Bytes::new(),
4273            }
4274        }
4275    }
4276}
4277
4278mod mempool {
4279    use super::*;
4280    use std::{
4281        cmp::Ordering,
4282        sync::Arc,
4283        time::{SystemTime, UNIX_EPOCH},
4284    };
4285
4286    #[derive(Debug, Clone, PartialEq, Eq)]
4287    pub struct MempoolTransaction {
4288        // Unix timestamp (in microseconds) created once the transaction reached the MemPool
4289        timestamp: u128,
4290        sender: Address,
4291        inner: Arc<Transaction>,
4292    }
4293
4294    impl MempoolTransaction {
4295        pub fn new(tx: Transaction, sender: Address) -> Self {
4296            Self {
4297                timestamp: SystemTime::now()
4298                    .duration_since(UNIX_EPOCH)
4299                    .expect("Invalid system time")
4300                    .as_micros(),
4301                sender,
4302                inner: Arc::new(tx),
4303            }
4304        }
4305        pub fn time(&self) -> u128 {
4306            self.timestamp
4307        }
4308
4309        pub fn sender(&self) -> Address {
4310            self.sender
4311        }
4312
4313        pub fn transaction(&self) -> &Transaction {
4314            &self.inner
4315        }
4316    }
4317
4318    impl RLPEncode for MempoolTransaction {
4319        fn encode(&self, buf: &mut dyn bytes::BufMut) {
4320            Encoder::new(buf)
4321                .encode_field(&self.timestamp)
4322                .encode_field(&*self.inner)
4323                .finish();
4324        }
4325    }
4326
4327    impl RLPDecode for MempoolTransaction {
4328        fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
4329            let decoder = Decoder::new(rlp)?;
4330            let (timestamp, decoder) = decoder.decode_field("timestamp")?;
4331            let (sender, decoder) = decoder.decode_field("sender")?;
4332            let (inner, decoder) = decoder.decode_field("inner")?;
4333            Ok((
4334                Self {
4335                    timestamp,
4336                    sender,
4337                    inner: Arc::new(inner),
4338                },
4339                decoder.finish()?,
4340            ))
4341        }
4342    }
4343
4344    impl std::ops::Deref for MempoolTransaction {
4345        type Target = Transaction;
4346
4347        fn deref(&self) -> &Self::Target {
4348            &self.inner
4349        }
4350    }
4351
4352    // Orders transactions by lowest nonce, if the nonce is equal, orders by highest timestamp
4353    impl Ord for MempoolTransaction {
4354        fn cmp(&self, other: &Self) -> Ordering {
4355            match (self.tx_type(), other.tx_type()) {
4356                (TxType::Privileged, TxType::Privileged) => {
4357                    return self.nonce().cmp(&other.nonce());
4358                }
4359                (TxType::Privileged, _) => return Ordering::Less,
4360                (_, TxType::Privileged) => return Ordering::Greater,
4361                _ => (),
4362            };
4363            match self.nonce().cmp(&other.nonce()) {
4364                Ordering::Equal => other.time().cmp(&self.time()),
4365                ordering => ordering,
4366            }
4367        }
4368    }
4369
4370    impl PartialOrd for MempoolTransaction {
4371        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
4372            Some(self.cmp(other))
4373        }
4374    }
4375}
4376
4377#[cfg(test)]
4378mod tests {
4379
4380    use super::*;
4381    use crate::types::{
4382        AuthorizationTuple, BlockBody, Receipt, compute_receipts_root, compute_transactions_root,
4383    };
4384    use ethereum_types::H160;
4385    use hex_literal::hex;
4386    use serde_impl::{AccessListEntry, GenericTransaction};
4387    use std::str::FromStr;
4388
4389    #[test]
4390    fn is_blob_carrying_covers_frame_txs_with_blob_hashes() {
4391        // An EIP-8141 frame tx carries `max_fee_per_blob_gas` +
4392        // `blob_versioned_hashes` of its own, so blob-ness cannot be decided
4393        // by matching on `EIP4844Transaction` alone: a blob-bearing frame tx
4394        // would otherwise be treated as a plain tx by the mempool's
4395        // replacement rules and skip the blob-fee comparison entirely.
4396        let plain_frame = Transaction::FrameTransaction(FrameTransaction::default());
4397        assert!(!plain_frame.is_blob_carrying());
4398
4399        let blob_frame = Transaction::FrameTransaction(FrameTransaction {
4400            max_fee_per_blob_gas: U256::from(7u64),
4401            blob_versioned_hashes: vec![H256::from_low_u64_be(1)],
4402            ..Default::default()
4403        });
4404        assert!(blob_frame.is_blob_carrying());
4405
4406        let blob_tx = Transaction::EIP4844Transaction(EIP4844Transaction {
4407            blob_versioned_hashes: vec![H256::from_low_u64_be(1)],
4408            ..Default::default()
4409        });
4410        assert!(blob_tx.is_blob_carrying());
4411
4412        let plain = Transaction::EIP1559Transaction(EIP1559Transaction::default());
4413        assert!(!plain.is_blob_carrying());
4414    }
4415
4416    #[test]
4417    fn nonce_over_u64_max_maps_to_nonce_is_max() {
4418        // RLP list [ 0x010000000000000000 ] — nonce = 2**64 (9 significant bytes),
4419        // one past u64::MAX. EEST expects TransactionException.NONCE_IS_MAX here.
4420        let rlp = hex!("ca89010000000000000000");
4421        let decoder = Decoder::new(&rlp).unwrap();
4422        let err = decode_nonce_field(decoder).unwrap_err();
4423        assert!(
4424            err.to_string().contains("Nonce is max"),
4425            "unexpected error: {err}"
4426        );
4427    }
4428
4429    #[test]
4430    fn nonce_at_u64_max_decodes() {
4431        // RLP list [ 0xffffffffffffffff ] — nonce = u64::MAX (8 bytes), still valid.
4432        let rlp = hex!("c988ffffffffffffffff");
4433        let decoder = Decoder::new(&rlp).unwrap();
4434        let (nonce, _) = decode_nonce_field(decoder).unwrap();
4435        assert_eq!(nonce, u64::MAX);
4436    }
4437
4438    #[test]
4439    fn legacy_chain_id_handles_out_of_range_v_without_underflow() {
4440        // A legacy transaction decodes `v` as an arbitrary U256, so a malformed tx can
4441        // carry any value. `derive_legacy_chain_id` must return None for the pre-EIP-155
4442        // values (27/28) and any v < 35 rather than underflowing `v - 35`, which panics
4443        // in debug and wraps to a bogus chain id in release. This is reachable on the
4444        // block-import path (every tx's chain id is now checked pre-execution).
4445        let legacy_chain_id = |v: u64| {
4446            Transaction::LegacyTransaction(LegacyTransaction {
4447                v: U256::from(v),
4448                ..Default::default()
4449            })
4450            .chain_id()
4451        };
4452        for v in [0u64, 1, 26, 27, 28, 34] {
4453            assert_eq!(legacy_chain_id(v), None, "v={v} must yield no chain id");
4454        }
4455        // EIP-155 values (v = chain_id * 2 + 35/36) still derive correctly.
4456        assert_eq!(legacy_chain_id(35), Some(0));
4457        assert_eq!(legacy_chain_id(36), Some(0));
4458        assert_eq!(legacy_chain_id(37), Some(1));
4459    }
4460
4461    #[test]
4462    fn test_compute_transactions_root() {
4463        let mut body = BlockBody::empty();
4464        let tx = LegacyTransaction {
4465            nonce: 0,
4466            gas_price: U256::from(0x0a),
4467            gas: 0x05f5e100,
4468            to: TxKind::Call(hex!("1000000000000000000000000000000000000000").into()),
4469            value: 0.into(),
4470            data: Default::default(),
4471            v: U256::from(0x1b),
4472            r: U256::from_big_endian(&hex!(
4473                "7e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37"
4474            )),
4475            s: U256::from_big_endian(&hex!(
4476                "5f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509b"
4477            )),
4478            ..Default::default()
4479        };
4480        body.transactions.push(Transaction::LegacyTransaction(tx));
4481        let expected_root =
4482            hex!("8151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcb");
4483        let result = compute_transactions_root(&body.transactions, &ethrex_crypto::NativeCrypto);
4484
4485        assert_eq!(result, expected_root.into());
4486    }
4487    #[test]
4488    fn test_compute_hash() {
4489        // taken from Hive
4490        let tx_eip2930 = EIP2930Transaction {
4491            chain_id: 3503995874084926u64,
4492            nonce: 7,
4493            gas_price: U256::from(0x2dbf1f9a_u64),
4494            gas_limit: 0x186A0,
4495            to: TxKind::Call(hex!("7dcd17433742f4c0ca53122ab541d0ba67fc27df").into()),
4496            value: 2.into(),
4497            data: Bytes::from(&b"\xdbS\x06$\x8e\x03\x13\xe7emit"[..]),
4498            access_list: vec![(
4499                hex!("7dcd17433742f4c0ca53122ab541d0ba67fc27df").into(),
4500                vec![
4501                    hex!("0000000000000000000000000000000000000000000000000000000000000000").into(),
4502                    hex!("a3d07a7d68fbd49ec2f8e6befdd86c885f86c272819f6f345f365dec35ae6707").into(),
4503                ],
4504            )],
4505            signature_y_parity: false,
4506            signature_r: U256::from_dec_str(
4507                "75813812796588349127366022588733264074091236448495248199152066031778895768879",
4508            )
4509            .unwrap(),
4510            signature_s: U256::from_dec_str(
4511                "25476208226281085290728123165613764315157904411823916642262684106502155457829",
4512            )
4513            .unwrap(),
4514            ..Default::default()
4515        };
4516        let tx = Transaction::EIP2930Transaction(tx_eip2930);
4517
4518        let expected_hash =
4519            hex!("a0762610d794acddd2dca15fb7c437ada3611c886f3bea675d53d8da8a6c41b2");
4520        let hash = tx.compute_hash(&NativeCrypto);
4521        assert_eq!(hash, expected_hash.into());
4522    }
4523
4524    #[test]
4525    fn test_compute_receipts_root() {
4526        // example taken from
4527        // https://github.com/ethereum/go-ethereum/blob/f8aa62353666a6368fb3f1a378bd0a82d1542052/cmd/evm/testdata/1/exp.json#L18
4528        let tx_type = TxType::Legacy;
4529        let succeeded = true;
4530        let cumulative_gas_used = 0x5208;
4531        let logs = vec![];
4532        let receipt = Receipt::new(tx_type, succeeded, cumulative_gas_used, logs);
4533
4534        let result = compute_receipts_root(&[receipt], &ethrex_crypto::NativeCrypto);
4535        let expected_root =
4536            hex!("056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2");
4537        assert_eq!(result, expected_root.into());
4538    }
4539
4540    #[test]
4541    fn legacy_tx_rlp_decode() {
4542        let encoded_tx = "f86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4";
4543        let encoded_tx_bytes = hex::decode(encoded_tx).unwrap();
4544        let tx = LegacyTransaction::decode(&encoded_tx_bytes).unwrap();
4545        let expected_tx = LegacyTransaction {
4546            nonce: 0,
4547            gas_price: U256::from(1001000000u64),
4548            gas: 63000,
4549            to: TxKind::Call(Address::from_slice(
4550                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4551            )),
4552            value: 3000000000000000_u64.into(),
4553            data: Bytes::new(),
4554            r: U256::from_str_radix(
4555                "151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d",
4556                16,
4557            )
4558            .unwrap(),
4559            s: U256::from_str_radix(
4560                "64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4",
4561                16,
4562            )
4563            .unwrap(),
4564            v: 6303851.into(),
4565            ..Default::default()
4566        };
4567        assert_eq!(tx, expected_tx);
4568    }
4569
4570    #[test]
4571    fn eip1559_tx_rlp_decode() {
4572        let encoded_tx = "f86c8330182480114e82f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee53800080c080a0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4";
4573        let encoded_tx_bytes = hex::decode(encoded_tx).unwrap();
4574        let tx = EIP1559Transaction::decode(&encoded_tx_bytes).unwrap();
4575        let expected_tx = EIP1559Transaction {
4576            nonce: 0,
4577            max_fee_per_gas: 78,
4578            max_priority_fee_per_gas: 17,
4579            to: TxKind::Call(Address::from_slice(
4580                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4581            )),
4582            value: 3000000000000000_u64.into(),
4583            data: Bytes::new(),
4584            signature_r: U256::from_str_radix(
4585                "151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65d",
4586                16,
4587            )
4588            .unwrap(),
4589            signature_s: U256::from_str_radix(
4590                "64c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4",
4591                16,
4592            )
4593            .unwrap(),
4594            signature_y_parity: false,
4595            chain_id: 3151908,
4596            gas_limit: 63000,
4597            access_list: vec![],
4598            ..Default::default()
4599        };
4600        assert_eq!(tx, expected_tx);
4601    }
4602
4603    #[test]
4604    fn deserialize_tx_kind() {
4605        let tx_kind_create = r#""""#;
4606        let tx_kind_call = r#""0x6177843db3138ae69679A54b95cf345ED759450d""#;
4607        let deserialized_tx_kind_create = TxKind::Create;
4608        let deserialized_tx_kind_call = TxKind::Call(Address::from_slice(
4609            &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4610        ));
4611        assert_eq!(
4612            deserialized_tx_kind_create,
4613            serde_json::from_str(tx_kind_create).unwrap()
4614        );
4615        assert_eq!(
4616            deserialized_tx_kind_call,
4617            serde_json::from_str(tx_kind_call).unwrap()
4618        )
4619    }
4620
4621    #[test]
4622    fn deserialize_tx_type() {
4623        let tx_type_eip2930 = r#""0x01""#;
4624        let tx_type_eip1559 = r#""0x02""#;
4625        let deserialized_tx_type_eip2930 = TxType::EIP2930;
4626        let deserialized_tx_type_eip1559 = TxType::EIP1559;
4627        assert_eq!(
4628            deserialized_tx_type_eip2930,
4629            serde_json::from_str(tx_type_eip2930).unwrap()
4630        );
4631        assert_eq!(
4632            deserialized_tx_type_eip1559,
4633            serde_json::from_str(tx_type_eip1559).unwrap()
4634        )
4635    }
4636
4637    #[test]
4638    fn deserialize_generic_transaction() {
4639        let generic_transaction = r#"{
4640            "type":"0x01",
4641            "nonce":"0x02",
4642            "to":"",
4643            "from":"0x6177843db3138ae69679A54b95cf345ED759450d",
4644            "gas":"0x5208",
4645            "value":"0x01",
4646            "input":"0x010203040506",
4647            "gasPrice":"0x07",
4648            "accessList": [
4649                {
4650                    "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4651                    "storageKeys": [
4652                        "0x000000000000000000000000000000000000000000000000000000000000000c",
4653                        "0x000000000000000000000000000000000000000000000000000000000000200b"
4654                    ]
4655                }
4656            ]
4657        }"#;
4658        let deserialized_generic_transaction = GenericTransaction {
4659            r#type: TxType::EIP2930,
4660            nonce: Some(2),
4661            to: TxKind::Create,
4662            from: Address::from_slice(
4663                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4664            ),
4665            gas: Some(0x5208),
4666            value: U256::from(1),
4667            input: Bytes::from(hex::decode("010203040506").unwrap()),
4668            gas_price: U256::from(7),
4669            max_priority_fee_per_gas: Default::default(),
4670            max_fee_per_gas: Default::default(),
4671            max_fee_per_blob_gas: Default::default(),
4672            access_list: vec![AccessListEntry {
4673                address: Address::from_slice(
4674                    &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4675                ),
4676                storage_keys: vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4677            }],
4678            fee_token: None,
4679            blob_versioned_hashes: Default::default(),
4680            blobs: Default::default(),
4681            wrapper_version: None,
4682            chain_id: Default::default(),
4683            authorization_list: None,
4684        };
4685        assert_eq!(
4686            deserialized_generic_transaction,
4687            serde_json::from_str(generic_transaction).unwrap()
4688        )
4689    }
4690
4691    #[test]
4692    fn deserialize_generic_transaction_with_data_and_input_fields() {
4693        let generic_transaction = r#"{
4694            "type":"0x01",
4695            "nonce":"0x02",
4696            "to":"",
4697            "from":"0x6177843db3138ae69679A54b95cf345ED759450d",
4698            "gas":"0x5208",
4699            "value":"0x01",
4700            "input":"0x010203040506",
4701            "data":"0x010203040506",
4702            "gasPrice":"0x07",
4703            "accessList": [
4704                {
4705                    "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4706                    "storageKeys": [
4707                        "0x000000000000000000000000000000000000000000000000000000000000000c",
4708                        "0x000000000000000000000000000000000000000000000000000000000000200b"
4709                    ]
4710                }
4711            ]
4712        }"#;
4713        let deserialized_generic_transaction = GenericTransaction {
4714            r#type: TxType::EIP2930,
4715            nonce: Some(2),
4716            to: TxKind::Create,
4717            from: Address::from_slice(
4718                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4719            ),
4720            gas: Some(0x5208),
4721            value: U256::from(1),
4722            input: Bytes::from(hex::decode("010203040506").unwrap()),
4723            gas_price: U256::from(7),
4724            max_priority_fee_per_gas: Default::default(),
4725            max_fee_per_gas: Default::default(),
4726            max_fee_per_blob_gas: Default::default(),
4727            access_list: vec![AccessListEntry {
4728                address: Address::from_slice(
4729                    &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4730                ),
4731                storage_keys: vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4732            }],
4733            fee_token: None,
4734            blob_versioned_hashes: Default::default(),
4735            blobs: Default::default(),
4736            wrapper_version: None,
4737            chain_id: Default::default(),
4738            authorization_list: None,
4739        };
4740        assert_eq!(
4741            deserialized_generic_transaction,
4742            serde_json::from_str(generic_transaction).unwrap()
4743        )
4744    }
4745
4746    #[test]
4747    fn deserialize_eip4844_transaction() {
4748        let eip4844_transaction = r#"{
4749            "chainId":"0x01",
4750            "nonce":"0x02",
4751            "maxPriorityFeePerGas":"0x01",
4752            "maxFeePerGas":"0x01",
4753            "gas":"0x5208",
4754            "to":"0x6177843db3138ae69679A54b95cf345ED759450d",
4755            "value":"0x01",
4756            "input":"0x3033",
4757            "accessList": [
4758                {
4759                    "address": "0x000f3df6d732807ef1319fb7b8bb8522d0beac02",
4760                    "storageKeys": [
4761                        "0x000000000000000000000000000000000000000000000000000000000000000c",
4762                        "0x000000000000000000000000000000000000000000000000000000000000200b"
4763                    ]
4764                }
4765            ],
4766            "maxFeePerBlobGas":"0x03",
4767            "blobVersionedHashes": [
4768                    "0x0000000000000000000000000000000000000000000000000000000000000001",
4769                    "0x0000000000000000000000000000000000000000000000000000000000000002"
4770            ],
4771            "yParity":"0x0",
4772            "r": "0x01",
4773            "s": "0x02"
4774        }"#;
4775        let deserialized_eip4844_transaction = EIP4844Transaction {
4776            chain_id: 0x01,
4777            nonce: 0x02,
4778            to: Address::from_slice(
4779                &hex::decode("6177843db3138ae69679A54b95cf345ED759450d").unwrap(),
4780            ),
4781            max_priority_fee_per_gas: 1,
4782            max_fee_per_gas: 1,
4783            max_fee_per_blob_gas: U256::from(0x03),
4784            gas: 0x5208,
4785            value: U256::from(0x01),
4786            // 03 in hex is 0x3033, that's why the 'input' has that number.
4787            data: Bytes::from_static(b"03"),
4788            access_list: vec![(
4789                Address::from_slice(
4790                    &hex::decode("000f3df6d732807ef1319fb7b8bb8522d0beac02").unwrap(),
4791                ),
4792                vec![H256::from_low_u64_be(12), H256::from_low_u64_be(8203)],
4793            )],
4794            blob_versioned_hashes: vec![H256::from_low_u64_be(1), H256::from_low_u64_be(2)],
4795            signature_y_parity: false,
4796            signature_r: U256::from(0x01),
4797            signature_s: U256::from(0x02),
4798            ..Default::default()
4799        };
4800
4801        assert_eq!(
4802            deserialized_eip4844_transaction,
4803            serde_json::from_str(eip4844_transaction).unwrap()
4804        )
4805    }
4806
4807    #[test]
4808    fn serialize_deserialize_transaction() {
4809        let eip1559 = EIP1559Transaction {
4810            chain_id: 65536999,
4811            nonce: 1,
4812            max_priority_fee_per_gas: 1000,
4813            max_fee_per_gas: 2000,
4814            gas_limit: 21000,
4815            to: TxKind::Call(H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B0").unwrap()),
4816            value: U256::from(100000),
4817            data: Bytes::from_static(b"03"),
4818            access_list: vec![(
4819                H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B3").unwrap(),
4820                vec![H256::zero()],
4821            )],
4822            signature_y_parity: true,
4823            signature_r: U256::one(),
4824            signature_s: U256::zero(),
4825            ..Default::default()
4826        };
4827        let tx_to_serialize = Transaction::EIP1559Transaction(eip1559.clone());
4828        let serialized = serde_json::to_string(&tx_to_serialize).expect("Failed to serialize");
4829
4830        let deserialized_tx: Transaction =
4831            serde_json::from_str(&serialized).expect("Failed to deserialize");
4832
4833        assert!(deserialized_tx.tx_type() == TxType::EIP1559);
4834
4835        if let Transaction::EIP1559Transaction(tx) = deserialized_tx {
4836            assert_eq!(tx, eip1559);
4837        }
4838    }
4839
4840    #[test]
4841    fn serialize_deserialize_eip7702transaction() {
4842        let eip7702 = EIP7702Transaction {
4843            chain_id: 65536999,
4844            nonce: 1,
4845            max_priority_fee_per_gas: 1000,
4846            max_fee_per_gas: 2000,
4847            gas_limit: 21000,
4848            to: Address::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B0").unwrap(),
4849            value: U256::from(100000),
4850            data: Bytes::from_static(b"03"),
4851            access_list: vec![],
4852            signature_y_parity: true,
4853            signature_r: U256::one(),
4854            signature_s: U256::zero(),
4855            authorization_list: vec![AuthorizationTuple {
4856                chain_id: U256::from(65536999),
4857                address: H160::from_str("0x000a52D537c4150ec274dcE3962a0d179B7E71B1").unwrap(),
4858                nonce: 2,
4859                y_parity: U256::one(),
4860                r_signature: U256::from(22),
4861                s_signature: U256::from(37),
4862            }],
4863            ..Default::default()
4864        };
4865        let tx_to_serialize = Transaction::EIP7702Transaction(eip7702.clone());
4866        let serialized = serde_json::to_string(&tx_to_serialize).expect("Failed to serialize");
4867
4868        let deserialized_tx: Transaction =
4869            serde_json::from_str(&serialized).expect("Failed to deserialize");
4870
4871        assert!(deserialized_tx.tx_type() == TxType::EIP7702);
4872
4873        if let Transaction::EIP7702Transaction(tx) = deserialized_tx {
4874            assert_eq!(tx, eip7702);
4875        }
4876    }
4877
4878    #[test]
4879    fn serialize_deserialize_privileged_l2_transaction() -> Result<(), RLPDecodeError> {
4880        let privileged_l2 = PrivilegedL2Transaction {
4881            chain_id: 65536999,
4882            nonce: 0,
4883            max_priority_fee_per_gas: 875000000,
4884            max_fee_per_gas: 875000000,
4885            gas_limit: 42000u64,
4886            to: TxKind::Call(
4887                Address::from_str("0x8943545177806ed17b9f23f0a21ee5948ecaa776").unwrap(),
4888            ),
4889            value: U256::from(500000000000000000000000000u128),
4890            data: Bytes::new(),
4891            access_list: vec![],
4892            from: Address::from_str("0x8943545177806ed17b9f23f0a21ee5948ecaa776").unwrap(),
4893            ..Default::default()
4894        };
4895
4896        let encoded = PrivilegedL2Transaction::encode_to_vec(&privileged_l2);
4897        println!("encoded length: {}", encoded.len());
4898        assert_eq!(encoded.len(), privileged_l2.length());
4899
4900        let deserialized_tx = PrivilegedL2Transaction::decode(&encoded)?;
4901
4902        assert_eq!(deserialized_tx, privileged_l2);
4903
4904        Ok(())
4905    }
4906
4907    #[test]
4908    fn test_legacy_transaction_into_generic() {
4909        let legacy_tx = LegacyTransaction {
4910            nonce: 1,
4911            gas_price: U256::from(20_000_000_000u64),
4912            gas: 21000,
4913            to: TxKind::Call(
4914                Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4915            ),
4916            value: U256::from(1_000_000_000_000_000_000u64),
4917            data: Bytes::default(),
4918            v: U256::from(27),
4919            r: U256::from(1),
4920            s: U256::from(1),
4921            ..Default::default()
4922        };
4923
4924        let generic_tx: GenericTransaction = legacy_tx.into();
4925        assert_eq!(generic_tx.r#type, TxType::Legacy);
4926        assert_eq!(generic_tx.nonce, Some(1));
4927        assert_eq!(generic_tx.gas_price, U256::from(20_000_000_000u64));
4928        assert_eq!(generic_tx.gas, Some(21000));
4929        assert_eq!(generic_tx.max_priority_fee_per_gas, None);
4930        assert_eq!(generic_tx.max_fee_per_gas, None);
4931        assert_eq!(generic_tx.access_list.len(), 0);
4932        assert_eq!(generic_tx.chain_id, None);
4933    }
4934
4935    #[test]
4936    fn test_eip2930_transaction_into_generic() {
4937        let access_list = vec![(
4938            Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4939            vec![
4940                H256::from_str(
4941                    "0x1234567890123456789012345678901234567890123456789012345678901234",
4942                )
4943                .unwrap(),
4944            ],
4945        )];
4946
4947        let eip2930_tx = EIP2930Transaction {
4948            chain_id: 1,
4949            nonce: 1,
4950            gas_price: U256::from(20_000_000_000u64),
4951            gas_limit: 21000,
4952            to: TxKind::Call(
4953                Address::from_str("0x742d35Cc6634C0532925a3b844Bc454e4438f44e").unwrap(),
4954            ),
4955            value: U256::from(1_000_000_000_000_000_000u64),
4956            data: Bytes::default(),
4957            access_list: access_list.clone(),
4958            signature_y_parity: false,
4959            signature_r: U256::from(1),
4960            signature_s: U256::from(1),
4961            ..Default::default()
4962        };
4963
4964        let generic_tx: GenericTransaction = eip2930_tx.into();
4965        assert_eq!(generic_tx.r#type, TxType::EIP2930);
4966        assert_eq!(generic_tx.nonce, Some(1));
4967        assert_eq!(generic_tx.gas_price, U256::from(20_000_000_000u64));
4968        assert_eq!(generic_tx.gas, Some(21000));
4969        assert_eq!(generic_tx.max_priority_fee_per_gas, None);
4970        assert_eq!(generic_tx.max_fee_per_gas, None);
4971        assert_eq!(generic_tx.chain_id, Some(1));
4972        assert_eq!(generic_tx.access_list.len(), 1);
4973        assert_eq!(generic_tx.access_list[0].address, access_list[0].0);
4974        assert_eq!(generic_tx.access_list[0].storage_keys, access_list[0].1);
4975    }
4976
4977    #[test]
4978    fn recover_address_rejects_high_s_signatures() {
4979        use ethrex_crypto::NativeCrypto;
4980        use k256::ecdsa::SigningKey;
4981
4982        let crypto = NativeCrypto;
4983
4984        // 1. Setup: Create a signer and a message
4985        // A random private key for testing
4986        let private_key = hex!("4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318");
4987        let signing_key = SigningKey::from_bytes(&private_key.into()).expect("Valid private key");
4988
4989        // The message we want to sign
4990        let msg = b"Test message for high-s signature rejection";
4991        // Calculate the Keccak256 hash of the message (the payload)
4992        let payload = keccak(msg).to_fixed_bytes();
4993
4994        // 2. Generate a valid low-s signature
4995        // k256's sign_prehash_recoverable produces canonical low-s signatures by default.
4996        let (signature, recovery_id) = signing_key
4997            .sign_prehash_recoverable(&payload)
4998            .expect("Signing failed");
4999
5000        // 3. Construct the signature bytes (r||s||v, 65 bytes)
5001        let mut sig_bytes = [0u8; 65];
5002        sig_bytes[..64].copy_from_slice(&signature.to_bytes());
5003        sig_bytes[64] = recovery_id.to_byte();
5004
5005        // 4. Verify that the valid low-s signature recovers the correct address
5006        // Calculate the expected address from the public key
5007        let uncompressed_pub = signing_key.verifying_key().to_encoded_point(false);
5008        let pub_hash = ethrex_crypto::keccak::keccak_hash(&uncompressed_pub.as_bytes()[1..]);
5009        let expected_address = Address::from_slice(&pub_hash[12..]);
5010
5011        let recovered = crypto
5012            .recover_signer(&sig_bytes, &payload)
5013            .expect("Valid low-s signature should recover successfully");
5014        assert_eq!(recovered, expected_address, "Recovered address mismatch");
5015
5016        // 5. Create a high-s signature: s' = N - s
5017        // The curve order N for secp256k1
5018        let n = U256::from_big_endian(&hex!(
5019            "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"
5020        ));
5021        let s = U256::from_big_endian(&sig_bytes[32..64]);
5022
5023        // Ensure the generated signature was indeed low-s (standard requirement)
5024        let half_n = n / 2;
5025        assert!(
5026            s <= half_n,
5027            "Generated signature was not low-s, cannot test high-s rejection"
5028        );
5029
5030        // Calculate high-s
5031        let s_high = n - s;
5032
5033        let mut sig_high_bytes = sig_bytes;
5034        // Replace s with s_high
5035        sig_high_bytes[32..64].copy_from_slice(&s_high.to_big_endian());
5036        // When flipping s to -s mod N, we must also flip the recovery ID (v) to maintain validity of the point R
5037        sig_high_bytes[64] ^= 1;
5038
5039        // 6. Verify that the high-s signature is rejected
5040        // EIP-2 requires rejecting s > N/2 to prevent malleability
5041        assert!(
5042            crypto.recover_signer(&sig_high_bytes, &payload).is_err(),
5043            "High-s signature should be rejected (EIP-2 compliance)"
5044        );
5045    }
5046
5047    #[test]
5048    fn encode_decode_low_size_tx() {
5049        let tx = Transaction::EIP2930Transaction(EIP2930Transaction::default());
5050        // Encode a separate copy so the original's cached_canonical stays uninit,
5051        // avoiding a false PartialEq mismatch with the decoded (uncached) tx.
5052        let tx_to_encode = tx.clone();
5053        let encoded = tx_to_encode.encode_to_vec();
5054        let decoded_tx = Transaction::decode(&encoded).unwrap();
5055        assert_eq!(tx, decoded_tx);
5056    }
5057
5058    #[test]
5059    fn test_eip1559_simple_transfer_size() {
5060        let tx = Transaction::EIP1559Transaction(EIP1559Transaction::default());
5061        assert_eq!(tx.encode_to_vec().len(), EIP1559_DEFAULT_SERIALIZED_LENGTH);
5062    }
5063
5064    // ── Frame Transaction (EIP-8141) tests ──
5065
5066    fn make_test_frame_tx() -> FrameTransaction {
5067        FrameTransaction {
5068            chain_id: 1,
5069            nonce: 42,
5070            sender: Address::from_low_u64_be(0xABCD),
5071            frames: vec![
5072                Frame {
5073                    mode: FrameMode::Verify as u8,
5074                    flags: 0x03, // APPROVE_PAYMENT_AND_EXECUTION
5075                    target: Some(Address::from_low_u64_be(0xABCD)),
5076                    gas_limit: 100_000,
5077                    value: U256::zero(),
5078                    data: Bytes::from_static(b"verify_data"),
5079                },
5080                Frame {
5081                    mode: FrameMode::Sender as u8,
5082                    flags: 0x00,
5083                    target: Some(Address::from_low_u64_be(0x1234)),
5084                    gas_limit: 200_000,
5085                    value: U256::zero(),
5086                    data: Bytes::from_static(b"call_data"),
5087                },
5088            ],
5089            signatures: vec![FrameSignature {
5090                scheme: FRAME_SIG_SCHEME_SECP256K1,
5091                signer: Some(Address::from_low_u64_be(0xABCD)),
5092                msg: Bytes::new(),
5093                signature: Bytes::from(vec![0u8; 65]),
5094            }],
5095            max_priority_fee_per_gas: 1_000_000_000,
5096            max_fee_per_gas: 30_000_000_000,
5097            max_fee_per_blob_gas: U256::zero(),
5098            blob_versioned_hashes: vec![],
5099            inner_hash: OnceCell::new(),
5100            cached_canonical: OnceCell::new(),
5101        }
5102    }
5103
5104    #[test]
5105    fn atomic_batch_flag_valid_on_default_and_sender_frames() {
5106        // Per EIP-8141 the atomic batch flag is valid on DEFAULT and SENDER
5107        // frames, and a batch never contains a VERIFY frame.
5108        let mut tx = make_test_frame_tx();
5109        tx.frames = vec![
5110            Frame {
5111                mode: FrameMode::Default as u8,
5112                flags: 0x04, // atomic batch
5113                target: Some(Address::from_low_u64_be(0xB0B)),
5114                gas_limit: 21_000,
5115                value: U256::zero(),
5116                data: Bytes::new(),
5117            },
5118            Frame {
5119                mode: FrameMode::Sender as u8,
5120                flags: 0x04, // atomic batch
5121                target: Some(Address::from_low_u64_be(0xB0B)),
5122                gas_limit: 21_000,
5123                value: U256::zero(),
5124                data: Bytes::new(),
5125            },
5126            Frame {
5127                mode: FrameMode::Sender as u8,
5128                flags: 0x00, // terminator: no flag
5129                target: Some(Address::from_low_u64_be(0xCAFE)),
5130                gas_limit: 21_000,
5131                value: U256::zero(),
5132                data: Bytes::new(),
5133            },
5134        ];
5135        assert!(tx.validate_static_constraints().is_ok());
5136    }
5137
5138    #[test]
5139    fn atomic_batch_flag_on_last_frame_still_invalid() {
5140        let mut tx = make_test_frame_tx();
5141        tx.frames = vec![Frame {
5142            mode: FrameMode::Sender as u8,
5143            flags: 0x04,
5144            target: Some(Address::from_low_u64_be(0xCAFE)),
5145            gas_limit: 21_000,
5146            value: U256::zero(),
5147            data: Bytes::new(),
5148        }];
5149        let err = tx.validate_static_constraints().unwrap_err();
5150        assert!(err.contains("atomic batch flag on last frame"), "{err}");
5151    }
5152
5153    #[test]
5154    fn test_frame_rlp_roundtrip() {
5155        let frame = Frame {
5156            mode: FrameMode::Verify as u8,
5157            flags: 0x03,
5158            target: Some(Address::from_low_u64_be(0x1234)),
5159            gas_limit: 50_000,
5160            value: U256::zero(),
5161            data: Bytes::from_static(b"hello"),
5162        };
5163        let encoded = frame.encode_to_vec();
5164        let decoded = Frame::decode(&encoded).unwrap();
5165        assert_eq!(frame, decoded);
5166    }
5167
5168    #[test]
5169    fn test_frame_null_target_rlp_roundtrip() {
5170        let frame = Frame {
5171            mode: FrameMode::Default as u8,
5172            flags: 0x00,
5173            target: None,
5174            gas_limit: 10_000,
5175            value: U256::zero(),
5176            data: Bytes::from_static(b"deploy"),
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_mode_with_flags_rlp_roundtrip() {
5185        // Test basic modes
5186        for mode_val in [0u8, 1, 2] {
5187            let frame = Frame {
5188                mode: mode_val,
5189                flags: if mode_val == 1 { 0x03 } else { 0x00 },
5190                target: Some(Address::from_low_u64_be(0x1234)),
5191                gas_limit: 50_000,
5192                value: U256::zero(),
5193                data: Bytes::new(),
5194            };
5195            let encoded = frame.encode_to_vec();
5196            let decoded = Frame::decode(&encoded).unwrap();
5197            assert_eq!(frame, decoded);
5198        }
5199        // Test mode with scope restriction (bits 0-1 of flags) and atomic batch (bit 2 of flags)
5200        let frame = Frame {
5201            mode: 2,            // SENDER
5202            flags: 0x01 | 0x04, // scope=1 + atomic_batch
5203            target: Some(Address::from_low_u64_be(0x1234)),
5204            gas_limit: 50_000,
5205            value: U256::zero(),
5206            data: Bytes::new(),
5207        };
5208        assert_eq!(frame.execution_mode(), FrameMode::Sender);
5209        assert_eq!(frame.scope_restriction(), 1);
5210        assert!(frame.is_atomic_batch());
5211        let encoded = frame.encode_to_vec();
5212        let decoded = Frame::decode(&encoded).unwrap();
5213        assert_eq!(frame, decoded);
5214    }
5215
5216    #[test]
5217    fn frame_rlp_roundtrip_preserves_value() {
5218        let frame = Frame {
5219            mode: FrameMode::Sender as u8,
5220            flags: 0x00,
5221            target: Some(Address::from_low_u64_be(0xCAFE)),
5222            gas_limit: 100_000,
5223            value: U256::from(1_000_000_000_000_000u64), // 0.001 ETH
5224            data: Bytes::from_static(b"hello"),
5225        };
5226        let encoded = frame.encode_to_vec();
5227        let decoded = Frame::decode(&encoded).unwrap();
5228        assert_eq!(frame, decoded);
5229        assert_eq!(decoded.value, U256::from(1_000_000_000_000_000u64));
5230    }
5231
5232    #[test]
5233    fn test_frame_transaction_rlp_roundtrip() {
5234        let tx = make_test_frame_tx();
5235        let encoded = tx.encode_to_vec();
5236        let decoded = FrameTransaction::decode(&encoded).unwrap();
5237        assert_eq!(tx, decoded);
5238    }
5239
5240    #[test]
5241    fn test_frame_transaction_canonical_roundtrip() {
5242        let tx = Transaction::FrameTransaction(make_test_frame_tx());
5243        let encoded = tx.encode_canonical_to_vec();
5244        let decoded = Transaction::decode_canonical(&encoded).unwrap();
5245        // Compare hashes since OnceCell cached state differs after encode
5246        assert_eq!(
5247            tx.hash(&ethrex_crypto::NativeCrypto),
5248            decoded.hash(&ethrex_crypto::NativeCrypto)
5249        );
5250        // Also verify core fields match
5251        assert_eq!(tx.tx_type(), decoded.tx_type());
5252        assert_eq!(tx.nonce(), decoded.nonce());
5253        assert_eq!(tx.chain_id(), decoded.chain_id());
5254    }
5255
5256    #[test]
5257    fn test_frame_transaction_sig_hash_covers_all_frame_data() {
5258        // Updated for frame data is NO LONGER elided.
5259        let tx = make_test_frame_tx();
5260        let hash1 = tx.compute_sig_hash();
5261
5262        // Changing VERIFY frame data now DOES change the sig_hash.
5263        let mut tx2 = tx.clone();
5264        tx2.frames[0].data = Bytes::from_static(b"completely_different_verify_data");
5265        let hash2 = tx2.compute_sig_hash();
5266        assert_ne!(hash1, hash2);
5267
5268        // Changing SENDER frame data also produces a different hash.
5269        let mut tx3 = tx;
5270        tx3.frames[1].data = Bytes::from_static(b"different_call_data");
5271        let hash3 = tx3.compute_sig_hash();
5272        assert_ne!(hash1, hash3);
5273    }
5274
5275    #[test]
5276    fn test_frame_transaction_accessor_methods() {
5277        let frame_tx = make_test_frame_tx();
5278        let tx = Transaction::FrameTransaction(frame_tx);
5279
5280        assert_eq!(tx.tx_type(), TxType::Frame);
5281        assert_eq!(tx.nonce(), 42);
5282        assert_eq!(tx.chain_id(), Some(1));
5283        assert_eq!(tx.to(), TxKind::Call(Address::from_low_u64_be(0xABCD)));
5284        assert_eq!(tx.value(), U256::zero());
5285        assert_eq!(tx.data(), &Bytes::new());
5286        assert!(tx.access_list().is_empty());
5287        assert!(tx.authorization_list().is_none());
5288        assert_eq!(tx.max_priority_fee(), Some(1_000_000_000));
5289        assert_eq!(tx.max_fee_per_gas(), Some(30_000_000_000));
5290        assert_eq!(tx.max_fee_per_blob_gas(), None); // no blobs
5291        assert!(!tx.is_contract_creation());
5292        // sender returns explicit sender, no ECDSA
5293        assert_eq!(
5294            tx.sender(&ethrex_crypto::NativeCrypto).unwrap(),
5295            Address::from_low_u64_be(0xABCD)
5296        );
5297        // gas_limit = intrinsic + calldata_cost + sum(frame.gas_limit)
5298        assert!(tx.gas_limit() >= 300_000); // at least sum of frame gas limits
5299    }
5300
5301    #[test]
5302    fn test_frame_transaction_hash_uses_oncecell() {
5303        let tx = Transaction::FrameTransaction(make_test_frame_tx());
5304        let h1 = tx.hash(&ethrex_crypto::NativeCrypto);
5305        let h2 = tx.hash(&ethrex_crypto::NativeCrypto);
5306        assert_eq!(h1, h2);
5307    }
5308
5309    #[test]
5310    fn frame_transaction_rlp_roundtrip_preserves_fields() {
5311        let tx = make_test_frame_tx();
5312        let mut buf = Vec::new();
5313        tx.encode(&mut buf);
5314        let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5315        assert!(rest.is_empty());
5316        assert_eq!(decoded.chain_id, tx.chain_id);
5317        assert_eq!(decoded.nonce, tx.nonce);
5318        assert_eq!(decoded.sender, tx.sender);
5319    }
5320
5321    #[test]
5322    fn frame_transaction_variant_is_exposed_on_transaction_enum() {
5323        let tx = Transaction::FrameTransaction(make_test_frame_tx());
5324        assert!(matches!(tx, Transaction::FrameTransaction(_)));
5325    }
5326
5327    #[test]
5328    fn frame_transaction_serializes_signatures_as_array() {
5329        // RPC must expose the full signature objects, not just a count.
5330        let tx = make_test_frame_tx();
5331        let json = serde_json::to_value(&tx).unwrap();
5332        let sigs = json
5333            .get("signatures")
5334            .expect("signatures field present")
5335            .as_array()
5336            .expect("signatures serialized as an array");
5337        assert_eq!(sigs.len(), tx.signatures.len());
5338        assert_eq!(sigs[0].get("scheme").unwrap(), "0x1");
5339        assert!(sigs[0].get("signer").is_some());
5340        assert!(sigs[0].get("signature").is_some());
5341        assert!(sigs[0].get("msg").is_some());
5342    }
5343
5344    fn make_frame_tx_with_gas_limits(limits: Vec<u64>) -> FrameTransaction {
5345        let frames = limits
5346            .into_iter()
5347            .map(|gl| Frame {
5348                mode: FrameMode::Sender as u8,
5349                flags: 0x00,
5350                target: Some(Address::from_low_u64_be(0x1234)),
5351                gas_limit: gl,
5352                value: U256::zero(),
5353                data: Bytes::new(),
5354            })
5355            .collect();
5356        FrameTransaction {
5357            chain_id: 1,
5358            nonce: 0,
5359            sender: Address::from_low_u64_be(0xABCD),
5360            frames,
5361            signatures: vec![],
5362            max_priority_fee_per_gas: 1_000_000_000,
5363            max_fee_per_gas: 30_000_000_000,
5364            max_fee_per_blob_gas: U256::zero(),
5365            blob_versioned_hashes: vec![],
5366            inner_hash: OnceCell::new(),
5367            cached_canonical: OnceCell::new(),
5368        }
5369    }
5370
5371    #[test]
5372    fn per_frame_gas_limit_above_i64_max_is_rejected() {
5373        let tx = make_frame_tx_with_gas_limits(vec![(i64::MAX as u64) + 1]);
5374        let err = tx.validate_static_constraints().unwrap_err();
5375        assert!(err.contains("exceeds 2**63-1"), "unexpected error: {err}");
5376    }
5377
5378    #[test]
5379    fn cumulative_frame_gas_limit_above_i64_max_is_rejected() {
5380        let half = (i64::MAX as u64) / 2 + 1;
5381        let tx = make_frame_tx_with_gas_limits(vec![half, half]);
5382        let err = tx.validate_static_constraints().unwrap_err();
5383        assert!(err.contains("cumulative"), "unexpected error: {err}");
5384    }
5385
5386    #[test]
5387    fn cumulative_frame_gas_limit_equal_to_i64_max_is_accepted() {
5388        let a = (i64::MAX as u64) / 2;
5389        let b = i64::MAX as u64 - a;
5390        let tx = make_frame_tx_with_gas_limits(vec![a, b]);
5391        tx.validate_static_constraints()
5392            .expect("exact i64::MAX total should be accepted");
5393    }
5394
5395    #[test]
5396    fn empty_frames_list_is_rejected_by_count_check_not_gas_check() {
5397        // The frame-count check fires before the gas-limit accumulator runs,
5398        // so an empty frame list surfaces the count error, not a gas error.
5399        let tx = make_frame_tx_with_gas_limits(vec![]);
5400        let err = tx.validate_static_constraints().unwrap_err();
5401        assert!(err.contains("between 1 and"), "unexpected error: {err}");
5402    }
5403
5404    #[test]
5405    fn sig_hash_covers_frame_value() {
5406        // Changing `value` on any frame (SENDER or VERIFY) must change the
5407        // canonical signature hash (all frame data covered).
5408        let tx = make_test_frame_tx();
5409        let baseline = tx.compute_sig_hash();
5410
5411        let mut with_sender_value = tx.clone();
5412        with_sender_value.frames[1].value = U256::from(1u64);
5413        assert_ne!(
5414            baseline,
5415            with_sender_value.compute_sig_hash(),
5416            "sig_hash must change when a SENDER frame's value changes"
5417        );
5418
5419        let mut with_verify_value = tx.clone();
5420        with_verify_value.frames[0].value = U256::from(1u64);
5421        assert_ne!(
5422            baseline,
5423            with_verify_value.compute_sig_hash(),
5424            "sig_hash must change when a VERIFY frame's value changes"
5425        );
5426
5427        // VERIFY.data is now covered too (EIP-8141 removed elision).
5428        let mut with_verify_data = tx;
5429        with_verify_data.frames[0].data = Bytes::from_static(b"different_verify_data");
5430        assert_ne!(
5431            baseline,
5432            with_verify_data.compute_sig_hash(),
5433            "VERIFY.data is now covered by sig_hash (no longer elided)"
5434        );
5435    }
5436
5437    #[test]
5438    fn validate_static_constraints_rejects_nonzero_value_on_non_sender_frames() {
5439        // VERIFY frame with non-zero value must be rejected.
5440        let verify_tx = FrameTransaction {
5441            chain_id: 1,
5442            nonce: 0,
5443            sender: Address::from_low_u64_be(0xABCD),
5444            frames: vec![Frame {
5445                mode: FrameMode::Verify as u8,
5446                flags: 0x01,
5447                target: None,
5448                gas_limit: 50_000,
5449                value: U256::from(1u64),
5450                data: Bytes::new(),
5451            }],
5452            signatures: vec![],
5453            max_priority_fee_per_gas: 1_000_000_000,
5454            max_fee_per_gas: 30_000_000_000,
5455            max_fee_per_blob_gas: U256::zero(),
5456            blob_versioned_hashes: vec![],
5457            inner_hash: OnceCell::new(),
5458            cached_canonical: OnceCell::new(),
5459        };
5460        let err = verify_tx.validate_static_constraints().unwrap_err();
5461        assert!(
5462            err.contains("non-zero value only allowed in SENDER mode"),
5463            "unexpected error for VERIFY: {err}"
5464        );
5465
5466        // DEFAULT frame with non-zero value must be rejected.
5467        let default_tx = FrameTransaction {
5468            frames: vec![Frame {
5469                mode: FrameMode::Default as u8,
5470                flags: 0x00,
5471                target: Some(Address::from_low_u64_be(0x1234)),
5472                gas_limit: 50_000,
5473                value: U256::from(1u64),
5474                data: Bytes::new(),
5475            }],
5476            ..verify_tx
5477        };
5478        let err = default_tx.validate_static_constraints().unwrap_err();
5479        assert!(
5480            err.contains("non-zero value only allowed in SENDER mode"),
5481            "unexpected error for DEFAULT: {err}"
5482        );
5483
5484        // SENDER frame with a non-zero value must remain valid.
5485        let sender_tx = FrameTransaction {
5486            frames: vec![Frame {
5487                mode: FrameMode::Sender as u8,
5488                flags: 0x00,
5489                target: Some(Address::from_low_u64_be(0x1234)),
5490                gas_limit: 50_000,
5491                value: U256::from(1u64),
5492                data: Bytes::new(),
5493            }],
5494            ..default_tx
5495        };
5496        sender_tx
5497            .validate_static_constraints()
5498            .expect("SENDER frames may carry non-zero value");
5499    }
5500
5501    // ── EIP-8141 fork-gate predicate tests ──
5502
5503    fn chain_config_with_hegota(hegota_time: Option<u64>) -> crate::types::ChainConfig {
5504        crate::types::ChainConfig {
5505            hegota_time,
5506            ..Default::default()
5507        }
5508    }
5509
5510    #[test]
5511    fn test_frame_tx_pre_fork_chain_config_rejects() {
5512        let cfg = chain_config_with_hegota(None);
5513        assert!(!cfg.is_hegota_activated(0));
5514        assert!(!cfg.is_hegota_activated(u64::MAX));
5515    }
5516
5517    #[test]
5518    fn test_frame_tx_post_fork_admits() {
5519        let cfg = chain_config_with_hegota(Some(1000));
5520        assert!(cfg.is_hegota_activated(2000));
5521    }
5522
5523    #[test]
5524    fn test_frame_tx_fork_boundary_admits() {
5525        let activation_time = 1_700_000_000u64;
5526        let cfg = chain_config_with_hegota(Some(activation_time));
5527        assert!(cfg.is_hegota_activated(activation_time));
5528        assert!(!cfg.is_hegota_activated(activation_time - 1));
5529    }
5530
5531    #[test]
5532    fn test_frame_tx_devnet_fork_epoch_zero_admits() {
5533        let cfg = chain_config_with_hegota(Some(0));
5534        assert!(cfg.is_hegota_activated(0));
5535        assert!(cfg.is_hegota_activated(1));
5536        assert!(cfg.is_hegota_activated(u64::MAX));
5537    }
5538
5539    #[test]
5540    fn test_frame_tx_pre_fork_rlp_roundtrip_still_decodes() {
5541        // RLP decoding is fork-unaware and must stay lossless so validators surface
5542        // a FrameTxPreFork error downstream rather than a corrupt-RLP error.
5543        let tx = make_test_frame_tx();
5544        let mut buf = Vec::new();
5545        tx.encode(&mut buf);
5546        let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5547        assert!(rest.is_empty());
5548        assert_eq!(decoded.chain_id, tx.chain_id);
5549        assert_eq!(decoded.nonce, tx.nonce);
5550        assert_eq!(decoded.sender, tx.sender);
5551    }
5552
5553    // ── EIP-8141 expiry verifier frame tests ──
5554
5555    fn expiry_frame(deadline: u64) -> Frame {
5556        Frame {
5557            mode: FrameMode::Verify as u8,
5558            flags: 0x00,
5559            target: Some(frame_tx_expiry_verifier()),
5560            gas_limit: 30_000,
5561            value: U256::zero(),
5562            data: Bytes::copy_from_slice(&deadline.to_be_bytes()),
5563        }
5564    }
5565
5566    #[test]
5567    fn expiry_verifier_frame_passes_static_validation() {
5568        let mut tx = make_test_frame_tx();
5569        tx.frames.insert(0, expiry_frame(1_700_000_000));
5570        assert!(tx.validate_static_constraints().is_ok());
5571    }
5572
5573    #[test]
5574    fn expiry_verifier_frame_rejects_bad_data_length() {
5575        let mut tx = make_test_frame_tx();
5576        let mut f = expiry_frame(0);
5577        f.data = Bytes::from_static(b"short");
5578        tx.frames.insert(0, f);
5579        let err = tx.validate_static_constraints().unwrap_err();
5580        assert!(err.contains("8 bytes"), "{err}");
5581    }
5582
5583    #[test]
5584    fn expiry_verifier_frame_rejects_nonzero_flags() {
5585        let mut tx = make_test_frame_tx();
5586        let mut f = expiry_frame(0);
5587        f.flags = 0x01;
5588        tx.frames.insert(0, f);
5589        let err = tx.validate_static_constraints().unwrap_err();
5590        assert!(err.contains("flags == 0"), "{err}");
5591    }
5592
5593    #[test]
5594    fn at_most_one_expiry_verifier_frame() {
5595        let mut tx = make_test_frame_tx();
5596        tx.frames.insert(0, expiry_frame(1));
5597        tx.frames.insert(0, expiry_frame(2));
5598        let err = tx.validate_static_constraints().unwrap_err();
5599        assert!(err.contains("more than one expiry"), "{err}");
5600    }
5601
5602    #[test]
5603    fn verify_frame_with_zero_scope_is_now_statically_valid() {
5604        // EIP-8141 has no the VERIFY-needs-nonzero-scope rule.
5605        // make_test_frame_tx() frame[0] is VERIFY with flags=0x03 targeting sender;
5606        // setting flags to 0 makes it a zero-scope VERIFY (not an expiry frame,
5607        // since target is the sender address, not EXPIRY_VERIFIER).
5608        let mut tx = make_test_frame_tx();
5609        tx.frames[0].flags = 0x00;
5610        assert!(tx.validate_static_constraints().is_ok());
5611    }
5612
5613    #[test]
5614    fn sig_hash_covers_expiry_deadline_and_all_verify_data() {
5615        // Updated for all frame data is now covered.
5616        let mut tx_a = make_test_frame_tx();
5617        tx_a.frames.insert(0, expiry_frame(100));
5618        let mut tx_b = make_test_frame_tx();
5619        tx_b.frames.insert(0, expiry_frame(200));
5620        // Different deadlines -> different sig hashes (expiry data covered).
5621        assert_ne!(tx_a.compute_sig_hash(), tx_b.compute_sig_hash());
5622
5623        // Different NON-expiry VERIFY data -> different sig hash (no longer elided).
5624        let tx_c = make_test_frame_tx();
5625        let mut tx_d = make_test_frame_tx();
5626        tx_d.frames[0].data = Bytes::from_static(b"different_verify_data");
5627        assert_ne!(tx_c.compute_sig_hash(), tx_d.compute_sig_hash());
5628    }
5629
5630    #[test]
5631    fn expiry_deadline_parses_or_none() {
5632        let mut tx = make_test_frame_tx();
5633        assert_eq!(tx.expiry_deadline(), None);
5634        tx.frames.insert(0, expiry_frame(1_700_000_123));
5635        assert_eq!(tx.expiry_deadline(), Some(1_700_000_123));
5636    }
5637
5638    #[test]
5639    fn signature_rlp_roundtrip() {
5640        let sig = FrameSignature {
5641            scheme: FRAME_SIG_SCHEME_P256,
5642            signer: Some(Address::from_low_u64_be(0x1234)),
5643            msg: Bytes::from(vec![7u8; 32]),
5644            signature: Bytes::from(vec![9u8; 128]),
5645        };
5646        let mut buf = Vec::new();
5647        sig.encode(&mut buf);
5648        let (decoded, rest) = FrameSignature::decode_unfinished(&buf).unwrap();
5649        assert!(rest.is_empty());
5650        assert_eq!(decoded, sig);
5651    }
5652
5653    #[test]
5654    fn frame_tx_with_signatures_rlp_roundtrip() {
5655        let tx = make_test_frame_tx();
5656        assert!(!tx.signatures.is_empty());
5657        let mut buf = Vec::new();
5658        tx.encode(&mut buf);
5659        let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5660        assert!(rest.is_empty());
5661        assert_eq!(decoded.signatures, tx.signatures);
5662        assert_eq!(decoded.frames, tx.frames);
5663        assert_eq!(decoded.nonce, tx.nonce);
5664    }
5665
5666    #[test]
5667    fn p2p_frame_transaction_rlp_roundtrip() {
5668        // A frame tx (EIP-8141) must survive a full P2PTransaction RLP
5669        // encode/decode round-trip so it can be served over the wire on request.
5670        let ft = make_test_frame_tx();
5671        assert!(!ft.signatures.is_empty());
5672        assert!(!ft.frames.is_empty());
5673        let original = P2PTransaction::FrameTransaction(ft);
5674
5675        let encoded = original.encode_to_vec();
5676        let (decoded, rest) = P2PTransaction::decode_unfinished(&encoded).unwrap();
5677        assert!(rest.is_empty());
5678        assert_eq!(decoded, original);
5679        assert_eq!(decoded.tx_type(), TxType::Frame);
5680
5681        // The decoded variant converts cleanly into a regular Transaction
5682        // (frame txs carry no blobs bundle).
5683        let as_tx: Transaction = decoded.try_into().unwrap();
5684        assert!(matches!(as_tx, Transaction::FrameTransaction(_)));
5685    }
5686
5687    #[test]
5688    fn static_validation_rejects_unknown_scheme() {
5689        let mut tx = make_test_frame_tx();
5690        tx.signatures[0].scheme = 3;
5691        assert!(
5692            tx.validate_static_constraints()
5693                .unwrap_err()
5694                .contains("unsupported scheme"),
5695        );
5696    }
5697
5698    #[test]
5699    fn static_validation_accepts_arbitrary_with_empty_signer() {
5700        let mut tx = make_test_frame_tx();
5701        // ARBITRARY (scheme 0) requires an empty signer and costs 100 verify gas.
5702        tx.signatures[0].scheme = FRAME_SIG_SCHEME_ARBITRARY;
5703        tx.signatures[0].signer = None;
5704        assert!(tx.validate_static_constraints().is_ok());
5705        assert_eq!(tx.signature_verification_cost(), 100);
5706    }
5707
5708    #[test]
5709    fn static_validation_rejects_arbitrary_with_signer() {
5710        let mut tx = make_test_frame_tx();
5711        tx.signatures[0].scheme = FRAME_SIG_SCHEME_ARBITRARY;
5712        tx.signatures[0].signer = Some(Address::from_low_u64_be(0xABCD));
5713        assert!(
5714            tx.validate_static_constraints()
5715                .unwrap_err()
5716                .contains("ARBITRARY signatures must not name a signer"),
5717        );
5718    }
5719
5720    #[test]
5721    fn static_validation_rejects_bad_msg_length() {
5722        let mut tx = make_test_frame_tx();
5723        tx.signatures[0].msg = Bytes::from(vec![1u8; 16]);
5724        assert!(
5725            tx.validate_static_constraints()
5726                .unwrap_err()
5727                .contains("32 bytes"),
5728        );
5729    }
5730
5731    #[test]
5732    fn static_validation_rejects_zero_explicit_msg() {
5733        let mut tx = make_test_frame_tx();
5734        tx.signatures[0].msg = Bytes::from(vec![0u8; 32]);
5735        assert!(
5736            tx.validate_static_constraints()
5737                .unwrap_err()
5738                .contains("zero digest"),
5739        );
5740    }
5741
5742    #[test]
5743    fn sig_hash_elides_empty_msg_signature_bytes() {
5744        let mut a = make_test_frame_tx();
5745        let mut b = make_test_frame_tx();
5746        // empty-msg signature: changing raw bytes must NOT change the hash.
5747        assert!(a.signatures[0].msg.is_empty());
5748        a.signatures[0].signature = Bytes::from(vec![1u8; 65]);
5749        b.signatures[0].signature = Bytes::from(vec![2u8; 65]);
5750        assert_eq!(a.compute_sig_hash(), b.compute_sig_hash());
5751    }
5752
5753    #[test]
5754    fn sig_hash_covers_frame_data_now() {
5755        let mut a = make_test_frame_tx();
5756        let mut b = make_test_frame_tx();
5757        // frame 0 is a VERIFY frame; its data is now COVERED (not elided).
5758        a.frames[0].data = Bytes::from_static(b"aaaa");
5759        b.frames[0].data = Bytes::from_static(b"bbbb");
5760        assert_ne!(a.compute_sig_hash(), b.compute_sig_hash());
5761    }
5762
5763    #[test]
5764    fn sig_hash_covers_explicit_msg_signature_bytes() {
5765        let mut a = make_test_frame_tx();
5766        let mut b = make_test_frame_tx();
5767        a.signatures[0].msg = Bytes::from(vec![9u8; 32]);
5768        b.signatures[0].msg = Bytes::from(vec![9u8; 32]);
5769        a.signatures[0].signature = Bytes::from(vec![1u8; 65]);
5770        b.signatures[0].signature = Bytes::from(vec![2u8; 65]);
5771        // explicit-msg signatures keep their bytes -> different hash.
5772        assert_ne!(a.compute_sig_hash(), b.compute_sig_hash());
5773    }
5774
5775    #[test]
5776    fn max_gas_includes_signature_costs() {
5777        let mut tx = make_test_frame_tx();
5778        let base = tx.max_gas();
5779        // Add a P256 signature; cost must rise by at least 6700 + its calldata.
5780        tx.signatures.push(FrameSignature {
5781            scheme: FRAME_SIG_SCHEME_P256,
5782            signer: Some(Address::from_low_u64_be(1)),
5783            msg: Bytes::new(),
5784            signature: Bytes::from(vec![0u8; 128]),
5785        });
5786        assert!(tx.max_gas() >= base + 6700);
5787        assert_eq!(tx.signature_verification_cost(), 2800 + 6700);
5788    }
5789
5790    #[test]
5791    fn golden_frame_tx_rlp_and_sig_hash() {
5792        // Regression lock for the EIP-8141 signatures-list wire format. No
5793        // external EEST reference vectors exist yet;
5794        // these values are the current canonical output and must only change
5795        // with a deliberate, reviewed format change.
5796        let tx = FrameTransaction {
5797            chain_id: 1,
5798            nonce: 7,
5799            sender: Address::from_low_u64_be(0xABCD),
5800            frames: vec![
5801                Frame {
5802                    mode: 1,
5803                    flags: 3,
5804                    target: None,
5805                    gas_limit: 0x5208,
5806                    value: U256::zero(),
5807                    data: Bytes::from_static(&[0x11, 0x22]),
5808                },
5809                Frame {
5810                    mode: 2,
5811                    flags: 0,
5812                    target: Some(Address::from_low_u64_be(0x1234)),
5813                    gas_limit: 0x9c40,
5814                    value: U256::zero(),
5815                    data: Bytes::new(),
5816                },
5817            ],
5818            signatures: vec![FrameSignature {
5819                scheme: FRAME_SIG_SCHEME_SECP256K1,
5820                signer: Some(Address::from_low_u64_be(0xABCD)),
5821                msg: Bytes::new(),
5822                signature: Bytes::from(vec![0x01u8; 65]),
5823            }],
5824            max_priority_fee_per_gas: 0x3b9aca00,
5825            max_fee_per_gas: 0x6fc23ac00,
5826            max_fee_per_blob_gas: U256::zero(),
5827            blob_versioned_hashes: vec![],
5828            inner_hash: OnceCell::new(),
5829            cached_canonical: OnceCell::new(),
5830        };
5831
5832        let mut buf = Vec::new();
5833        tx.encode(&mut buf);
5834        let rlp_hex = hex::encode(&buf);
5835        // GOLDEN_RLP: obtained from first run
5836        assert_eq!(
5837            rlp_hex,
5838            "f8ab010794000000000000000000000000000000000000abcde8ca01038082520880821122dc0280940000000000000000000000000000000000001234829c408080f85cf85a0194000000000000000000000000000000000000abcd80b8410101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101843b9aca008506fc23ac0080c0"
5839        );
5840
5841        // Round-trips losslessly.
5842        let (decoded, rest) = FrameTransaction::decode_unfinished(&buf).unwrap();
5843        assert!(rest.is_empty());
5844        assert_eq!(decoded, tx);
5845
5846        let sig_hash = tx.compute_sig_hash();
5847        // GOLDEN_SIG_HASH: obtained from first run
5848        assert_eq!(
5849            format!("{:#x}", sig_hash),
5850            "0xe7dc3f33413fc69c09f9c690be154ded294954e497aeea6ce0010ba513f2f26d",
5851        );
5852
5853        // Elision invariant: changing empty-msg signature bytes must NOT change sig_hash.
5854        let mut tx2 = tx.clone();
5855        tx2.signatures[0].signature = Bytes::from(vec![0x02u8; 65]);
5856        assert_eq!(
5857            tx.compute_sig_hash(),
5858            tx2.compute_sig_hash(),
5859            "sig_hash must be independent of empty-msg signature bytes",
5860        );
5861    }
5862
5863    #[test]
5864    fn test_cost_without_base_fee_eip4844_includes_blob_gas() {
5865        // Regression test for mempool balance check: for EIP-4844 txs,
5866        // cost_without_base_fee() MUST include blob_gas_used * max_fee_per_blob_gas.
5867        // Every peer client (geth, reth, nethermind, erigon, besu) does this.
5868        use crate::constants::GAS_PER_BLOB;
5869
5870        let max_fee_per_gas: u64 = 100;
5871        let gas: u64 = 21_000;
5872        let value = U256::from(7u64);
5873        let max_fee_per_blob_gas = U256::from(50u64);
5874        let blob_count: usize = 1;
5875
5876        let tx = Transaction::EIP4844Transaction(EIP4844Transaction {
5877            max_fee_per_gas,
5878            gas,
5879            value,
5880            max_fee_per_blob_gas,
5881            blob_versioned_hashes: vec![H256::zero(); blob_count],
5882            ..Default::default()
5883        });
5884
5885        let got = tx.cost_without_base_fee().expect("cost is computable");
5886
5887        let gas_cost = U256::from(max_fee_per_gas) * U256::from(gas);
5888        let blob_gas = U256::from(GAS_PER_BLOB) * U256::from(blob_count as u64);
5889        let blob_cost = blob_gas * max_fee_per_blob_gas;
5890        let expected = gas_cost + blob_cost + value;
5891
5892        assert_eq!(
5893            got, expected,
5894            "blob-gas term missing from cost_without_base_fee() for EIP-4844"
5895        );
5896    }
5897}