alloy_consensus/transaction/
envelope.rs

1use super::SignableTransaction;
2use crate::{
3    error::ValueError,
4    transaction::{
5        eip4844::{TxEip4844, TxEip4844Variant},
6        tx_type::TxType,
7        RlpEcdsaDecodableTx, RlpEcdsaEncodableTx,
8    },
9    EthereumTypedTransaction, Signed, Transaction, TxEip1559, TxEip2930, TxEip4844WithSidecar,
10    TxEip7702, TxLegacy,
11};
12use alloy_eips::{
13    eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718, IsTyped2718},
14    eip2930::AccessList,
15    eip7594::Encodable7594,
16    Typed2718,
17};
18use alloy_primitives::{Bytes, ChainId, Signature, TxKind, B256, U256};
19use alloy_rlp::{Decodable, Encodable};
20use core::{
21    fmt::Debug,
22    hash::{Hash, Hasher},
23};
24
25/// The Ethereum [EIP-2718] Transaction Envelope.
26///
27/// # Note:
28///
29/// This enum distinguishes between tagged and untagged legacy transactions, as
30/// the in-protocol merkle tree may commit to EITHER 0-prefixed or raw.
31/// Therefore we must ensure that encoding returns the precise byte-array that
32/// was decoded, preserving the presence or absence of the `TransactionType`
33/// flag.
34///
35/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
36pub type TxEnvelope = EthereumTxEnvelope<TxEip4844Variant>;
37
38impl<T: Encodable7594> EthereumTxEnvelope<TxEip4844Variant<T>> {
39    /// Attempts to convert the envelope into the pooled variant.
40    ///
41    /// Returns an error if the envelope's variant is incompatible with the pooled format:
42    /// [`crate::TxEip4844`] without the sidecar.
43    pub fn try_into_pooled(
44        self,
45    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
46        match self {
47            Self::Legacy(tx) => Ok(tx.into()),
48            Self::Eip2930(tx) => Ok(tx.into()),
49            Self::Eip1559(tx) => Ok(tx.into()),
50            Self::Eip4844(tx) => EthereumTxEnvelope::try_from(tx).map_err(ValueError::convert),
51            Self::Eip7702(tx) => Ok(tx.into()),
52        }
53    }
54}
55
56impl EthereumTxEnvelope<TxEip4844> {
57    /// Attempts to convert the envelope into the pooled variant.
58    ///
59    /// Returns an error if the envelope's variant is incompatible with the pooled format:
60    /// [`crate::TxEip4844`] without the sidecar.
61    pub fn try_into_pooled<T>(
62        self,
63    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
64        match self {
65            Self::Legacy(tx) => Ok(tx.into()),
66            Self::Eip2930(tx) => Ok(tx.into()),
67            Self::Eip1559(tx) => Ok(tx.into()),
68            Self::Eip4844(tx) => {
69                Err(ValueError::new(tx.into(), "pooled transaction requires 4844 sidecar"))
70            }
71            Self::Eip7702(tx) => Ok(tx.into()),
72        }
73    }
74
75    /// Converts from an EIP-4844 transaction to a [`EthereumTxEnvelope<TxEip4844WithSidecar<T>>`]
76    /// with the given sidecar.
77    ///
78    /// Returns an `Err` containing the original [`EthereumTxEnvelope`] if the transaction is not an
79    /// EIP-4844 variant.
80    pub fn try_into_pooled_eip4844<T>(
81        self,
82        sidecar: T,
83    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
84        match self {
85            Self::Eip4844(tx) => {
86                Ok(EthereumTxEnvelope::Eip4844(tx.map(|tx| tx.with_sidecar(sidecar))))
87            }
88            this => Err(ValueError::new_static(this, "Expected 4844 transaction")),
89        }
90    }
91}
92
93impl<T> EthereumTxEnvelope<T> {
94    /// Creates a new signed transaction from the given transaction, signature and hash.
95    ///
96    /// Caution: This assumes the given hash is the correct transaction hash.
97    pub fn new_unchecked(
98        transaction: EthereumTypedTransaction<T>,
99        signature: Signature,
100        hash: B256,
101    ) -> Self
102    where
103        T: RlpEcdsaEncodableTx,
104    {
105        Signed::new_unchecked(transaction, signature, hash).into()
106    }
107
108    /// Creates a new signed transaction from the given transaction, signature and hash.
109    ///
110    /// Caution: This assumes the given hash is the correct transaction hash.
111    #[deprecated(note = "Use new_unchecked() instead")]
112    pub fn new(transaction: EthereumTypedTransaction<T>, signature: Signature, hash: B256) -> Self
113    where
114        T: RlpEcdsaEncodableTx,
115    {
116        Self::new_unchecked(transaction, signature, hash)
117    }
118
119    /// Creates a new signed transaction from the given typed transaction and signature without the
120    /// hash.
121    ///
122    /// Note: this only calculates the hash on the first [`EthereumTxEnvelope::hash`] call.
123    pub fn new_unhashed(transaction: EthereumTypedTransaction<T>, signature: Signature) -> Self
124    where
125        T: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
126    {
127        transaction.into_signed(signature).into()
128    }
129
130    /// Consumes the type, removes the signature and returns the transaction.
131    #[inline]
132    pub fn into_typed_transaction(self) -> EthereumTypedTransaction<T>
133    where
134        T: RlpEcdsaEncodableTx,
135    {
136        match self {
137            Self::Legacy(tx) => EthereumTypedTransaction::Legacy(tx.into_parts().0),
138            Self::Eip2930(tx) => EthereumTypedTransaction::Eip2930(tx.into_parts().0),
139            Self::Eip1559(tx) => EthereumTypedTransaction::Eip1559(tx.into_parts().0),
140            Self::Eip4844(tx) => EthereumTypedTransaction::Eip4844(tx.into_parts().0),
141            Self::Eip7702(tx) => EthereumTypedTransaction::Eip7702(tx.into_parts().0),
142        }
143    }
144
145    /// Returns a mutable reference to the transaction's input.
146    #[doc(hidden)]
147    pub fn input_mut(&mut self) -> &mut Bytes
148    where
149        T: AsMut<TxEip4844>,
150    {
151        match self {
152            Self::Eip1559(tx) => &mut tx.tx_mut().input,
153            Self::Eip2930(tx) => &mut tx.tx_mut().input,
154            Self::Legacy(tx) => &mut tx.tx_mut().input,
155            Self::Eip7702(tx) => &mut tx.tx_mut().input,
156            Self::Eip4844(tx) => &mut tx.tx_mut().as_mut().input,
157        }
158    }
159}
160
161/// The Ethereum [EIP-2718] Transaction Envelope.
162///
163/// # Note:
164///
165/// This enum distinguishes between tagged and untagged legacy transactions, as
166/// the in-protocol merkle tree may commit to EITHER 0-prefixed or raw.
167/// Therefore we must ensure that encoding returns the precise byte-array that
168/// was decoded, preserving the presence or absence of the `TransactionType`
169/// flag.
170///
171/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
172#[derive(Clone, Debug)]
173#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
174#[cfg_attr(
175    feature = "serde",
176    serde(
177        into = "serde_from::TaggedTxEnvelope<Eip4844>",
178        from = "serde_from::MaybeTaggedTxEnvelope<Eip4844>",
179        bound = "Eip4844: Clone + RlpEcdsaEncodableTx + serde::Serialize + serde::de::DeserializeOwned"
180    )
181)]
182#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))]
183#[cfg_attr(
184    all(any(test, feature = "arbitrary"), feature = "k256"),
185    arbitrary(
186        bound = "Eip4844: for<'a> arbitrary::Arbitrary<'a> + RlpEcdsaEncodableTx + SignableTransaction<Signature>"
187    )
188)]
189#[doc(alias = "TransactionEnvelope")]
190pub enum EthereumTxEnvelope<Eip4844> {
191    /// An untagged [`TxLegacy`].
192    Legacy(Signed<TxLegacy>),
193    /// A [`TxEip2930`] tagged with type 1.
194    Eip2930(Signed<TxEip2930>),
195    /// A [`TxEip1559`] tagged with type 2.
196    Eip1559(Signed<TxEip1559>),
197    /// A TxEip4844 tagged with type 3.
198    /// An EIP-4844 transaction has two network representations:
199    /// 1 - The transaction itself, which is a regular RLP-encoded transaction and used to retrieve
200    /// historical transactions..
201    ///
202    /// 2 - The transaction with a sidecar, which is the form used to
203    /// send transactions to the network.
204    Eip4844(Signed<Eip4844>),
205    /// A [`TxEip7702`] tagged with type 4.
206    Eip7702(Signed<TxEip7702>),
207}
208
209impl<Eip4844: RlpEcdsaEncodableTx + PartialEq> PartialEq for EthereumTxEnvelope<Eip4844>
210where
211    Eip4844: PartialEq,
212{
213    fn eq(&self, other: &Self) -> bool {
214        match (self, other) {
215            (Self::Legacy(f0_self), Self::Legacy(f0_other)) => f0_self.eq(f0_other),
216            (Self::Eip2930(f0_self), Self::Eip2930(f0_other)) => f0_self.eq(f0_other),
217            (Self::Eip1559(f0_self), Self::Eip1559(f0_other)) => f0_self.eq(f0_other),
218            (Self::Eip4844(f0_self), Self::Eip4844(f0_other)) => f0_self.eq(f0_other),
219            (Self::Eip7702(f0_self), Self::Eip7702(f0_other)) => f0_self.eq(f0_other),
220            _unused => false,
221        }
222    }
223}
224
225impl<Eip4844: RlpEcdsaEncodableTx + PartialEq> Eq for EthereumTxEnvelope<Eip4844> {}
226
227impl<Eip4844> Hash for EthereumTxEnvelope<Eip4844>
228where
229    Self: Encodable2718,
230{
231    fn hash<H: Hasher>(&self, state: &mut H) {
232        self.trie_hash().hash(state);
233    }
234}
235
236impl<T, Eip4844> From<Signed<T>> for EthereumTxEnvelope<Eip4844>
237where
238    EthereumTypedTransaction<Eip4844>: From<T>,
239    T: RlpEcdsaEncodableTx,
240{
241    fn from(v: Signed<T>) -> Self {
242        let (tx, sig, hash) = v.into_parts();
243        let typed = EthereumTypedTransaction::from(tx);
244        match typed {
245            EthereumTypedTransaction::Legacy(tx_legacy) => {
246                let tx = Signed::new_unchecked(tx_legacy, sig, hash);
247                Self::Legacy(tx)
248            }
249            EthereumTypedTransaction::Eip2930(tx_eip2930) => {
250                let tx = Signed::new_unchecked(tx_eip2930, sig, hash);
251                Self::Eip2930(tx)
252            }
253            EthereumTypedTransaction::Eip1559(tx_eip1559) => {
254                let tx = Signed::new_unchecked(tx_eip1559, sig, hash);
255                Self::Eip1559(tx)
256            }
257            EthereumTypedTransaction::Eip4844(tx_eip4844_variant) => {
258                let tx = Signed::new_unchecked(tx_eip4844_variant, sig, hash);
259                Self::Eip4844(tx)
260            }
261            EthereumTypedTransaction::Eip7702(tx_eip7702) => {
262                let tx = Signed::new_unchecked(tx_eip7702, sig, hash);
263                Self::Eip7702(tx)
264            }
265        }
266    }
267}
268
269impl<Eip4844: RlpEcdsaEncodableTx> From<EthereumTxEnvelope<Eip4844>>
270    for Signed<EthereumTypedTransaction<Eip4844>>
271where
272    EthereumTypedTransaction<Eip4844>: From<Eip4844>,
273{
274    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
275        value.into_signed()
276    }
277}
278
279impl<Eip4844> From<(EthereumTypedTransaction<Eip4844>, Signature)> for EthereumTxEnvelope<Eip4844>
280where
281    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
282{
283    fn from(value: (EthereumTypedTransaction<Eip4844>, Signature)) -> Self {
284        value.0.into_signed(value.1).into()
285    }
286}
287
288impl<T> From<EthereumTxEnvelope<TxEip4844WithSidecar<T>>> for EthereumTxEnvelope<TxEip4844> {
289    fn from(value: EthereumTxEnvelope<TxEip4844WithSidecar<T>>) -> Self {
290        value.map_eip4844(|eip4844| eip4844.into())
291    }
292}
293
294impl<T> From<EthereumTxEnvelope<TxEip4844Variant<T>>> for EthereumTxEnvelope<TxEip4844> {
295    fn from(value: EthereumTxEnvelope<TxEip4844Variant<T>>) -> Self {
296        value.map_eip4844(|eip4844| eip4844.into())
297    }
298}
299
300impl<T> From<EthereumTxEnvelope<TxEip4844>> for EthereumTxEnvelope<TxEip4844Variant<T>> {
301    fn from(value: EthereumTxEnvelope<TxEip4844>) -> Self {
302        value.map_eip4844(|eip4844| eip4844.into())
303    }
304}
305
306impl<Eip4844> EthereumTxEnvelope<Eip4844> {
307    /// Converts the EIP-4844 variant of this transaction with the given closure.
308    ///
309    /// This is intended to convert between the EIP-4844 variants, specifically for stripping away
310    /// non consensus data (blob sidecar data).
311    pub fn map_eip4844<U>(self, f: impl FnMut(Eip4844) -> U) -> EthereumTxEnvelope<U> {
312        match self {
313            Self::Legacy(tx) => EthereumTxEnvelope::Legacy(tx),
314            Self::Eip2930(tx) => EthereumTxEnvelope::Eip2930(tx),
315            Self::Eip1559(tx) => EthereumTxEnvelope::Eip1559(tx),
316            Self::Eip4844(tx) => EthereumTxEnvelope::Eip4844(tx.map(f)),
317            Self::Eip7702(tx) => EthereumTxEnvelope::Eip7702(tx),
318        }
319    }
320
321    /// Return the [`TxType`] of the inner txn.
322    #[doc(alias = "transaction_type")]
323    pub const fn tx_type(&self) -> TxType {
324        match self {
325            Self::Legacy(_) => TxType::Legacy,
326            Self::Eip2930(_) => TxType::Eip2930,
327            Self::Eip1559(_) => TxType::Eip1559,
328            Self::Eip4844(_) => TxType::Eip4844,
329            Self::Eip7702(_) => TxType::Eip7702,
330        }
331    }
332
333    /// Consumes the type into a [`Signed`]
334    pub fn into_signed(self) -> Signed<EthereumTypedTransaction<Eip4844>>
335    where
336        EthereumTypedTransaction<Eip4844>: From<Eip4844>,
337    {
338        match self {
339            Self::Legacy(tx) => tx.convert(),
340            Self::Eip2930(tx) => tx.convert(),
341            Self::Eip1559(tx) => tx.convert(),
342            Self::Eip4844(tx) => tx.convert(),
343            Self::Eip7702(tx) => tx.convert(),
344        }
345    }
346}
347
348impl<Eip4844: RlpEcdsaEncodableTx> EthereumTxEnvelope<Eip4844> {
349    /// Returns true if the transaction is a legacy transaction.
350    #[inline]
351    pub const fn is_legacy(&self) -> bool {
352        matches!(self, Self::Legacy(_))
353    }
354
355    /// Returns true if the transaction is an EIP-2930 transaction.
356    #[inline]
357    pub const fn is_eip2930(&self) -> bool {
358        matches!(self, Self::Eip2930(_))
359    }
360
361    /// Returns true if the transaction is an EIP-1559 transaction.
362    #[inline]
363    pub const fn is_eip1559(&self) -> bool {
364        matches!(self, Self::Eip1559(_))
365    }
366
367    /// Returns true if the transaction is an EIP-4844 transaction.
368    #[inline]
369    pub const fn is_eip4844(&self) -> bool {
370        matches!(self, Self::Eip4844(_))
371    }
372
373    /// Returns true if the transaction is an EIP-7702 transaction.
374    #[inline]
375    pub const fn is_eip7702(&self) -> bool {
376        matches!(self, Self::Eip7702(_))
377    }
378
379    /// Returns true if the transaction is replay protected.
380    ///
381    /// All non-legacy transactions are replay protected, as the chain id is
382    /// included in the transaction body. Legacy transactions are considered
383    /// replay protected if the `v` value is not 27 or 28, according to the
384    /// rules of [EIP-155].
385    ///
386    /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
387    #[inline]
388    pub const fn is_replay_protected(&self) -> bool {
389        match self {
390            Self::Legacy(tx) => tx.tx().chain_id.is_some(),
391            _ => true,
392        }
393    }
394
395    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
396    pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
397        match self {
398            Self::Legacy(tx) => Some(tx),
399            _ => None,
400        }
401    }
402
403    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
404    pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
405        match self {
406            Self::Eip2930(tx) => Some(tx),
407            _ => None,
408        }
409    }
410
411    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
412    pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
413        match self {
414            Self::Eip1559(tx) => Some(tx),
415            _ => None,
416        }
417    }
418
419    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
420    pub const fn as_eip4844(&self) -> Option<&Signed<Eip4844>> {
421        match self {
422            Self::Eip4844(tx) => Some(tx),
423            _ => None,
424        }
425    }
426
427    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
428    pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
429        match self {
430            Self::Eip7702(tx) => Some(tx),
431            _ => None,
432        }
433    }
434
435    /// Calculate the signing hash for the transaction.
436    pub fn signature_hash(&self) -> B256
437    where
438        Eip4844: SignableTransaction<Signature>,
439    {
440        match self {
441            Self::Legacy(tx) => tx.signature_hash(),
442            Self::Eip2930(tx) => tx.signature_hash(),
443            Self::Eip1559(tx) => tx.signature_hash(),
444            Self::Eip4844(tx) => tx.signature_hash(),
445            Self::Eip7702(tx) => tx.signature_hash(),
446        }
447    }
448
449    /// Return the reference to signature.
450    pub const fn signature(&self) -> &Signature {
451        match self {
452            Self::Legacy(tx) => tx.signature(),
453            Self::Eip2930(tx) => tx.signature(),
454            Self::Eip1559(tx) => tx.signature(),
455            Self::Eip4844(tx) => tx.signature(),
456            Self::Eip7702(tx) => tx.signature(),
457        }
458    }
459
460    /// Return the hash of the inner Signed.
461    #[doc(alias = "transaction_hash")]
462    pub fn tx_hash(&self) -> &B256 {
463        match self {
464            Self::Legacy(tx) => tx.hash(),
465            Self::Eip2930(tx) => tx.hash(),
466            Self::Eip1559(tx) => tx.hash(),
467            Self::Eip4844(tx) => tx.hash(),
468            Self::Eip7702(tx) => tx.hash(),
469        }
470    }
471
472    /// Reference to transaction hash. Used to identify transaction.
473    pub fn hash(&self) -> &B256 {
474        match self {
475            Self::Legacy(tx) => tx.hash(),
476            Self::Eip2930(tx) => tx.hash(),
477            Self::Eip1559(tx) => tx.hash(),
478            Self::Eip7702(tx) => tx.hash(),
479            Self::Eip4844(tx) => tx.hash(),
480        }
481    }
482
483    /// Return the length of the inner txn, including type byte length
484    pub fn eip2718_encoded_length(&self) -> usize {
485        match self {
486            Self::Legacy(t) => t.eip2718_encoded_length(),
487            Self::Eip2930(t) => t.eip2718_encoded_length(),
488            Self::Eip1559(t) => t.eip2718_encoded_length(),
489            Self::Eip4844(t) => t.eip2718_encoded_length(),
490            Self::Eip7702(t) => t.eip2718_encoded_length(),
491        }
492    }
493}
494
495#[cfg(any(feature = "secp256k1", feature = "k256"))]
496impl<Eip4844> crate::transaction::SignerRecoverable for EthereumTxEnvelope<Eip4844>
497where
498    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
499{
500    fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
501        let signature_hash = self.signature_hash();
502        crate::crypto::secp256k1::recover_signer(self.signature(), signature_hash)
503    }
504
505    fn recover_signer_unchecked(
506        &self,
507    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
508        let signature_hash = self.signature_hash();
509        crate::crypto::secp256k1::recover_signer_unchecked(self.signature(), signature_hash)
510    }
511}
512
513impl<Eip4844> Encodable for EthereumTxEnvelope<Eip4844>
514where
515    Self: Encodable2718,
516{
517    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
518        self.network_encode(out)
519    }
520
521    fn length(&self) -> usize {
522        self.network_len()
523    }
524}
525
526impl<Eip4844: RlpEcdsaDecodableTx> Decodable for EthereumTxEnvelope<Eip4844> {
527    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
528        Ok(Self::network_decode(buf)?)
529    }
530}
531
532impl<Eip4844: RlpEcdsaDecodableTx> Decodable2718 for EthereumTxEnvelope<Eip4844> {
533    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
534        match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("unexpected tx type"))? {
535            TxType::Eip2930 => Ok(TxEip2930::rlp_decode_signed(buf)?.into()),
536            TxType::Eip1559 => Ok(TxEip1559::rlp_decode_signed(buf)?.into()),
537            TxType::Eip4844 => Ok(Self::Eip4844(Eip4844::rlp_decode_signed(buf)?)),
538            TxType::Eip7702 => Ok(TxEip7702::rlp_decode_signed(buf)?.into()),
539            TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
540        }
541    }
542
543    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
544        TxLegacy::rlp_decode_signed(buf).map(Into::into).map_err(Into::into)
545    }
546}
547
548impl<T> Typed2718 for Signed<T>
549where
550    T: RlpEcdsaEncodableTx + Send + Sync + Typed2718,
551{
552    fn ty(&self) -> u8 {
553        self.tx().ty()
554    }
555}
556
557impl<T> IsTyped2718 for EthereumTxEnvelope<T> {
558    fn is_type(type_id: u8) -> bool {
559        <TxType as IsTyped2718>::is_type(type_id)
560    }
561}
562
563impl<T> Encodable2718 for Signed<T>
564where
565    T: RlpEcdsaEncodableTx + Typed2718 + Send + Sync,
566{
567    fn encode_2718_len(&self) -> usize {
568        self.eip2718_encoded_length()
569    }
570
571    fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
572        self.eip2718_encode(out)
573    }
574
575    fn trie_hash(&self) -> B256 {
576        *self.hash()
577    }
578}
579
580impl<T> Decodable2718 for Signed<T>
581where
582    T: RlpEcdsaDecodableTx + Typed2718 + Send + Sync,
583{
584    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
585        let decoded = T::rlp_decode_signed(buf)?;
586
587        if decoded.ty() != ty {
588            return Err(Eip2718Error::UnexpectedType(ty));
589        }
590
591        Ok(decoded)
592    }
593
594    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
595        T::rlp_decode_signed(buf).map_err(Into::into)
596    }
597}
598
599impl<Eip4844> Encodable2718 for EthereumTxEnvelope<Eip4844>
600where
601    Eip4844: RlpEcdsaEncodableTx + Typed2718 + Send + Sync,
602{
603    fn encode_2718_len(&self) -> usize {
604        self.eip2718_encoded_length()
605    }
606
607    fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
608        match self {
609            // Legacy transactions have no difference between network and 2718
610            Self::Legacy(tx) => tx.eip2718_encode(out),
611            Self::Eip2930(tx) => {
612                tx.eip2718_encode(out);
613            }
614            Self::Eip1559(tx) => {
615                tx.eip2718_encode(out);
616            }
617            Self::Eip4844(tx) => {
618                tx.eip2718_encode(out);
619            }
620            Self::Eip7702(tx) => {
621                tx.eip2718_encode(out);
622            }
623        }
624    }
625
626    fn trie_hash(&self) -> B256 {
627        match self {
628            Self::Legacy(tx) => *tx.hash(),
629            Self::Eip2930(tx) => *tx.hash(),
630            Self::Eip1559(tx) => *tx.hash(),
631            Self::Eip4844(tx) => *tx.hash(),
632            Self::Eip7702(tx) => *tx.hash(),
633        }
634    }
635}
636
637impl<Eip4844> Transaction for EthereumTxEnvelope<Eip4844>
638where
639    Self: Typed2718,
640    Eip4844: Transaction + Send + Sync,
641{
642    #[inline]
643    fn chain_id(&self) -> Option<ChainId> {
644        match self {
645            Self::Legacy(tx) => tx.tx().chain_id(),
646            Self::Eip2930(tx) => tx.tx().chain_id(),
647            Self::Eip1559(tx) => tx.tx().chain_id(),
648            Self::Eip4844(tx) => tx.tx().chain_id(),
649            Self::Eip7702(tx) => tx.tx().chain_id(),
650        }
651    }
652
653    #[inline]
654    fn nonce(&self) -> u64 {
655        match self {
656            Self::Legacy(tx) => tx.tx().nonce(),
657            Self::Eip2930(tx) => tx.tx().nonce(),
658            Self::Eip1559(tx) => tx.tx().nonce(),
659            Self::Eip4844(tx) => tx.tx().nonce(),
660            Self::Eip7702(tx) => tx.tx().nonce(),
661        }
662    }
663
664    #[inline]
665    fn gas_limit(&self) -> u64 {
666        match self {
667            Self::Legacy(tx) => tx.tx().gas_limit(),
668            Self::Eip2930(tx) => tx.tx().gas_limit(),
669            Self::Eip1559(tx) => tx.tx().gas_limit(),
670            Self::Eip4844(tx) => tx.tx().gas_limit(),
671            Self::Eip7702(tx) => tx.tx().gas_limit(),
672        }
673    }
674
675    #[inline]
676    fn gas_price(&self) -> Option<u128> {
677        match self {
678            Self::Legacy(tx) => tx.tx().gas_price(),
679            Self::Eip2930(tx) => tx.tx().gas_price(),
680            Self::Eip1559(tx) => tx.tx().gas_price(),
681            Self::Eip4844(tx) => tx.tx().gas_price(),
682            Self::Eip7702(tx) => tx.tx().gas_price(),
683        }
684    }
685
686    #[inline]
687    fn max_fee_per_gas(&self) -> u128 {
688        match self {
689            Self::Legacy(tx) => tx.tx().max_fee_per_gas(),
690            Self::Eip2930(tx) => tx.tx().max_fee_per_gas(),
691            Self::Eip1559(tx) => tx.tx().max_fee_per_gas(),
692            Self::Eip4844(tx) => tx.tx().max_fee_per_gas(),
693            Self::Eip7702(tx) => tx.tx().max_fee_per_gas(),
694        }
695    }
696
697    #[inline]
698    fn max_priority_fee_per_gas(&self) -> Option<u128> {
699        match self {
700            Self::Legacy(tx) => tx.tx().max_priority_fee_per_gas(),
701            Self::Eip2930(tx) => tx.tx().max_priority_fee_per_gas(),
702            Self::Eip1559(tx) => tx.tx().max_priority_fee_per_gas(),
703            Self::Eip4844(tx) => tx.tx().max_priority_fee_per_gas(),
704            Self::Eip7702(tx) => tx.tx().max_priority_fee_per_gas(),
705        }
706    }
707
708    #[inline]
709    fn max_fee_per_blob_gas(&self) -> Option<u128> {
710        match self {
711            Self::Legacy(tx) => tx.tx().max_fee_per_blob_gas(),
712            Self::Eip2930(tx) => tx.tx().max_fee_per_blob_gas(),
713            Self::Eip1559(tx) => tx.tx().max_fee_per_blob_gas(),
714            Self::Eip4844(tx) => tx.tx().max_fee_per_blob_gas(),
715            Self::Eip7702(tx) => tx.tx().max_fee_per_blob_gas(),
716        }
717    }
718
719    #[inline]
720    fn priority_fee_or_price(&self) -> u128 {
721        match self {
722            Self::Legacy(tx) => tx.tx().priority_fee_or_price(),
723            Self::Eip2930(tx) => tx.tx().priority_fee_or_price(),
724            Self::Eip1559(tx) => tx.tx().priority_fee_or_price(),
725            Self::Eip4844(tx) => tx.tx().priority_fee_or_price(),
726            Self::Eip7702(tx) => tx.tx().priority_fee_or_price(),
727        }
728    }
729
730    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
731        match self {
732            Self::Legacy(tx) => tx.tx().effective_gas_price(base_fee),
733            Self::Eip2930(tx) => tx.tx().effective_gas_price(base_fee),
734            Self::Eip1559(tx) => tx.tx().effective_gas_price(base_fee),
735            Self::Eip4844(tx) => tx.tx().effective_gas_price(base_fee),
736            Self::Eip7702(tx) => tx.tx().effective_gas_price(base_fee),
737        }
738    }
739
740    #[inline]
741    fn is_dynamic_fee(&self) -> bool {
742        match self {
743            Self::Legacy(tx) => tx.tx().is_dynamic_fee(),
744            Self::Eip2930(tx) => tx.tx().is_dynamic_fee(),
745            Self::Eip1559(tx) => tx.tx().is_dynamic_fee(),
746            Self::Eip4844(tx) => tx.tx().is_dynamic_fee(),
747            Self::Eip7702(tx) => tx.tx().is_dynamic_fee(),
748        }
749    }
750
751    #[inline]
752    fn kind(&self) -> TxKind {
753        match self {
754            Self::Legacy(tx) => tx.tx().kind(),
755            Self::Eip2930(tx) => tx.tx().kind(),
756            Self::Eip1559(tx) => tx.tx().kind(),
757            Self::Eip4844(tx) => tx.tx().kind(),
758            Self::Eip7702(tx) => tx.tx().kind(),
759        }
760    }
761
762    #[inline]
763    fn is_create(&self) -> bool {
764        match self {
765            Self::Legacy(tx) => tx.tx().is_create(),
766            Self::Eip2930(tx) => tx.tx().is_create(),
767            Self::Eip1559(tx) => tx.tx().is_create(),
768            Self::Eip4844(tx) => tx.tx().is_create(),
769            Self::Eip7702(tx) => tx.tx().is_create(),
770        }
771    }
772
773    #[inline]
774    fn value(&self) -> U256 {
775        match self {
776            Self::Legacy(tx) => tx.tx().value(),
777            Self::Eip2930(tx) => tx.tx().value(),
778            Self::Eip1559(tx) => tx.tx().value(),
779            Self::Eip4844(tx) => tx.tx().value(),
780            Self::Eip7702(tx) => tx.tx().value(),
781        }
782    }
783
784    #[inline]
785    fn input(&self) -> &Bytes {
786        match self {
787            Self::Legacy(tx) => tx.tx().input(),
788            Self::Eip2930(tx) => tx.tx().input(),
789            Self::Eip1559(tx) => tx.tx().input(),
790            Self::Eip4844(tx) => tx.tx().input(),
791            Self::Eip7702(tx) => tx.tx().input(),
792        }
793    }
794
795    #[inline]
796    fn access_list(&self) -> Option<&AccessList> {
797        match self {
798            Self::Legacy(tx) => tx.tx().access_list(),
799            Self::Eip2930(tx) => tx.tx().access_list(),
800            Self::Eip1559(tx) => tx.tx().access_list(),
801            Self::Eip4844(tx) => tx.tx().access_list(),
802            Self::Eip7702(tx) => tx.tx().access_list(),
803        }
804    }
805
806    #[inline]
807    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
808        match self {
809            Self::Legacy(tx) => tx.tx().blob_versioned_hashes(),
810            Self::Eip2930(tx) => tx.tx().blob_versioned_hashes(),
811            Self::Eip1559(tx) => tx.tx().blob_versioned_hashes(),
812            Self::Eip4844(tx) => tx.tx().blob_versioned_hashes(),
813            Self::Eip7702(tx) => tx.tx().blob_versioned_hashes(),
814        }
815    }
816
817    fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> {
818        match self {
819            Self::Legacy(tx) => tx.tx().authorization_list(),
820            Self::Eip2930(tx) => tx.tx().authorization_list(),
821            Self::Eip1559(tx) => tx.tx().authorization_list(),
822            Self::Eip4844(tx) => tx.tx().authorization_list(),
823            Self::Eip7702(tx) => tx.tx().authorization_list(),
824        }
825    }
826}
827
828impl<Eip4844: Typed2718> Typed2718 for EthereumTxEnvelope<Eip4844> {
829    fn ty(&self) -> u8 {
830        match self {
831            Self::Legacy(tx) => tx.tx().ty(),
832            Self::Eip2930(tx) => tx.tx().ty(),
833            Self::Eip1559(tx) => tx.tx().ty(),
834            Self::Eip4844(tx) => tx.tx().ty(),
835            Self::Eip7702(tx) => tx.tx().ty(),
836        }
837    }
838}
839
840#[cfg(feature = "serde")]
841mod serde_from {
842    //! NB: Why do we need this?
843    //!
844    //! Because the tag may be missing, we need an abstraction over tagged (with
845    //! type) and untagged (always legacy). This is [`MaybeTaggedTxEnvelope`].
846    //!
847    //! The tagged variant is [`TaggedTxEnvelope`], which always has a type tag.
848    //!
849    //! We serialize via [`TaggedTxEnvelope`] and deserialize via
850    //! [`MaybeTaggedTxEnvelope`].
851    use crate::{
852        transaction::RlpEcdsaEncodableTx, EthereumTxEnvelope, Signed, TxEip1559, TxEip2930,
853        TxEip7702, TxLegacy,
854    };
855
856    #[derive(Debug, serde::Deserialize)]
857    pub(crate) struct UntaggedLegacy {
858        #[serde(default, rename = "type", deserialize_with = "alloy_serde::reject_if_some")]
859        pub _ty: Option<()>,
860        #[serde(flatten, with = "crate::transaction::signed_legacy_serde")]
861        pub tx: Signed<TxLegacy>,
862    }
863
864    #[derive(Debug)]
865    pub(crate) enum MaybeTaggedTxEnvelope<Eip4844> {
866        Tagged(TaggedTxEnvelope<Eip4844>),
867        Untagged(UntaggedLegacy),
868    }
869
870    // Manually modified derived serde(untagged) to preserve the error of the [`TaggedTxEnvelope`]
871    // attempt. Note: This use private serde API
872    impl<'de, Eip4844> serde::Deserialize<'de> for MaybeTaggedTxEnvelope<Eip4844>
873    where
874        Eip4844: Clone + RlpEcdsaEncodableTx + serde::Serialize + serde::de::DeserializeOwned,
875    {
876        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
877        where
878            D: serde::Deserializer<'de>,
879        {
880            let content = serde::__private::de::Content::deserialize(deserializer)?;
881            let deserializer =
882                serde::__private::de::ContentRefDeserializer::<D::Error>::new(&content);
883
884            let tagged_res =
885                TaggedTxEnvelope::deserialize(deserializer).map(MaybeTaggedTxEnvelope::Tagged);
886
887            if tagged_res.is_ok() {
888                // return tagged if successful
889                return tagged_res;
890            }
891
892            // proceed with untagged legacy
893            if let Ok(val) =
894                UntaggedLegacy::deserialize(deserializer).map(MaybeTaggedTxEnvelope::Untagged)
895            {
896                return Ok(val);
897            }
898
899            // return the original error, which is more useful than the untagged error
900            //  > "data did not match any variant of untagged enum MaybeTaggedTxEnvelope"
901            tagged_res
902        }
903    }
904
905    #[derive(Debug, serde::Serialize, serde::Deserialize)]
906    #[serde(
907        tag = "type",
908        bound = "Eip4844: Clone + RlpEcdsaEncodableTx + serde::Serialize + serde::de::DeserializeOwned"
909    )]
910    pub(crate) enum TaggedTxEnvelope<Eip4844> {
911        #[serde(rename = "0x0", alias = "0x00", with = "crate::transaction::signed_legacy_serde")]
912        Legacy(Signed<TxLegacy>),
913        #[serde(rename = "0x1", alias = "0x01")]
914        Eip2930(Signed<TxEip2930>),
915        #[serde(rename = "0x2", alias = "0x02")]
916        Eip1559(Signed<TxEip1559>),
917        #[serde(rename = "0x3", alias = "0x03")]
918        Eip4844(Signed<Eip4844>),
919        #[serde(rename = "0x4", alias = "0x04")]
920        Eip7702(Signed<TxEip7702>),
921    }
922
923    impl<Eip4844> From<MaybeTaggedTxEnvelope<Eip4844>> for EthereumTxEnvelope<Eip4844> {
924        fn from(value: MaybeTaggedTxEnvelope<Eip4844>) -> Self {
925            match value {
926                MaybeTaggedTxEnvelope::Tagged(tagged) => tagged.into(),
927                MaybeTaggedTxEnvelope::Untagged(UntaggedLegacy { tx, .. }) => Self::Legacy(tx),
928            }
929        }
930    }
931
932    impl<Eip4844> From<TaggedTxEnvelope<Eip4844>> for EthereumTxEnvelope<Eip4844> {
933        fn from(value: TaggedTxEnvelope<Eip4844>) -> Self {
934            match value {
935                TaggedTxEnvelope::Legacy(signed) => Self::Legacy(signed),
936                TaggedTxEnvelope::Eip2930(signed) => Self::Eip2930(signed),
937                TaggedTxEnvelope::Eip1559(signed) => Self::Eip1559(signed),
938                TaggedTxEnvelope::Eip4844(signed) => Self::Eip4844(signed),
939                TaggedTxEnvelope::Eip7702(signed) => Self::Eip7702(signed),
940            }
941        }
942    }
943
944    impl<Eip4844> From<EthereumTxEnvelope<Eip4844>> for TaggedTxEnvelope<Eip4844> {
945        fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
946            match value {
947                EthereumTxEnvelope::Legacy(signed) => Self::Legacy(signed),
948                EthereumTxEnvelope::Eip2930(signed) => Self::Eip2930(signed),
949                EthereumTxEnvelope::Eip1559(signed) => Self::Eip1559(signed),
950                EthereumTxEnvelope::Eip4844(signed) => Self::Eip4844(signed),
951                EthereumTxEnvelope::Eip7702(signed) => Self::Eip7702(signed),
952            }
953        }
954    }
955
956    // <https://github.com/succinctlabs/kona/issues/31>
957    #[test]
958    fn serde_block_tx() {
959        let rpc_tx = r#"{
960      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
961      "blockNumber": "0x6edcde",
962      "transactionIndex": "0x7",
963      "hash": "0x2cb125e083d6d2631e3752bd2b3d757bf31bf02bfe21de0ffa46fbb118d28b19",
964      "from": "0x03e5badf3bb1ade1a8f33f94536c827b6531948d",
965      "to": "0x3267e72dc8780a1512fa69da7759ec66f30350e3",
966      "input": "0x62e4c545000000000000000000000000464c8ec100f2f42fb4e42e07e203da2324f9fc6700000000000000000000000003e5badf3bb1ade1a8f33f94536c827b6531948d000000000000000000000000a064bfb5c7e81426647dc20a0d854da1538559dc00000000000000000000000000000000000000000000000000c6f3b40b6c0000",
967      "nonce": "0x2a8",
968      "value": "0x0",
969      "gas": "0x28afd",
970      "gasPrice": "0x23ec5dbc2",
971      "accessList": [],
972      "chainId": "0xaa36a7",
973      "type": "0x0",
974      "v": "0x1546d71",
975      "r": "0x809b9f0a1777e376cd1ee5d2f551035643755edf26ea65b7a00c822a24504962",
976      "s": "0x6a57bb8e21fe85c7e092868ee976fef71edca974d8c452fcf303f9180c764f64"
977    }"#;
978
979        let _ = serde_json::from_str::<MaybeTaggedTxEnvelope<crate::TxEip4844>>(rpc_tx).unwrap();
980    }
981
982    // <https://github.com/succinctlabs/kona/issues/31>
983    #[test]
984    fn serde_block_tx_legacy_chain_id() {
985        let rpc_tx = r#"{
986      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
987      "blockNumber": "0x6edcde",
988      "transactionIndex": "0x8",
989      "hash": "0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6",
990      "from": "0x8b87f0a788cc14b4f0f374da59920f5017ff05de",
991      "to": "0xcb33aa5b38d79e3d9fa8b10aff38aa201399a7e3",
992      "input": "0xaf7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce80000000000000000000000000000000000000000000000000000000000000064",
993      "nonce": "0x2",
994      "value": "0x0",
995      "gas": "0x2dc6c0",
996      "gasPrice": "0x18ef61d0a",
997      "accessList": [],
998      "chainId": "0xaa36a7",
999      "type": "0x0",
1000      "v": "0x1c",
1001      "r": "0x5e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664",
1002      "s": "0x2353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4"
1003    }"#;
1004
1005        let _ = serde_json::from_str::<TaggedTxEnvelope<crate::TxEip4844>>(rpc_tx).unwrap();
1006    }
1007}
1008
1009/// Bincode-compatible [`EthereumTxEnvelope`] serde implementation.
1010#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
1011pub mod serde_bincode_compat {
1012    use crate::{EthereumTypedTransaction, Signed};
1013    use alloc::borrow::Cow;
1014    use alloy_primitives::Signature;
1015    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1016    use serde_with::{DeserializeAs, SerializeAs};
1017
1018    /// Bincode-compatible [`super::EthereumTxEnvelope`] serde implementation.
1019    ///
1020    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
1021    /// ```rust
1022    /// use alloy_consensus::{serde_bincode_compat, EthereumTxEnvelope};
1023    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
1024    /// use serde_with::serde_as;
1025    ///
1026    /// #[serde_as]
1027    /// #[derive(Serialize, Deserialize)]
1028    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
1029    ///     #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_, T>")]
1030    ///     receipt: EthereumTxEnvelope<T>,
1031    /// }
1032    /// ```
1033    #[derive(Debug, Serialize, Deserialize)]
1034    pub struct EthereumTxEnvelope<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
1035        /// Transaction signature
1036        signature: Signature,
1037        /// bincode compatible transaction
1038        transaction:
1039            crate::serde_bincode_compat::transaction::EthereumTypedTransaction<'a, Eip4844>,
1040    }
1041
1042    impl<'a, T: Clone> From<&'a super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'a, T> {
1043        fn from(value: &'a super::EthereumTxEnvelope<T>) -> Self {
1044            match value {
1045                super::EthereumTxEnvelope::Legacy(tx) => Self {
1046                    signature: *tx.signature(),
1047                    transaction:
1048                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Legacy(
1049                            tx.tx().into(),
1050                        ),
1051                },
1052                super::EthereumTxEnvelope::Eip2930(tx) => Self {
1053                    signature: *tx.signature(),
1054                    transaction:
1055                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip2930(
1056                            tx.tx().into(),
1057                        ),
1058                },
1059                super::EthereumTxEnvelope::Eip1559(tx) => Self {
1060                    signature: *tx.signature(),
1061                    transaction:
1062                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip1559(
1063                            tx.tx().into(),
1064                        ),
1065                },
1066                super::EthereumTxEnvelope::Eip4844(tx) => Self {
1067                    signature: *tx.signature(),
1068                    transaction:
1069                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip4844(
1070                            Cow::Borrowed(tx.tx()),
1071                        ),
1072                },
1073                super::EthereumTxEnvelope::Eip7702(tx) => Self {
1074                    signature: *tx.signature(),
1075                    transaction:
1076                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip7702(
1077                            tx.tx().into(),
1078                        ),
1079                },
1080            }
1081        }
1082    }
1083
1084    impl<'a, T: Clone> From<EthereumTxEnvelope<'a, T>> for super::EthereumTxEnvelope<T> {
1085        fn from(value: EthereumTxEnvelope<'a, T>) -> Self {
1086            let EthereumTxEnvelope { signature, transaction } = value;
1087            let transaction: crate::transaction::typed::EthereumTypedTransaction<T> =
1088                transaction.into();
1089            match transaction {
1090                EthereumTypedTransaction::Legacy(tx) => Signed::new_unhashed(tx, signature).into(),
1091                EthereumTypedTransaction::Eip2930(tx) => Signed::new_unhashed(tx, signature).into(),
1092                EthereumTypedTransaction::Eip1559(tx) => Signed::new_unhashed(tx, signature).into(),
1093                EthereumTypedTransaction::Eip4844(tx) => {
1094                    Self::Eip4844(Signed::new_unhashed(tx, signature))
1095                }
1096                EthereumTypedTransaction::Eip7702(tx) => Signed::new_unhashed(tx, signature).into(),
1097            }
1098        }
1099    }
1100
1101    impl<T: Serialize + Clone> SerializeAs<super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'_, T> {
1102        fn serialize_as<S>(
1103            source: &super::EthereumTxEnvelope<T>,
1104            serializer: S,
1105        ) -> Result<S::Ok, S::Error>
1106        where
1107            S: Serializer,
1108        {
1109            EthereumTxEnvelope::<'_, T>::from(source).serialize(serializer)
1110        }
1111    }
1112
1113    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTxEnvelope<T>>
1114        for EthereumTxEnvelope<'de, T>
1115    {
1116        fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumTxEnvelope<T>, D::Error>
1117        where
1118            D: Deserializer<'de>,
1119        {
1120            EthereumTxEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
1121        }
1122    }
1123
1124    #[cfg(test)]
1125    mod tests {
1126        use super::super::{serde_bincode_compat, EthereumTxEnvelope};
1127        use crate::TxEip4844;
1128        use arbitrary::Arbitrary;
1129        use bincode::config;
1130        use rand::Rng;
1131        use serde::{Deserialize, Serialize};
1132        use serde_with::serde_as;
1133
1134        #[test]
1135        fn test_typed_tx_envelope_bincode_roundtrip() {
1136            #[serde_as]
1137            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
1138            struct Data {
1139                #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_>")]
1140                transaction: EthereumTxEnvelope<TxEip4844>,
1141            }
1142
1143            let mut bytes = [0u8; 1024];
1144            rand::thread_rng().fill(bytes.as_mut_slice());
1145            let data = Data {
1146                transaction: EthereumTxEnvelope::arbitrary(&mut arbitrary::Unstructured::new(
1147                    &bytes,
1148                ))
1149                .unwrap(),
1150            };
1151
1152            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
1153            let (decoded, _) =
1154                bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
1155            assert_eq!(decoded, data);
1156        }
1157    }
1158}
1159
1160#[cfg(test)]
1161mod tests {
1162    use super::*;
1163    use crate::{
1164        transaction::{recovered::SignerRecoverable, SignableTransaction},
1165        TxEip4844, TxEip4844WithSidecar,
1166    };
1167    use alloc::vec::Vec;
1168    use alloy_eips::{
1169        eip2930::{AccessList, AccessListItem},
1170        eip4844::BlobTransactionSidecar,
1171        eip7702::Authorization,
1172    };
1173    #[allow(unused_imports)]
1174    use alloy_primitives::{b256, Bytes, TxKind};
1175    use alloy_primitives::{hex, Address, Signature, U256};
1176    use std::{fs, path::PathBuf, str::FromStr, vec};
1177
1178    #[test]
1179    #[cfg(feature = "k256")]
1180    // Test vector from https://etherscan.io/tx/0xce4dc6d7a7549a98ee3b071b67e970879ff51b5b95d1c340bacd80fa1e1aab31
1181    fn test_decode_live_1559_tx() {
1182        use alloy_primitives::address;
1183
1184        let raw_tx = alloy_primitives::hex::decode("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8").unwrap();
1185        let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
1186
1187        assert_eq!(res.tx_type(), TxType::Eip1559);
1188
1189        let tx = match res {
1190            TxEnvelope::Eip1559(tx) => tx,
1191            _ => unreachable!(),
1192        };
1193
1194        assert_eq!(tx.tx().to, TxKind::Call(address!("D9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
1195        let from = tx.recover_signer().unwrap();
1196        assert_eq!(from, address!("001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
1197    }
1198
1199    #[test]
1200    fn test_is_replay_protected_v() {
1201        let sig = Signature::test_signature();
1202        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
1203            TxLegacy::default(),
1204            sig,
1205            Default::default(),
1206        ))
1207        .is_replay_protected());
1208        let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565");
1209        let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1");
1210        let v = false;
1211        let valid_sig = Signature::from_scalars_and_parity(r, s, v);
1212        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
1213            TxLegacy::default(),
1214            valid_sig,
1215            Default::default(),
1216        ))
1217        .is_replay_protected());
1218        assert!(&TxEnvelope::Eip2930(Signed::new_unchecked(
1219            TxEip2930::default(),
1220            sig,
1221            Default::default(),
1222        ))
1223        .is_replay_protected());
1224        assert!(&TxEnvelope::Eip1559(Signed::new_unchecked(
1225            TxEip1559::default(),
1226            sig,
1227            Default::default(),
1228        ))
1229        .is_replay_protected());
1230        assert!(&TxEnvelope::Eip4844(Signed::new_unchecked(
1231            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1232            sig,
1233            Default::default(),
1234        ))
1235        .is_replay_protected());
1236        assert!(&TxEnvelope::Eip7702(Signed::new_unchecked(
1237            TxEip7702::default(),
1238            sig,
1239            Default::default(),
1240        ))
1241        .is_replay_protected());
1242    }
1243
1244    #[test]
1245    #[cfg(feature = "k256")]
1246    // Test vector from https://etherscan.io/tx/0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4
1247    fn test_decode_live_legacy_tx() {
1248        use alloy_primitives::address;
1249
1250        let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
1251        let res = TxEnvelope::decode_2718(&mut raw_tx.as_ref()).unwrap();
1252        assert_eq!(res.tx_type(), TxType::Legacy);
1253
1254        let tx = match res {
1255            TxEnvelope::Legacy(tx) => tx,
1256            _ => unreachable!(),
1257        };
1258
1259        assert_eq!(tx.tx().chain_id(), Some(1));
1260
1261        assert_eq!(tx.tx().to, TxKind::Call(address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D")));
1262        assert_eq!(
1263            tx.hash().to_string(),
1264            "0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4"
1265        );
1266        let from = tx.recover_signer().unwrap();
1267        assert_eq!(from, address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
1268    }
1269
1270    #[test]
1271    #[cfg(feature = "k256")]
1272    // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1273    // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1274    fn test_decode_live_4844_tx() {
1275        use crate::Transaction;
1276        use alloy_primitives::{address, b256};
1277
1278        // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
1279        let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
1280
1281        let res = TxEnvelope::decode_2718(&mut raw_tx.as_slice()).unwrap();
1282        assert_eq!(res.tx_type(), TxType::Eip4844);
1283
1284        let tx = match res {
1285            TxEnvelope::Eip4844(tx) => tx,
1286            _ => unreachable!(),
1287        };
1288
1289        assert_eq!(
1290            tx.tx().kind(),
1291            TxKind::Call(address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064"))
1292        );
1293
1294        // Assert this is the correct variant of the EIP-4844 enum, which only contains the tx.
1295        assert!(matches!(tx.tx(), TxEip4844Variant::TxEip4844(_)));
1296
1297        assert_eq!(
1298            tx.tx().tx().blob_versioned_hashes,
1299            vec![
1300                b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
1301                b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
1302                b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
1303                b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
1304                b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
1305            ]
1306        );
1307
1308        let from = tx.recover_signer().unwrap();
1309        assert_eq!(from, address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
1310    }
1311
1312    fn test_encode_decode_roundtrip<T: SignableTransaction<Signature>>(
1313        tx: T,
1314        signature: Option<Signature>,
1315    ) where
1316        Signed<T>: Into<TxEnvelope>,
1317    {
1318        let signature = signature.unwrap_or_else(Signature::test_signature);
1319        let tx_signed = tx.into_signed(signature);
1320        let tx_envelope: TxEnvelope = tx_signed.into();
1321        let encoded = tx_envelope.encoded_2718();
1322        let mut slice = encoded.as_slice();
1323        let decoded = TxEnvelope::decode_2718(&mut slice).unwrap();
1324        assert_eq!(encoded.len(), tx_envelope.encode_2718_len());
1325        assert_eq!(decoded, tx_envelope);
1326        assert_eq!(slice.len(), 0);
1327    }
1328
1329    #[test]
1330    fn test_encode_decode_legacy() {
1331        let tx = TxLegacy {
1332            chain_id: None,
1333            nonce: 2,
1334            gas_limit: 1000000,
1335            gas_price: 10000000000,
1336            to: Address::left_padding_from(&[6]).into(),
1337            value: U256::from(7_u64),
1338            ..Default::default()
1339        };
1340        test_encode_decode_roundtrip(tx, Some(Signature::test_signature().with_parity(true)));
1341    }
1342
1343    #[test]
1344    fn test_encode_decode_eip1559() {
1345        let tx = TxEip1559 {
1346            chain_id: 1u64,
1347            nonce: 2,
1348            max_fee_per_gas: 3,
1349            max_priority_fee_per_gas: 4,
1350            gas_limit: 5,
1351            to: Address::left_padding_from(&[6]).into(),
1352            value: U256::from(7_u64),
1353            input: vec![8].into(),
1354            access_list: Default::default(),
1355        };
1356        test_encode_decode_roundtrip(tx, None);
1357    }
1358
1359    #[test]
1360    fn test_encode_decode_eip1559_parity_eip155() {
1361        let tx = TxEip1559 {
1362            chain_id: 1u64,
1363            nonce: 2,
1364            max_fee_per_gas: 3,
1365            max_priority_fee_per_gas: 4,
1366            gas_limit: 5,
1367            to: Address::left_padding_from(&[6]).into(),
1368            value: U256::from(7_u64),
1369            input: vec![8].into(),
1370            access_list: Default::default(),
1371        };
1372        let signature = Signature::test_signature().with_parity(true);
1373
1374        test_encode_decode_roundtrip(tx, Some(signature));
1375    }
1376
1377    #[test]
1378    fn test_encode_decode_eip2930_parity_eip155() {
1379        let tx = TxEip2930 {
1380            chain_id: 1u64,
1381            nonce: 2,
1382            gas_price: 3,
1383            gas_limit: 4,
1384            to: Address::left_padding_from(&[5]).into(),
1385            value: U256::from(6_u64),
1386            input: vec![7].into(),
1387            access_list: Default::default(),
1388        };
1389        let signature = Signature::test_signature().with_parity(true);
1390        test_encode_decode_roundtrip(tx, Some(signature));
1391    }
1392
1393    #[test]
1394    fn test_encode_decode_eip4844_parity_eip155() {
1395        let tx = TxEip4844 {
1396            chain_id: 1,
1397            nonce: 100,
1398            max_fee_per_gas: 50_000_000_000,
1399            max_priority_fee_per_gas: 1_000_000_000_000,
1400            gas_limit: 1_000_000,
1401            to: Address::random(),
1402            value: U256::from(10e18),
1403            input: Bytes::new(),
1404            access_list: AccessList(vec![AccessListItem {
1405                address: Address::random(),
1406                storage_keys: vec![B256::random()],
1407            }]),
1408            blob_versioned_hashes: vec![B256::random()],
1409            max_fee_per_blob_gas: 0,
1410        };
1411        let signature = Signature::test_signature().with_parity(true);
1412        test_encode_decode_roundtrip(tx, Some(signature));
1413    }
1414
1415    #[test]
1416    fn test_encode_decode_eip4844_sidecar_parity_eip155() {
1417        let tx = TxEip4844 {
1418            chain_id: 1,
1419            nonce: 100,
1420            max_fee_per_gas: 50_000_000_000,
1421            max_priority_fee_per_gas: 1_000_000_000_000,
1422            gas_limit: 1_000_000,
1423            to: Address::random(),
1424            value: U256::from(10e18),
1425            input: Bytes::new(),
1426            access_list: AccessList(vec![AccessListItem {
1427                address: Address::random(),
1428                storage_keys: vec![B256::random()],
1429            }]),
1430            blob_versioned_hashes: vec![B256::random()],
1431            max_fee_per_blob_gas: 0,
1432        };
1433        let sidecar = BlobTransactionSidecar {
1434            blobs: vec![[2; 131072].into()],
1435            commitments: vec![[3; 48].into()],
1436            proofs: vec![[4; 48].into()],
1437        };
1438        let tx = TxEip4844WithSidecar { tx, sidecar };
1439        let signature = Signature::test_signature().with_parity(true);
1440
1441        let tx_signed = tx.into_signed(signature);
1442        let tx_envelope: TxEnvelope = tx_signed.into();
1443
1444        let mut out = Vec::new();
1445        tx_envelope.network_encode(&mut out);
1446        let mut slice = out.as_slice();
1447        let decoded = TxEnvelope::network_decode(&mut slice).unwrap();
1448        assert_eq!(slice.len(), 0);
1449        assert_eq!(out.len(), tx_envelope.network_len());
1450        assert_eq!(decoded, tx_envelope);
1451    }
1452
1453    #[test]
1454    fn test_encode_decode_eip4844_variant_parity_eip155() {
1455        let tx = TxEip4844 {
1456            chain_id: 1,
1457            nonce: 100,
1458            max_fee_per_gas: 50_000_000_000,
1459            max_priority_fee_per_gas: 1_000_000_000_000,
1460            gas_limit: 1_000_000,
1461            to: Address::random(),
1462            value: U256::from(10e18),
1463            input: Bytes::new(),
1464            access_list: AccessList(vec![AccessListItem {
1465                address: Address::random(),
1466                storage_keys: vec![B256::random()],
1467            }]),
1468            blob_versioned_hashes: vec![B256::random()],
1469            max_fee_per_blob_gas: 0,
1470        };
1471        let tx = TxEip4844Variant::TxEip4844(tx);
1472        let signature = Signature::test_signature().with_parity(true);
1473        test_encode_decode_roundtrip(tx, Some(signature));
1474    }
1475
1476    #[test]
1477    fn test_encode_decode_eip2930() {
1478        let tx = TxEip2930 {
1479            chain_id: 1u64,
1480            nonce: 2,
1481            gas_price: 3,
1482            gas_limit: 4,
1483            to: Address::left_padding_from(&[5]).into(),
1484            value: U256::from(6_u64),
1485            input: vec![7].into(),
1486            access_list: AccessList(vec![AccessListItem {
1487                address: Address::left_padding_from(&[8]),
1488                storage_keys: vec![B256::left_padding_from(&[9])],
1489            }]),
1490        };
1491        test_encode_decode_roundtrip(tx, None);
1492    }
1493
1494    #[test]
1495    fn test_encode_decode_eip7702() {
1496        let tx = TxEip7702 {
1497            chain_id: 1u64,
1498            nonce: 2,
1499            gas_limit: 3,
1500            max_fee_per_gas: 4,
1501            max_priority_fee_per_gas: 5,
1502            to: Address::left_padding_from(&[5]),
1503            value: U256::from(6_u64),
1504            input: vec![7].into(),
1505            access_list: AccessList(vec![AccessListItem {
1506                address: Address::left_padding_from(&[8]),
1507                storage_keys: vec![B256::left_padding_from(&[9])],
1508            }]),
1509            authorization_list: vec![(Authorization {
1510                chain_id: U256::from(1),
1511                address: Address::left_padding_from(&[10]),
1512                nonce: 1u64,
1513            })
1514            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1515        };
1516        test_encode_decode_roundtrip(tx, None);
1517    }
1518
1519    #[test]
1520    fn test_encode_decode_transaction_list() {
1521        let signature = Signature::test_signature();
1522        let tx = TxEnvelope::Eip1559(
1523            TxEip1559 {
1524                chain_id: 1u64,
1525                nonce: 2,
1526                max_fee_per_gas: 3,
1527                max_priority_fee_per_gas: 4,
1528                gas_limit: 5,
1529                to: Address::left_padding_from(&[6]).into(),
1530                value: U256::from(7_u64),
1531                input: vec![8].into(),
1532                access_list: Default::default(),
1533            }
1534            .into_signed(signature),
1535        );
1536        let transactions = vec![tx.clone(), tx];
1537        let encoded = alloy_rlp::encode(&transactions);
1538        let decoded = Vec::<TxEnvelope>::decode(&mut &encoded[..]).unwrap();
1539        assert_eq!(transactions, decoded);
1540    }
1541
1542    #[test]
1543    fn decode_encode_known_rpc_transaction() {
1544        // test data pulled from hive test that sends blob transactions
1545        let network_data_path =
1546            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/rpc_blob_transaction.rlp");
1547        let data = fs::read_to_string(network_data_path).expect("Unable to read file");
1548        let hex_data = hex::decode(data.trim()).unwrap();
1549
1550        let tx: TxEnvelope = TxEnvelope::decode_2718(&mut hex_data.as_slice()).unwrap();
1551        let encoded = tx.encoded_2718();
1552        assert_eq!(encoded, hex_data);
1553        assert_eq!(tx.encode_2718_len(), hex_data.len());
1554    }
1555
1556    #[cfg(feature = "serde")]
1557    fn test_serde_roundtrip<T: SignableTransaction<Signature>>(tx: T)
1558    where
1559        Signed<T>: Into<TxEnvelope>,
1560    {
1561        let signature = Signature::test_signature();
1562        let tx_envelope: TxEnvelope = tx.into_signed(signature).into();
1563
1564        let serialized = serde_json::to_string(&tx_envelope).unwrap();
1565
1566        let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap();
1567
1568        assert_eq!(tx_envelope, deserialized);
1569    }
1570
1571    #[test]
1572    #[cfg(feature = "serde")]
1573    fn test_serde_roundtrip_legacy() {
1574        let tx = TxLegacy {
1575            chain_id: Some(1),
1576            nonce: 100,
1577            gas_price: 3_000_000_000,
1578            gas_limit: 50_000,
1579            to: Address::default().into(),
1580            value: U256::from(10e18),
1581            input: Bytes::new(),
1582        };
1583        test_serde_roundtrip(tx);
1584    }
1585
1586    #[test]
1587    #[cfg(feature = "serde")]
1588    fn test_serde_roundtrip_eip1559() {
1589        let tx = TxEip1559 {
1590            chain_id: 1,
1591            nonce: 100,
1592            max_fee_per_gas: 50_000_000_000,
1593            max_priority_fee_per_gas: 1_000_000_000_000,
1594            gas_limit: 1_000_000,
1595            to: TxKind::Create,
1596            value: U256::from(10e18),
1597            input: Bytes::new(),
1598            access_list: AccessList(vec![AccessListItem {
1599                address: Address::random(),
1600                storage_keys: vec![B256::random()],
1601            }]),
1602        };
1603        test_serde_roundtrip(tx);
1604    }
1605
1606    #[test]
1607    #[cfg(feature = "serde")]
1608    fn test_serde_roundtrip_eip2930() {
1609        let tx = TxEip2930 {
1610            chain_id: u64::MAX,
1611            nonce: u64::MAX,
1612            gas_price: u128::MAX,
1613            gas_limit: u64::MAX,
1614            to: Address::random().into(),
1615            value: U256::MAX,
1616            input: Bytes::new(),
1617            access_list: Default::default(),
1618        };
1619        test_serde_roundtrip(tx);
1620    }
1621
1622    #[test]
1623    #[cfg(feature = "serde")]
1624    fn test_serde_roundtrip_eip4844() {
1625        let tx = TxEip4844Variant::TxEip4844(TxEip4844 {
1626            chain_id: 1,
1627            nonce: 100,
1628            max_fee_per_gas: 50_000_000_000,
1629            max_priority_fee_per_gas: 1_000_000_000_000,
1630            gas_limit: 1_000_000,
1631            to: Address::random(),
1632            value: U256::from(10e18),
1633            input: Bytes::new(),
1634            access_list: AccessList(vec![AccessListItem {
1635                address: Address::random(),
1636                storage_keys: vec![B256::random()],
1637            }]),
1638            blob_versioned_hashes: vec![B256::random()],
1639            max_fee_per_blob_gas: 0,
1640        });
1641        test_serde_roundtrip(tx);
1642
1643        let tx = TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar {
1644            tx: TxEip4844 {
1645                chain_id: 1,
1646                nonce: 100,
1647                max_fee_per_gas: 50_000_000_000,
1648                max_priority_fee_per_gas: 1_000_000_000_000,
1649                gas_limit: 1_000_000,
1650                to: Address::random(),
1651                value: U256::from(10e18),
1652                input: Bytes::new(),
1653                access_list: AccessList(vec![AccessListItem {
1654                    address: Address::random(),
1655                    storage_keys: vec![B256::random()],
1656                }]),
1657                blob_versioned_hashes: vec![B256::random()],
1658                max_fee_per_blob_gas: 0,
1659            },
1660            sidecar: Default::default(),
1661        });
1662        test_serde_roundtrip(tx);
1663    }
1664
1665    #[test]
1666    #[cfg(feature = "serde")]
1667    fn test_serde_roundtrip_eip7702() {
1668        let tx = TxEip7702 {
1669            chain_id: u64::MAX,
1670            nonce: u64::MAX,
1671            gas_limit: u64::MAX,
1672            max_fee_per_gas: u128::MAX,
1673            max_priority_fee_per_gas: u128::MAX,
1674            to: Address::random(),
1675            value: U256::MAX,
1676            input: Bytes::new(),
1677            access_list: AccessList(vec![AccessListItem {
1678                address: Address::random(),
1679                storage_keys: vec![B256::random()],
1680            }]),
1681            authorization_list: vec![(Authorization {
1682                chain_id: U256::from(1),
1683                address: Address::left_padding_from(&[1]),
1684                nonce: 1u64,
1685            })
1686            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1687        };
1688        test_serde_roundtrip(tx);
1689    }
1690
1691    #[test]
1692    #[cfg(feature = "serde")]
1693    fn serde_tx_from_contract_call() {
1694        let rpc_tx = r#"{"hash":"0x018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f","nonce":"0x1","blockHash":"0x3ca295f1dcaf8ac073c543dc0eccf18859f411206df181731e374e9917252931","blockNumber":"0x2","transactionIndex":"0x0","from":"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266","to":"0x5fbdb2315678afecb367f032d93f642f64180aa3","value":"0x0","gasPrice":"0x3a29f0f8","gas":"0x1c9c380","maxFeePerGas":"0xba43b7400","maxPriorityFeePerGas":"0x5f5e100","input":"0xd09de08a","r":"0xd309309a59a49021281cb6bb41d164c96eab4e50f0c1bd24c03ca336e7bc2bb7","s":"0x28a7f089143d0a1355ebeb2a1b9f0e5ad9eca4303021c1400d61bc23c9ac5319","v":"0x0","yParity":"0x0","chainId":"0x7a69","accessList":[],"type":"0x2"}"#;
1695
1696        let te = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1697
1698        assert_eq!(
1699            *te.tx_hash(),
1700            alloy_primitives::b256!(
1701                "018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f"
1702            )
1703        );
1704    }
1705
1706    #[test]
1707    #[cfg(feature = "k256")]
1708    fn test_arbitrary_envelope() {
1709        use arbitrary::Arbitrary;
1710        let mut unstructured = arbitrary::Unstructured::new(b"arbitrary tx envelope");
1711        let tx = TxEnvelope::arbitrary(&mut unstructured).unwrap();
1712
1713        assert!(tx.recover_signer().is_ok());
1714    }
1715
1716    #[test]
1717    #[cfg(feature = "serde")]
1718    fn test_serde_untagged_legacy() {
1719        let data = r#"{
1720            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1721            "input": "0x",
1722            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1723            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1724            "v": "0x1c",
1725            "gas": "0x15f90",
1726            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1727            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1728            "value": "0xf606682badd7800",
1729            "nonce": "0x11f398",
1730            "gasPrice": "0x4a817c800"
1731        }"#;
1732
1733        let tx: TxEnvelope = serde_json::from_str(data).unwrap();
1734
1735        assert!(matches!(tx, TxEnvelope::Legacy(_)));
1736
1737        let data_with_wrong_type = r#"{
1738            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1739            "input": "0x",
1740            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1741            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1742            "v": "0x1c",
1743            "gas": "0x15f90",
1744            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1745            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1746            "value": "0xf606682badd7800",
1747            "nonce": "0x11f398",
1748            "gasPrice": "0x4a817c800",
1749            "type": "0x12"
1750        }"#;
1751
1752        assert!(serde_json::from_str::<TxEnvelope>(data_with_wrong_type).is_err());
1753    }
1754
1755    #[test]
1756    fn test_tx_type_try_from_u8() {
1757        assert_eq!(TxType::try_from(0u8).unwrap(), TxType::Legacy);
1758        assert_eq!(TxType::try_from(1u8).unwrap(), TxType::Eip2930);
1759        assert_eq!(TxType::try_from(2u8).unwrap(), TxType::Eip1559);
1760        assert_eq!(TxType::try_from(3u8).unwrap(), TxType::Eip4844);
1761        assert_eq!(TxType::try_from(4u8).unwrap(), TxType::Eip7702);
1762        assert!(TxType::try_from(5u8).is_err()); // Invalid case
1763    }
1764
1765    #[test]
1766    fn test_tx_type_try_from_u64() {
1767        assert_eq!(TxType::try_from(0u64).unwrap(), TxType::Legacy);
1768        assert_eq!(TxType::try_from(1u64).unwrap(), TxType::Eip2930);
1769        assert_eq!(TxType::try_from(2u64).unwrap(), TxType::Eip1559);
1770        assert_eq!(TxType::try_from(3u64).unwrap(), TxType::Eip4844);
1771        assert_eq!(TxType::try_from(4u64).unwrap(), TxType::Eip7702);
1772        assert!(TxType::try_from(10u64).is_err()); // Invalid case
1773    }
1774
1775    #[test]
1776    fn test_tx_type_from_conversions() {
1777        let legacy_tx = Signed::new_unchecked(
1778            TxLegacy::default(),
1779            Signature::test_signature(),
1780            Default::default(),
1781        );
1782        let eip2930_tx = Signed::new_unchecked(
1783            TxEip2930::default(),
1784            Signature::test_signature(),
1785            Default::default(),
1786        );
1787        let eip1559_tx = Signed::new_unchecked(
1788            TxEip1559::default(),
1789            Signature::test_signature(),
1790            Default::default(),
1791        );
1792        let eip4844_variant = Signed::new_unchecked(
1793            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1794            Signature::test_signature(),
1795            Default::default(),
1796        );
1797        let eip7702_tx = Signed::new_unchecked(
1798            TxEip7702::default(),
1799            Signature::test_signature(),
1800            Default::default(),
1801        );
1802
1803        assert!(matches!(TxEnvelope::from(legacy_tx), TxEnvelope::Legacy(_)));
1804        assert!(matches!(TxEnvelope::from(eip2930_tx), TxEnvelope::Eip2930(_)));
1805        assert!(matches!(TxEnvelope::from(eip1559_tx), TxEnvelope::Eip1559(_)));
1806        assert!(matches!(TxEnvelope::from(eip4844_variant), TxEnvelope::Eip4844(_)));
1807        assert!(matches!(TxEnvelope::from(eip7702_tx), TxEnvelope::Eip7702(_)));
1808    }
1809
1810    #[test]
1811    fn test_tx_type_is_methods() {
1812        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1813            TxLegacy::default(),
1814            Signature::test_signature(),
1815            Default::default(),
1816        ));
1817        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1818            TxEip2930::default(),
1819            Signature::test_signature(),
1820            Default::default(),
1821        ));
1822        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1823            TxEip1559::default(),
1824            Signature::test_signature(),
1825            Default::default(),
1826        ));
1827        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1828            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1829            Signature::test_signature(),
1830            Default::default(),
1831        ));
1832        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1833            TxEip7702::default(),
1834            Signature::test_signature(),
1835            Default::default(),
1836        ));
1837
1838        assert!(legacy_tx.is_legacy());
1839        assert!(!legacy_tx.is_eip2930());
1840        assert!(!legacy_tx.is_eip1559());
1841        assert!(!legacy_tx.is_eip4844());
1842        assert!(!legacy_tx.is_eip7702());
1843
1844        assert!(eip2930_tx.is_eip2930());
1845        assert!(!eip2930_tx.is_legacy());
1846        assert!(!eip2930_tx.is_eip1559());
1847        assert!(!eip2930_tx.is_eip4844());
1848        assert!(!eip2930_tx.is_eip7702());
1849
1850        assert!(eip1559_tx.is_eip1559());
1851        assert!(!eip1559_tx.is_legacy());
1852        assert!(!eip1559_tx.is_eip2930());
1853        assert!(!eip1559_tx.is_eip4844());
1854        assert!(!eip1559_tx.is_eip7702());
1855
1856        assert!(eip4844_tx.is_eip4844());
1857        assert!(!eip4844_tx.is_legacy());
1858        assert!(!eip4844_tx.is_eip2930());
1859        assert!(!eip4844_tx.is_eip1559());
1860        assert!(!eip4844_tx.is_eip7702());
1861
1862        assert!(eip7702_tx.is_eip7702());
1863        assert!(!eip7702_tx.is_legacy());
1864        assert!(!eip7702_tx.is_eip2930());
1865        assert!(!eip7702_tx.is_eip1559());
1866        assert!(!eip7702_tx.is_eip4844());
1867    }
1868
1869    #[test]
1870    fn test_tx_type() {
1871        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1872            TxLegacy::default(),
1873            Signature::test_signature(),
1874            Default::default(),
1875        ));
1876        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1877            TxEip2930::default(),
1878            Signature::test_signature(),
1879            Default::default(),
1880        ));
1881        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1882            TxEip1559::default(),
1883            Signature::test_signature(),
1884            Default::default(),
1885        ));
1886        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1887            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1888            Signature::test_signature(),
1889            Default::default(),
1890        ));
1891        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1892            TxEip7702::default(),
1893            Signature::test_signature(),
1894            Default::default(),
1895        ));
1896
1897        assert_eq!(legacy_tx.tx_type(), TxType::Legacy);
1898        assert_eq!(eip2930_tx.tx_type(), TxType::Eip2930);
1899        assert_eq!(eip1559_tx.tx_type(), TxType::Eip1559);
1900        assert_eq!(eip4844_tx.tx_type(), TxType::Eip4844);
1901        assert_eq!(eip7702_tx.tx_type(), TxType::Eip7702);
1902    }
1903
1904    // <https://sepolia.etherscan.io/getRawTx?tx=0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6>
1905    #[test]
1906    fn decode_raw_legacy() {
1907        let raw = hex!("f8aa0285018ef61d0a832dc6c094cb33aa5b38d79e3d9fa8b10aff38aa201399a7e380b844af7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce800000000000000000000000000000000000000000000000000000000000000641ca05e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664a02353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4");
1908        let tx = TxEnvelope::decode_2718(&mut raw.as_ref()).unwrap();
1909        assert!(tx.chain_id().is_none());
1910    }
1911
1912    #[test]
1913    fn can_deserialize_system_transaction_with_zero_signature_envelope() {
1914        let raw_tx = r#"{
1915            "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
1916            "blockNumber": "0x45a59bb",
1917            "from": "0x0000000000000000000000000000000000000000",
1918            "gas": "0x1e8480",
1919            "gasPrice": "0x0",
1920            "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
1921            "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
1922            "nonce": "0x469f7",
1923            "to": "0x4200000000000000000000000000000000000007",
1924            "transactionIndex": "0x0",
1925            "value": "0x0",
1926            "v": "0x0",
1927            "r": "0x0",
1928            "s": "0x0",
1929            "queueOrigin": "l1",
1930            "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
1931            "l1BlockNumber": "0xfd1a6c",
1932            "l1Timestamp": "0x63e434ff",
1933            "index": "0x45a59ba",
1934            "queueIndex": "0x469f7",
1935            "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
1936        }"#;
1937
1938        let tx = serde_json::from_str::<TxEnvelope>(raw_tx).unwrap();
1939
1940        assert_eq!(tx.signature().r(), U256::ZERO);
1941        assert_eq!(tx.signature().s(), U256::ZERO);
1942        assert!(!tx.signature().v());
1943
1944        assert_eq!(
1945            tx.hash(),
1946            &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
1947            "hash should match the transaction hash"
1948        );
1949    }
1950}