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