alloy_consensus/transaction/
envelope.rs

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