alloy_consensus/transaction/
envelope.rs

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