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