Skip to main content

ethrex_common/types/
transaction.rs

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