alloy_consensus/transaction/
envelope.rs

1use super::SignableTransaction;
2use crate::{
3    error::ValueError,
4    transaction::{
5        eip4844::{TxEip4844, TxEip4844Variant},
6        RlpEcdsaEncodableTx, TxHashRef,
7    },
8    EthereumTypedTransaction, Signed, TransactionEnvelope, TxEip1559, TxEip2930,
9    TxEip4844WithSidecar, TxEip7702, TxLegacy,
10};
11use alloy_eips::{eip2718::Encodable2718, eip7594::Encodable7594};
12use alloy_primitives::{Bytes, Signature, B256};
13use core::fmt::Debug;
14
15/// The Ethereum [EIP-2718] Transaction Envelope.
16///
17/// # Note:
18///
19/// This enum distinguishes between tagged and untagged legacy transactions, as
20/// the in-protocol merkle tree may commit to EITHER 0-prefixed or raw.
21/// Therefore we must ensure that encoding returns the precise byte-array that
22/// was decoded, preserving the presence or absence of the `TransactionType`
23/// flag.
24///
25/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
26pub type TxEnvelope = EthereumTxEnvelope<TxEip4844Variant>;
27
28impl<T: Encodable7594> EthereumTxEnvelope<TxEip4844Variant<T>> {
29    /// Attempts to convert the envelope into the pooled variant.
30    ///
31    /// Returns an error if the envelope's variant is incompatible with the pooled format:
32    /// [`crate::TxEip4844`] without the sidecar.
33    pub fn try_into_pooled(
34        self,
35    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
36        match self {
37            Self::Legacy(tx) => Ok(tx.into()),
38            Self::Eip2930(tx) => Ok(tx.into()),
39            Self::Eip1559(tx) => Ok(tx.into()),
40            Self::Eip4844(tx) => EthereumTxEnvelope::try_from(tx).map_err(ValueError::convert),
41            Self::Eip7702(tx) => Ok(tx.into()),
42        }
43    }
44}
45
46impl EthereumTxEnvelope<TxEip4844> {
47    /// Attempts to convert the envelope into the pooled variant.
48    ///
49    /// Returns an error if the envelope's variant is incompatible with the pooled format:
50    /// [`crate::TxEip4844`] without the sidecar.
51    pub fn try_into_pooled<T>(
52        self,
53    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
54        match self {
55            Self::Legacy(tx) => Ok(tx.into()),
56            Self::Eip2930(tx) => Ok(tx.into()),
57            Self::Eip1559(tx) => Ok(tx.into()),
58            Self::Eip4844(tx) => {
59                Err(ValueError::new(tx.into(), "pooled transaction requires 4844 sidecar"))
60            }
61            Self::Eip7702(tx) => Ok(tx.into()),
62        }
63    }
64
65    /// Converts from an EIP-4844 transaction to a [`EthereumTxEnvelope<TxEip4844WithSidecar<T>>`]
66    /// with the given sidecar.
67    ///
68    /// Returns an `Err` containing the original [`EthereumTxEnvelope`] if the transaction is not an
69    /// EIP-4844 variant.
70    pub fn try_into_pooled_eip4844<T>(
71        self,
72        sidecar: T,
73    ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
74        match self {
75            Self::Eip4844(tx) => {
76                Ok(EthereumTxEnvelope::Eip4844(tx.map(|tx| tx.with_sidecar(sidecar))))
77            }
78            this => Err(ValueError::new_static(this, "Expected 4844 transaction")),
79        }
80    }
81}
82
83impl<T> EthereumTxEnvelope<T> {
84    /// Creates a new signed transaction from the given transaction, signature and hash.
85    ///
86    /// Caution: This assumes the given hash is the correct transaction hash.
87    pub fn new_unchecked(
88        transaction: EthereumTypedTransaction<T>,
89        signature: Signature,
90        hash: B256,
91    ) -> Self
92    where
93        T: RlpEcdsaEncodableTx,
94    {
95        Signed::new_unchecked(transaction, signature, hash).into()
96    }
97
98    /// Creates a new signed transaction from the given transaction, signature and hash.
99    ///
100    /// Caution: This assumes the given hash is the correct transaction hash.
101    #[deprecated(note = "Use new_unchecked() instead")]
102    pub fn new(transaction: EthereumTypedTransaction<T>, signature: Signature, hash: B256) -> Self
103    where
104        T: RlpEcdsaEncodableTx,
105    {
106        Self::new_unchecked(transaction, signature, hash)
107    }
108
109    /// Creates a new signed transaction from the given typed transaction and signature without the
110    /// hash.
111    ///
112    /// Note: this only calculates the hash on the first [`EthereumTxEnvelope::hash`] call.
113    pub fn new_unhashed(transaction: EthereumTypedTransaction<T>, signature: Signature) -> Self
114    where
115        T: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
116    {
117        transaction.into_signed(signature).into()
118    }
119
120    /// Consumes the type, removes the signature and returns the transaction.
121    #[inline]
122    pub fn into_typed_transaction(self) -> EthereumTypedTransaction<T>
123    where
124        T: RlpEcdsaEncodableTx,
125    {
126        match self {
127            Self::Legacy(tx) => EthereumTypedTransaction::Legacy(tx.into_parts().0),
128            Self::Eip2930(tx) => EthereumTypedTransaction::Eip2930(tx.into_parts().0),
129            Self::Eip1559(tx) => EthereumTypedTransaction::Eip1559(tx.into_parts().0),
130            Self::Eip4844(tx) => EthereumTypedTransaction::Eip4844(tx.into_parts().0),
131            Self::Eip7702(tx) => EthereumTypedTransaction::Eip7702(tx.into_parts().0),
132        }
133    }
134
135    /// Returns a mutable reference to the transaction's input.
136    #[doc(hidden)]
137    pub fn input_mut(&mut self) -> &mut Bytes
138    where
139        T: AsMut<TxEip4844>,
140    {
141        match self {
142            Self::Eip1559(tx) => &mut tx.tx_mut().input,
143            Self::Eip2930(tx) => &mut tx.tx_mut().input,
144            Self::Legacy(tx) => &mut tx.tx_mut().input,
145            Self::Eip7702(tx) => &mut tx.tx_mut().input,
146            Self::Eip4844(tx) => &mut tx.tx_mut().as_mut().input,
147        }
148    }
149}
150
151/// The Ethereum [EIP-2718] Transaction Envelope.
152///
153/// # Note:
154///
155/// This enum distinguishes between tagged and untagged legacy transactions, as
156/// the in-protocol merkle tree may commit to EITHER 0-prefixed or raw.
157/// Therefore we must ensure that encoding returns the precise byte-array that
158/// was decoded, preserving the presence or absence of the `TransactionType`
159/// flag.
160///
161/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
162#[derive(Clone, Debug, TransactionEnvelope)]
163#[envelope(alloy_consensus = crate, tx_type_name = TxType, arbitrary_cfg(feature = "arbitrary"))]
164#[doc(alias = "TransactionEnvelope")]
165pub enum EthereumTxEnvelope<Eip4844> {
166    /// An untagged [`TxLegacy`].
167    #[envelope(ty = 0)]
168    Legacy(Signed<TxLegacy>),
169    /// A [`TxEip2930`] tagged with type 1.
170    #[envelope(ty = 1)]
171    Eip2930(Signed<TxEip2930>),
172    /// A [`TxEip1559`] tagged with type 2.
173    #[envelope(ty = 2)]
174    Eip1559(Signed<TxEip1559>),
175    /// A TxEip4844 tagged with type 3.
176    /// An EIP-4844 transaction has two network representations:
177    /// 1 - The transaction itself, which is a regular RLP-encoded transaction and used to retrieve
178    /// historical transactions..
179    ///
180    /// 2 - The transaction with a sidecar, which is the form used to
181    /// send transactions to the network.
182    #[envelope(ty = 3)]
183    Eip4844(Signed<Eip4844>),
184    /// A [`TxEip7702`] tagged with type 4.
185    #[envelope(ty = 4)]
186    Eip7702(Signed<TxEip7702>),
187}
188
189impl<T, Eip4844> From<Signed<T>> for EthereumTxEnvelope<Eip4844>
190where
191    EthereumTypedTransaction<Eip4844>: From<T>,
192    T: RlpEcdsaEncodableTx,
193{
194    fn from(v: Signed<T>) -> Self {
195        let (tx, sig, hash) = v.into_parts();
196        let typed = EthereumTypedTransaction::from(tx);
197        match typed {
198            EthereumTypedTransaction::Legacy(tx_legacy) => {
199                let tx = Signed::new_unchecked(tx_legacy, sig, hash);
200                Self::Legacy(tx)
201            }
202            EthereumTypedTransaction::Eip2930(tx_eip2930) => {
203                let tx = Signed::new_unchecked(tx_eip2930, sig, hash);
204                Self::Eip2930(tx)
205            }
206            EthereumTypedTransaction::Eip1559(tx_eip1559) => {
207                let tx = Signed::new_unchecked(tx_eip1559, sig, hash);
208                Self::Eip1559(tx)
209            }
210            EthereumTypedTransaction::Eip4844(tx_eip4844_variant) => {
211                let tx = Signed::new_unchecked(tx_eip4844_variant, sig, hash);
212                Self::Eip4844(tx)
213            }
214            EthereumTypedTransaction::Eip7702(tx_eip7702) => {
215                let tx = Signed::new_unchecked(tx_eip7702, sig, hash);
216                Self::Eip7702(tx)
217            }
218        }
219    }
220}
221
222impl<Eip4844: RlpEcdsaEncodableTx> From<EthereumTxEnvelope<Eip4844>>
223    for Signed<EthereumTypedTransaction<Eip4844>>
224where
225    EthereumTypedTransaction<Eip4844>: From<Eip4844>,
226{
227    fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
228        value.into_signed()
229    }
230}
231
232impl<Eip4844> From<(EthereumTypedTransaction<Eip4844>, Signature)> for EthereumTxEnvelope<Eip4844>
233where
234    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
235{
236    fn from(value: (EthereumTypedTransaction<Eip4844>, Signature)) -> Self {
237        value.0.into_signed(value.1).into()
238    }
239}
240
241impl<T> From<EthereumTxEnvelope<TxEip4844WithSidecar<T>>> for EthereumTxEnvelope<TxEip4844> {
242    fn from(value: EthereumTxEnvelope<TxEip4844WithSidecar<T>>) -> Self {
243        value.map_eip4844(|eip4844| eip4844.into())
244    }
245}
246
247impl<T> From<EthereumTxEnvelope<TxEip4844Variant<T>>> for EthereumTxEnvelope<TxEip4844> {
248    fn from(value: EthereumTxEnvelope<TxEip4844Variant<T>>) -> Self {
249        value.map_eip4844(|eip4844| eip4844.into())
250    }
251}
252
253impl<T> From<EthereumTxEnvelope<TxEip4844>> for EthereumTxEnvelope<TxEip4844Variant<T>> {
254    fn from(value: EthereumTxEnvelope<TxEip4844>) -> Self {
255        value.map_eip4844(|eip4844| eip4844.into())
256    }
257}
258
259impl<Eip4844> EthereumTxEnvelope<Eip4844> {
260    /// Converts the EIP-4844 variant of this transaction with the given closure.
261    ///
262    /// This is intended to convert between the EIP-4844 variants, specifically for stripping away
263    /// non consensus data (blob sidecar data).
264    pub fn map_eip4844<U>(self, f: impl FnMut(Eip4844) -> U) -> EthereumTxEnvelope<U> {
265        match self {
266            Self::Legacy(tx) => EthereumTxEnvelope::Legacy(tx),
267            Self::Eip2930(tx) => EthereumTxEnvelope::Eip2930(tx),
268            Self::Eip1559(tx) => EthereumTxEnvelope::Eip1559(tx),
269            Self::Eip4844(tx) => EthereumTxEnvelope::Eip4844(tx.map(f)),
270            Self::Eip7702(tx) => EthereumTxEnvelope::Eip7702(tx),
271        }
272    }
273
274    /// Return the [`TxType`] of the inner txn.
275    #[doc(alias = "transaction_type")]
276    pub const fn tx_type(&self) -> TxType {
277        match self {
278            Self::Legacy(_) => TxType::Legacy,
279            Self::Eip2930(_) => TxType::Eip2930,
280            Self::Eip1559(_) => TxType::Eip1559,
281            Self::Eip4844(_) => TxType::Eip4844,
282            Self::Eip7702(_) => TxType::Eip7702,
283        }
284    }
285
286    /// Consumes the type into a [`Signed`]
287    pub fn into_signed(self) -> Signed<EthereumTypedTransaction<Eip4844>>
288    where
289        EthereumTypedTransaction<Eip4844>: From<Eip4844>,
290    {
291        match self {
292            Self::Legacy(tx) => tx.convert(),
293            Self::Eip2930(tx) => tx.convert(),
294            Self::Eip1559(tx) => tx.convert(),
295            Self::Eip4844(tx) => tx.convert(),
296            Self::Eip7702(tx) => tx.convert(),
297        }
298    }
299}
300
301impl<Eip4844: RlpEcdsaEncodableTx> EthereumTxEnvelope<Eip4844> {
302    /// Returns true if the transaction is a legacy transaction.
303    #[inline]
304    pub const fn is_legacy(&self) -> bool {
305        matches!(self, Self::Legacy(_))
306    }
307
308    /// Returns true if the transaction is an EIP-2930 transaction.
309    #[inline]
310    pub const fn is_eip2930(&self) -> bool {
311        matches!(self, Self::Eip2930(_))
312    }
313
314    /// Returns true if the transaction is an EIP-1559 transaction.
315    #[inline]
316    pub const fn is_eip1559(&self) -> bool {
317        matches!(self, Self::Eip1559(_))
318    }
319
320    /// Returns true if the transaction is an EIP-4844 transaction.
321    #[inline]
322    pub const fn is_eip4844(&self) -> bool {
323        matches!(self, Self::Eip4844(_))
324    }
325
326    /// Returns true if the transaction is an EIP-7702 transaction.
327    #[inline]
328    pub const fn is_eip7702(&self) -> bool {
329        matches!(self, Self::Eip7702(_))
330    }
331
332    /// Returns true if the transaction is replay protected.
333    ///
334    /// All non-legacy transactions are replay protected, as the chain id is
335    /// included in the transaction body. Legacy transactions are considered
336    /// replay protected if the `v` value is not 27 or 28, according to the
337    /// rules of [EIP-155].
338    ///
339    /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
340    #[inline]
341    pub const fn is_replay_protected(&self) -> bool {
342        match self {
343            Self::Legacy(tx) => tx.tx().chain_id.is_some(),
344            _ => true,
345        }
346    }
347
348    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
349    pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
350        match self {
351            Self::Legacy(tx) => Some(tx),
352            _ => None,
353        }
354    }
355
356    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
357    pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
358        match self {
359            Self::Eip2930(tx) => Some(tx),
360            _ => None,
361        }
362    }
363
364    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
365    pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
366        match self {
367            Self::Eip1559(tx) => Some(tx),
368            _ => None,
369        }
370    }
371
372    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
373    pub const fn as_eip4844(&self) -> Option<&Signed<Eip4844>> {
374        match self {
375            Self::Eip4844(tx) => Some(tx),
376            _ => None,
377        }
378    }
379
380    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
381    pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
382        match self {
383            Self::Eip7702(tx) => Some(tx),
384            _ => None,
385        }
386    }
387
388    /// Calculate the signing hash for the transaction.
389    pub fn signature_hash(&self) -> B256
390    where
391        Eip4844: SignableTransaction<Signature>,
392    {
393        match self {
394            Self::Legacy(tx) => tx.signature_hash(),
395            Self::Eip2930(tx) => tx.signature_hash(),
396            Self::Eip1559(tx) => tx.signature_hash(),
397            Self::Eip4844(tx) => tx.signature_hash(),
398            Self::Eip7702(tx) => tx.signature_hash(),
399        }
400    }
401
402    /// Return the reference to signature.
403    pub const fn signature(&self) -> &Signature {
404        match self {
405            Self::Legacy(tx) => tx.signature(),
406            Self::Eip2930(tx) => tx.signature(),
407            Self::Eip1559(tx) => tx.signature(),
408            Self::Eip4844(tx) => tx.signature(),
409            Self::Eip7702(tx) => tx.signature(),
410        }
411    }
412
413    /// Return the hash of the inner Signed.
414    #[doc(alias = "transaction_hash")]
415    pub fn tx_hash(&self) -> &B256 {
416        match self {
417            Self::Legacy(tx) => tx.hash(),
418            Self::Eip2930(tx) => tx.hash(),
419            Self::Eip1559(tx) => tx.hash(),
420            Self::Eip4844(tx) => tx.hash(),
421            Self::Eip7702(tx) => tx.hash(),
422        }
423    }
424
425    /// Reference to transaction hash. Used to identify transaction.
426    pub fn hash(&self) -> &B256 {
427        match self {
428            Self::Legacy(tx) => tx.hash(),
429            Self::Eip2930(tx) => tx.hash(),
430            Self::Eip1559(tx) => tx.hash(),
431            Self::Eip7702(tx) => tx.hash(),
432            Self::Eip4844(tx) => tx.hash(),
433        }
434    }
435
436    /// Return the length of the inner txn, including type byte length
437    pub fn eip2718_encoded_length(&self) -> usize {
438        match self {
439            Self::Legacy(t) => t.eip2718_encoded_length(),
440            Self::Eip2930(t) => t.eip2718_encoded_length(),
441            Self::Eip1559(t) => t.eip2718_encoded_length(),
442            Self::Eip4844(t) => t.eip2718_encoded_length(),
443            Self::Eip7702(t) => t.eip2718_encoded_length(),
444        }
445    }
446}
447
448impl<Eip4844: RlpEcdsaEncodableTx> TxHashRef for EthereumTxEnvelope<Eip4844> {
449    fn tx_hash(&self) -> &B256 {
450        Self::tx_hash(self)
451    }
452}
453
454#[cfg(any(feature = "secp256k1", feature = "k256"))]
455impl<Eip4844> crate::transaction::SignerRecoverable for EthereumTxEnvelope<Eip4844>
456where
457    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
458{
459    fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
460        match self {
461            Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
462            Self::Eip2930(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
463            Self::Eip1559(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
464            Self::Eip4844(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
465            Self::Eip7702(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
466        }
467    }
468
469    fn recover_signer_unchecked(
470        &self,
471    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
472        match self {
473            Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer_unchecked(tx),
474            Self::Eip2930(tx) => {
475                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
476            }
477            Self::Eip1559(tx) => {
478                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
479            }
480            Self::Eip4844(tx) => {
481                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
482            }
483            Self::Eip7702(tx) => {
484                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
485            }
486        }
487    }
488
489    fn recover_unchecked_with_buf(
490        &self,
491        buf: &mut alloc::vec::Vec<u8>,
492    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
493        match self {
494            Self::Legacy(tx) => {
495                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
496            }
497            Self::Eip2930(tx) => {
498                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
499            }
500            Self::Eip1559(tx) => {
501                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
502            }
503            Self::Eip4844(tx) => {
504                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
505            }
506            Self::Eip7702(tx) => {
507                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
508            }
509        }
510    }
511}
512
513/// Bincode-compatible [`EthereumTxEnvelope`] serde implementation.
514#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
515pub mod serde_bincode_compat {
516    use crate::{EthereumTypedTransaction, Signed};
517    use alloc::borrow::Cow;
518    use alloy_primitives::Signature;
519    use serde::{Deserialize, Deserializer, Serialize, Serializer};
520    use serde_with::{DeserializeAs, SerializeAs};
521
522    /// Bincode-compatible [`super::EthereumTxEnvelope`] serde implementation.
523    ///
524    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
525    /// ```rust
526    /// use alloy_consensus::{serde_bincode_compat, EthereumTxEnvelope};
527    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
528    /// use serde_with::serde_as;
529    ///
530    /// #[serde_as]
531    /// #[derive(Serialize, Deserialize)]
532    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
533    ///     #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_, T>")]
534    ///     receipt: EthereumTxEnvelope<T>,
535    /// }
536    /// ```
537    #[derive(Debug, Serialize, Deserialize)]
538    pub struct EthereumTxEnvelope<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
539        /// Transaction signature
540        signature: Signature,
541        /// bincode compatible transaction
542        transaction:
543            crate::serde_bincode_compat::transaction::EthereumTypedTransaction<'a, Eip4844>,
544    }
545
546    impl<'a, T: Clone> From<&'a super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'a, T> {
547        fn from(value: &'a super::EthereumTxEnvelope<T>) -> Self {
548            match value {
549                super::EthereumTxEnvelope::Legacy(tx) => Self {
550                    signature: *tx.signature(),
551                    transaction:
552                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Legacy(
553                            tx.tx().into(),
554                        ),
555                },
556                super::EthereumTxEnvelope::Eip2930(tx) => Self {
557                    signature: *tx.signature(),
558                    transaction:
559                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip2930(
560                            tx.tx().into(),
561                        ),
562                },
563                super::EthereumTxEnvelope::Eip1559(tx) => Self {
564                    signature: *tx.signature(),
565                    transaction:
566                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip1559(
567                            tx.tx().into(),
568                        ),
569                },
570                super::EthereumTxEnvelope::Eip4844(tx) => Self {
571                    signature: *tx.signature(),
572                    transaction:
573                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip4844(
574                            Cow::Borrowed(tx.tx()),
575                        ),
576                },
577                super::EthereumTxEnvelope::Eip7702(tx) => Self {
578                    signature: *tx.signature(),
579                    transaction:
580                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip7702(
581                            tx.tx().into(),
582                        ),
583                },
584            }
585        }
586    }
587
588    impl<'a, T: Clone> From<EthereumTxEnvelope<'a, T>> for super::EthereumTxEnvelope<T> {
589        fn from(value: EthereumTxEnvelope<'a, T>) -> Self {
590            let EthereumTxEnvelope { signature, transaction } = value;
591            let transaction: crate::transaction::typed::EthereumTypedTransaction<T> =
592                transaction.into();
593            match transaction {
594                EthereumTypedTransaction::Legacy(tx) => Signed::new_unhashed(tx, signature).into(),
595                EthereumTypedTransaction::Eip2930(tx) => Signed::new_unhashed(tx, signature).into(),
596                EthereumTypedTransaction::Eip1559(tx) => Signed::new_unhashed(tx, signature).into(),
597                EthereumTypedTransaction::Eip4844(tx) => {
598                    Self::Eip4844(Signed::new_unhashed(tx, signature))
599                }
600                EthereumTypedTransaction::Eip7702(tx) => Signed::new_unhashed(tx, signature).into(),
601            }
602        }
603    }
604
605    impl<T: Serialize + Clone> SerializeAs<super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'_, T> {
606        fn serialize_as<S>(
607            source: &super::EthereumTxEnvelope<T>,
608            serializer: S,
609        ) -> Result<S::Ok, S::Error>
610        where
611            S: Serializer,
612        {
613            EthereumTxEnvelope::<'_, T>::from(source).serialize(serializer)
614        }
615    }
616
617    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTxEnvelope<T>>
618        for EthereumTxEnvelope<'de, T>
619    {
620        fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumTxEnvelope<T>, D::Error>
621        where
622            D: Deserializer<'de>,
623        {
624            EthereumTxEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
625        }
626    }
627
628    #[cfg(test)]
629    mod tests {
630        use super::super::{serde_bincode_compat, EthereumTxEnvelope};
631        use crate::TxEip4844;
632        use arbitrary::Arbitrary;
633        use bincode::config;
634        use rand::Rng;
635        use serde::{Deserialize, Serialize};
636        use serde_with::serde_as;
637
638        #[test]
639        fn test_typed_tx_envelope_bincode_roundtrip() {
640            #[serde_as]
641            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
642            struct Data {
643                #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_>")]
644                transaction: EthereumTxEnvelope<TxEip4844>,
645            }
646
647            let mut bytes = [0u8; 1024];
648            rand::thread_rng().fill(bytes.as_mut_slice());
649            let data = Data {
650                transaction: EthereumTxEnvelope::arbitrary(&mut arbitrary::Unstructured::new(
651                    &bytes,
652                ))
653                .unwrap(),
654            };
655
656            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
657            let (decoded, _) =
658                bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
659            assert_eq!(decoded, data);
660        }
661    }
662}
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667    use crate::{
668        transaction::{Recovered, SignableTransaction},
669        Transaction, TxEip4844, TxEip4844WithSidecar,
670    };
671    use alloc::vec::Vec;
672    use alloy_eips::{
673        eip2930::{AccessList, AccessListItem},
674        eip4844::BlobTransactionSidecar,
675        eip7702::Authorization,
676    };
677    #[allow(unused_imports)]
678    use alloy_primitives::{b256, Bytes, TxKind};
679    use alloy_primitives::{hex, Address, Signature, U256};
680    use alloy_rlp::Decodable;
681    use std::{fs, path::PathBuf, str::FromStr, vec};
682
683    #[test]
684    fn assert_encodable() {
685        fn assert_encodable<T: Encodable2718>() {}
686
687        assert_encodable::<EthereumTxEnvelope<TxEip4844>>();
688        assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844>>>();
689        assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844Variant>>>();
690    }
691
692    #[test]
693    #[cfg(feature = "k256")]
694    // Test vector from https://etherscan.io/tx/0xce4dc6d7a7549a98ee3b071b67e970879ff51b5b95d1c340bacd80fa1e1aab31
695    fn test_decode_live_1559_tx() {
696        use alloy_primitives::address;
697
698        let raw_tx = alloy_primitives::hex::decode("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8").unwrap();
699        let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
700
701        assert_eq!(res.tx_type(), TxType::Eip1559);
702
703        let tx = match res {
704            TxEnvelope::Eip1559(tx) => tx,
705            _ => unreachable!(),
706        };
707
708        assert_eq!(tx.tx().to, TxKind::Call(address!("D9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
709        let from = tx.recover_signer().unwrap();
710        assert_eq!(from, address!("001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
711    }
712
713    #[test]
714    fn test_is_replay_protected_v() {
715        let sig = Signature::test_signature();
716        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
717            TxLegacy::default(),
718            sig,
719            Default::default(),
720        ))
721        .is_replay_protected());
722        let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565");
723        let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1");
724        let v = false;
725        let valid_sig = Signature::from_scalars_and_parity(r, s, v);
726        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
727            TxLegacy::default(),
728            valid_sig,
729            Default::default(),
730        ))
731        .is_replay_protected());
732        assert!(&TxEnvelope::Eip2930(Signed::new_unchecked(
733            TxEip2930::default(),
734            sig,
735            Default::default(),
736        ))
737        .is_replay_protected());
738        assert!(&TxEnvelope::Eip1559(Signed::new_unchecked(
739            TxEip1559::default(),
740            sig,
741            Default::default(),
742        ))
743        .is_replay_protected());
744        assert!(&TxEnvelope::Eip4844(Signed::new_unchecked(
745            TxEip4844Variant::TxEip4844(TxEip4844::default()),
746            sig,
747            Default::default(),
748        ))
749        .is_replay_protected());
750        assert!(&TxEnvelope::Eip7702(Signed::new_unchecked(
751            TxEip7702::default(),
752            sig,
753            Default::default(),
754        ))
755        .is_replay_protected());
756    }
757
758    #[test]
759    #[cfg(feature = "k256")]
760    // Test vector from https://etherscan.io/tx/0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4
761    fn test_decode_live_legacy_tx() {
762        use alloy_primitives::address;
763
764        let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
765        let res = TxEnvelope::decode_2718(&mut raw_tx.as_ref()).unwrap();
766        assert_eq!(res.tx_type(), TxType::Legacy);
767
768        let tx = match res {
769            TxEnvelope::Legacy(tx) => tx,
770            _ => unreachable!(),
771        };
772
773        assert_eq!(tx.tx().chain_id(), Some(1));
774
775        assert_eq!(tx.tx().to, TxKind::Call(address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D")));
776        assert_eq!(
777            tx.hash().to_string(),
778            "0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4"
779        );
780        let from = tx.recover_signer().unwrap();
781        assert_eq!(from, address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
782    }
783
784    #[test]
785    #[cfg(feature = "k256")]
786    // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
787    // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
788    fn test_decode_live_4844_tx() {
789        use crate::Transaction;
790        use alloy_primitives::{address, b256};
791
792        // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
793        let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
794
795        let res = TxEnvelope::decode_2718(&mut raw_tx.as_slice()).unwrap();
796        assert_eq!(res.tx_type(), TxType::Eip4844);
797
798        let tx = match res {
799            TxEnvelope::Eip4844(tx) => tx,
800            _ => unreachable!(),
801        };
802
803        assert_eq!(
804            tx.tx().kind(),
805            TxKind::Call(address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064"))
806        );
807
808        // Assert this is the correct variant of the EIP-4844 enum, which only contains the tx.
809        assert!(matches!(tx.tx(), TxEip4844Variant::TxEip4844(_)));
810
811        assert_eq!(
812            tx.tx().tx().blob_versioned_hashes,
813            vec![
814                b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
815                b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
816                b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
817                b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
818                b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
819            ]
820        );
821
822        let from = tx.recover_signer().unwrap();
823        assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
824    }
825
826    fn test_encode_decode_roundtrip<T: SignableTransaction<Signature>>(
827        tx: T,
828        signature: Option<Signature>,
829    ) where
830        Signed<T>: Into<TxEnvelope>,
831    {
832        let signature = signature.unwrap_or_else(Signature::test_signature);
833        let tx_signed = tx.into_signed(signature);
834        let tx_envelope: TxEnvelope = tx_signed.into();
835        let encoded = tx_envelope.encoded_2718();
836        let mut slice = encoded.as_slice();
837        let decoded = TxEnvelope::decode_2718(&mut slice).unwrap();
838        assert_eq!(encoded.len(), tx_envelope.encode_2718_len());
839        assert_eq!(decoded, tx_envelope);
840        assert_eq!(slice.len(), 0);
841    }
842
843    #[test]
844    fn test_encode_decode_legacy() {
845        let tx = TxLegacy {
846            chain_id: None,
847            nonce: 2,
848            gas_limit: 1000000,
849            gas_price: 10000000000,
850            to: Address::left_padding_from(&[6]).into(),
851            value: U256::from(7_u64),
852            ..Default::default()
853        };
854        test_encode_decode_roundtrip(tx, Some(Signature::test_signature().with_parity(true)));
855    }
856
857    #[test]
858    fn test_encode_decode_eip1559() {
859        let tx = TxEip1559 {
860            chain_id: 1u64,
861            nonce: 2,
862            max_fee_per_gas: 3,
863            max_priority_fee_per_gas: 4,
864            gas_limit: 5,
865            to: Address::left_padding_from(&[6]).into(),
866            value: U256::from(7_u64),
867            input: vec![8].into(),
868            access_list: Default::default(),
869        };
870        test_encode_decode_roundtrip(tx, None);
871    }
872
873    #[test]
874    fn test_encode_decode_eip1559_parity_eip155() {
875        let tx = TxEip1559 {
876            chain_id: 1u64,
877            nonce: 2,
878            max_fee_per_gas: 3,
879            max_priority_fee_per_gas: 4,
880            gas_limit: 5,
881            to: Address::left_padding_from(&[6]).into(),
882            value: U256::from(7_u64),
883            input: vec![8].into(),
884            access_list: Default::default(),
885        };
886        let signature = Signature::test_signature().with_parity(true);
887
888        test_encode_decode_roundtrip(tx, Some(signature));
889    }
890
891    #[test]
892    fn test_encode_decode_eip2930_parity_eip155() {
893        let tx = TxEip2930 {
894            chain_id: 1u64,
895            nonce: 2,
896            gas_price: 3,
897            gas_limit: 4,
898            to: Address::left_padding_from(&[5]).into(),
899            value: U256::from(6_u64),
900            input: vec![7].into(),
901            access_list: Default::default(),
902        };
903        let signature = Signature::test_signature().with_parity(true);
904        test_encode_decode_roundtrip(tx, Some(signature));
905    }
906
907    #[test]
908    fn test_encode_decode_eip4844_parity_eip155() {
909        let tx = TxEip4844 {
910            chain_id: 1,
911            nonce: 100,
912            max_fee_per_gas: 50_000_000_000,
913            max_priority_fee_per_gas: 1_000_000_000_000,
914            gas_limit: 1_000_000,
915            to: Address::random(),
916            value: U256::from(10e18),
917            input: Bytes::new(),
918            access_list: AccessList(vec![AccessListItem {
919                address: Address::random(),
920                storage_keys: vec![B256::random()],
921            }]),
922            blob_versioned_hashes: vec![B256::random()],
923            max_fee_per_blob_gas: 0,
924        };
925        let signature = Signature::test_signature().with_parity(true);
926        test_encode_decode_roundtrip(tx, Some(signature));
927    }
928
929    #[test]
930    fn test_encode_decode_eip4844_sidecar_parity_eip155() {
931        let tx = TxEip4844 {
932            chain_id: 1,
933            nonce: 100,
934            max_fee_per_gas: 50_000_000_000,
935            max_priority_fee_per_gas: 1_000_000_000_000,
936            gas_limit: 1_000_000,
937            to: Address::random(),
938            value: U256::from(10e18),
939            input: Bytes::new(),
940            access_list: AccessList(vec![AccessListItem {
941                address: Address::random(),
942                storage_keys: vec![B256::random()],
943            }]),
944            blob_versioned_hashes: vec![B256::random()],
945            max_fee_per_blob_gas: 0,
946        };
947        let sidecar = BlobTransactionSidecar {
948            blobs: vec![[2; 131072].into()],
949            commitments: vec![[3; 48].into()],
950            proofs: vec![[4; 48].into()],
951        };
952        let tx = TxEip4844WithSidecar { tx, sidecar };
953        let signature = Signature::test_signature().with_parity(true);
954
955        let tx_signed = tx.into_signed(signature);
956        let tx_envelope: TxEnvelope = tx_signed.into();
957
958        let mut out = Vec::new();
959        tx_envelope.network_encode(&mut out);
960        let mut slice = out.as_slice();
961        let decoded = TxEnvelope::network_decode(&mut slice).unwrap();
962        assert_eq!(slice.len(), 0);
963        assert_eq!(out.len(), tx_envelope.network_len());
964        assert_eq!(decoded, tx_envelope);
965    }
966
967    #[test]
968    fn test_encode_decode_eip4844_variant_parity_eip155() {
969        let tx = TxEip4844 {
970            chain_id: 1,
971            nonce: 100,
972            max_fee_per_gas: 50_000_000_000,
973            max_priority_fee_per_gas: 1_000_000_000_000,
974            gas_limit: 1_000_000,
975            to: Address::random(),
976            value: U256::from(10e18),
977            input: Bytes::new(),
978            access_list: AccessList(vec![AccessListItem {
979                address: Address::random(),
980                storage_keys: vec![B256::random()],
981            }]),
982            blob_versioned_hashes: vec![B256::random()],
983            max_fee_per_blob_gas: 0,
984        };
985        let tx = TxEip4844Variant::TxEip4844(tx);
986        let signature = Signature::test_signature().with_parity(true);
987        test_encode_decode_roundtrip(tx, Some(signature));
988    }
989
990    #[test]
991    fn test_encode_decode_eip2930() {
992        let tx = TxEip2930 {
993            chain_id: 1u64,
994            nonce: 2,
995            gas_price: 3,
996            gas_limit: 4,
997            to: Address::left_padding_from(&[5]).into(),
998            value: U256::from(6_u64),
999            input: vec![7].into(),
1000            access_list: AccessList(vec![AccessListItem {
1001                address: Address::left_padding_from(&[8]),
1002                storage_keys: vec![B256::left_padding_from(&[9])],
1003            }]),
1004        };
1005        test_encode_decode_roundtrip(tx, None);
1006    }
1007
1008    #[test]
1009    fn test_encode_decode_eip7702() {
1010        let tx = TxEip7702 {
1011            chain_id: 1u64,
1012            nonce: 2,
1013            gas_limit: 3,
1014            max_fee_per_gas: 4,
1015            max_priority_fee_per_gas: 5,
1016            to: Address::left_padding_from(&[5]),
1017            value: U256::from(6_u64),
1018            input: vec![7].into(),
1019            access_list: AccessList(vec![AccessListItem {
1020                address: Address::left_padding_from(&[8]),
1021                storage_keys: vec![B256::left_padding_from(&[9])],
1022            }]),
1023            authorization_list: vec![(Authorization {
1024                chain_id: U256::from(1),
1025                address: Address::left_padding_from(&[10]),
1026                nonce: 1u64,
1027            })
1028            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1029        };
1030        test_encode_decode_roundtrip(tx, None);
1031    }
1032
1033    #[test]
1034    fn test_encode_decode_transaction_list() {
1035        let signature = Signature::test_signature();
1036        let tx = TxEnvelope::Eip1559(
1037            TxEip1559 {
1038                chain_id: 1u64,
1039                nonce: 2,
1040                max_fee_per_gas: 3,
1041                max_priority_fee_per_gas: 4,
1042                gas_limit: 5,
1043                to: Address::left_padding_from(&[6]).into(),
1044                value: U256::from(7_u64),
1045                input: vec![8].into(),
1046                access_list: Default::default(),
1047            }
1048            .into_signed(signature),
1049        );
1050        let transactions = vec![tx.clone(), tx];
1051        let encoded = alloy_rlp::encode(&transactions);
1052        let decoded = Vec::<TxEnvelope>::decode(&mut &encoded[..]).unwrap();
1053        assert_eq!(transactions, decoded);
1054    }
1055
1056    #[test]
1057    fn decode_encode_known_rpc_transaction() {
1058        // test data pulled from hive test that sends blob transactions
1059        let network_data_path =
1060            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/rpc_blob_transaction.rlp");
1061        let data = fs::read_to_string(network_data_path).expect("Unable to read file");
1062        let hex_data = hex::decode(data.trim()).unwrap();
1063
1064        let tx: TxEnvelope = TxEnvelope::decode_2718(&mut hex_data.as_slice()).unwrap();
1065        let encoded = tx.encoded_2718();
1066        assert_eq!(encoded, hex_data);
1067        assert_eq!(tx.encode_2718_len(), hex_data.len());
1068    }
1069
1070    #[cfg(feature = "serde")]
1071    fn test_serde_roundtrip<T: SignableTransaction<Signature>>(tx: T)
1072    where
1073        Signed<T>: Into<TxEnvelope>,
1074    {
1075        let signature = Signature::test_signature();
1076        let tx_envelope: TxEnvelope = tx.into_signed(signature).into();
1077
1078        let serialized = serde_json::to_string(&tx_envelope).unwrap();
1079
1080        let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap();
1081
1082        assert_eq!(tx_envelope, deserialized);
1083    }
1084
1085    #[test]
1086    #[cfg(feature = "serde")]
1087    fn test_serde_roundtrip_legacy() {
1088        let tx = TxLegacy {
1089            chain_id: Some(1),
1090            nonce: 100,
1091            gas_price: 3_000_000_000,
1092            gas_limit: 50_000,
1093            to: Address::default().into(),
1094            value: U256::from(10e18),
1095            input: Bytes::new(),
1096        };
1097        test_serde_roundtrip(tx);
1098    }
1099
1100    #[test]
1101    #[cfg(feature = "serde")]
1102    fn test_serde_roundtrip_eip1559() {
1103        let tx = TxEip1559 {
1104            chain_id: 1,
1105            nonce: 100,
1106            max_fee_per_gas: 50_000_000_000,
1107            max_priority_fee_per_gas: 1_000_000_000_000,
1108            gas_limit: 1_000_000,
1109            to: TxKind::Create,
1110            value: U256::from(10e18),
1111            input: Bytes::new(),
1112            access_list: AccessList(vec![AccessListItem {
1113                address: Address::random(),
1114                storage_keys: vec![B256::random()],
1115            }]),
1116        };
1117        test_serde_roundtrip(tx);
1118    }
1119
1120    #[test]
1121    #[cfg(feature = "serde")]
1122    fn test_serde_roundtrip_eip2930() {
1123        let tx = TxEip2930 {
1124            chain_id: u64::MAX,
1125            nonce: u64::MAX,
1126            gas_price: u128::MAX,
1127            gas_limit: u64::MAX,
1128            to: Address::random().into(),
1129            value: U256::MAX,
1130            input: Bytes::new(),
1131            access_list: Default::default(),
1132        };
1133        test_serde_roundtrip(tx);
1134    }
1135
1136    #[test]
1137    #[cfg(feature = "serde")]
1138    fn test_serde_roundtrip_eip4844() {
1139        let tx = TxEip4844Variant::TxEip4844(TxEip4844 {
1140            chain_id: 1,
1141            nonce: 100,
1142            max_fee_per_gas: 50_000_000_000,
1143            max_priority_fee_per_gas: 1_000_000_000_000,
1144            gas_limit: 1_000_000,
1145            to: Address::random(),
1146            value: U256::from(10e18),
1147            input: Bytes::new(),
1148            access_list: AccessList(vec![AccessListItem {
1149                address: Address::random(),
1150                storage_keys: vec![B256::random()],
1151            }]),
1152            blob_versioned_hashes: vec![B256::random()],
1153            max_fee_per_blob_gas: 0,
1154        });
1155        test_serde_roundtrip(tx);
1156
1157        let tx = TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar {
1158            tx: TxEip4844 {
1159                chain_id: 1,
1160                nonce: 100,
1161                max_fee_per_gas: 50_000_000_000,
1162                max_priority_fee_per_gas: 1_000_000_000_000,
1163                gas_limit: 1_000_000,
1164                to: Address::random(),
1165                value: U256::from(10e18),
1166                input: Bytes::new(),
1167                access_list: AccessList(vec![AccessListItem {
1168                    address: Address::random(),
1169                    storage_keys: vec![B256::random()],
1170                }]),
1171                blob_versioned_hashes: vec![B256::random()],
1172                max_fee_per_blob_gas: 0,
1173            },
1174            sidecar: Default::default(),
1175        });
1176        test_serde_roundtrip(tx);
1177    }
1178
1179    #[test]
1180    #[cfg(feature = "serde")]
1181    fn test_serde_roundtrip_eip7702() {
1182        let tx = TxEip7702 {
1183            chain_id: u64::MAX,
1184            nonce: u64::MAX,
1185            gas_limit: u64::MAX,
1186            max_fee_per_gas: u128::MAX,
1187            max_priority_fee_per_gas: u128::MAX,
1188            to: Address::random(),
1189            value: U256::MAX,
1190            input: Bytes::new(),
1191            access_list: AccessList(vec![AccessListItem {
1192                address: Address::random(),
1193                storage_keys: vec![B256::random()],
1194            }]),
1195            authorization_list: vec![(Authorization {
1196                chain_id: U256::from(1),
1197                address: Address::left_padding_from(&[1]),
1198                nonce: 1u64,
1199            })
1200            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1201        };
1202        test_serde_roundtrip(tx);
1203    }
1204
1205    #[test]
1206    #[cfg(feature = "serde")]
1207    fn serde_tx_from_contract_call() {
1208        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"}"#;
1209
1210        let te = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1211
1212        assert_eq!(
1213            *te.tx_hash(),
1214            alloy_primitives::b256!(
1215                "018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f"
1216            )
1217        );
1218    }
1219
1220    #[test]
1221    #[cfg(feature = "k256")]
1222    fn test_arbitrary_envelope() {
1223        use crate::transaction::SignerRecoverable;
1224        use arbitrary::Arbitrary;
1225        let mut unstructured = arbitrary::Unstructured::new(b"arbitrary tx envelope");
1226        let tx = TxEnvelope::arbitrary(&mut unstructured).unwrap();
1227
1228        assert!(tx.recover_signer().is_ok());
1229    }
1230
1231    #[test]
1232    #[cfg(feature = "serde")]
1233    fn test_serde_untagged_legacy() {
1234        let data = r#"{
1235            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1236            "input": "0x",
1237            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1238            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1239            "v": "0x1c",
1240            "gas": "0x15f90",
1241            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1242            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1243            "value": "0xf606682badd7800",
1244            "nonce": "0x11f398",
1245            "gasPrice": "0x4a817c800"
1246        }"#;
1247
1248        let tx: TxEnvelope = serde_json::from_str(data).unwrap();
1249
1250        assert!(matches!(tx, TxEnvelope::Legacy(_)));
1251
1252        let data_with_wrong_type = r#"{
1253            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1254            "input": "0x",
1255            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1256            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1257            "v": "0x1c",
1258            "gas": "0x15f90",
1259            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1260            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1261            "value": "0xf606682badd7800",
1262            "nonce": "0x11f398",
1263            "gasPrice": "0x4a817c800",
1264            "type": "0x12"
1265        }"#;
1266
1267        assert!(serde_json::from_str::<TxEnvelope>(data_with_wrong_type).is_err());
1268    }
1269
1270    #[test]
1271    fn test_tx_type_try_from_u8() {
1272        assert_eq!(TxType::try_from(0u8).unwrap(), TxType::Legacy);
1273        assert_eq!(TxType::try_from(1u8).unwrap(), TxType::Eip2930);
1274        assert_eq!(TxType::try_from(2u8).unwrap(), TxType::Eip1559);
1275        assert_eq!(TxType::try_from(3u8).unwrap(), TxType::Eip4844);
1276        assert_eq!(TxType::try_from(4u8).unwrap(), TxType::Eip7702);
1277        assert!(TxType::try_from(5u8).is_err()); // Invalid case
1278    }
1279
1280    #[test]
1281    fn test_tx_type_try_from_u64() {
1282        assert_eq!(TxType::try_from(0u64).unwrap(), TxType::Legacy);
1283        assert_eq!(TxType::try_from(1u64).unwrap(), TxType::Eip2930);
1284        assert_eq!(TxType::try_from(2u64).unwrap(), TxType::Eip1559);
1285        assert_eq!(TxType::try_from(3u64).unwrap(), TxType::Eip4844);
1286        assert_eq!(TxType::try_from(4u64).unwrap(), TxType::Eip7702);
1287        assert!(TxType::try_from(10u64).is_err()); // Invalid case
1288    }
1289
1290    #[test]
1291    fn test_tx_type_from_conversions() {
1292        let legacy_tx = Signed::new_unchecked(
1293            TxLegacy::default(),
1294            Signature::test_signature(),
1295            Default::default(),
1296        );
1297        let eip2930_tx = Signed::new_unchecked(
1298            TxEip2930::default(),
1299            Signature::test_signature(),
1300            Default::default(),
1301        );
1302        let eip1559_tx = Signed::new_unchecked(
1303            TxEip1559::default(),
1304            Signature::test_signature(),
1305            Default::default(),
1306        );
1307        let eip4844_variant = Signed::new_unchecked(
1308            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1309            Signature::test_signature(),
1310            Default::default(),
1311        );
1312        let eip7702_tx = Signed::new_unchecked(
1313            TxEip7702::default(),
1314            Signature::test_signature(),
1315            Default::default(),
1316        );
1317
1318        assert!(matches!(TxEnvelope::from(legacy_tx), TxEnvelope::Legacy(_)));
1319        assert!(matches!(TxEnvelope::from(eip2930_tx), TxEnvelope::Eip2930(_)));
1320        assert!(matches!(TxEnvelope::from(eip1559_tx), TxEnvelope::Eip1559(_)));
1321        assert!(matches!(TxEnvelope::from(eip4844_variant), TxEnvelope::Eip4844(_)));
1322        assert!(matches!(TxEnvelope::from(eip7702_tx), TxEnvelope::Eip7702(_)));
1323    }
1324
1325    #[test]
1326    fn test_tx_type_is_methods() {
1327        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1328            TxLegacy::default(),
1329            Signature::test_signature(),
1330            Default::default(),
1331        ));
1332        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1333            TxEip2930::default(),
1334            Signature::test_signature(),
1335            Default::default(),
1336        ));
1337        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1338            TxEip1559::default(),
1339            Signature::test_signature(),
1340            Default::default(),
1341        ));
1342        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1343            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1344            Signature::test_signature(),
1345            Default::default(),
1346        ));
1347        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1348            TxEip7702::default(),
1349            Signature::test_signature(),
1350            Default::default(),
1351        ));
1352
1353        assert!(legacy_tx.is_legacy());
1354        assert!(!legacy_tx.is_eip2930());
1355        assert!(!legacy_tx.is_eip1559());
1356        assert!(!legacy_tx.is_eip4844());
1357        assert!(!legacy_tx.is_eip7702());
1358
1359        assert!(eip2930_tx.is_eip2930());
1360        assert!(!eip2930_tx.is_legacy());
1361        assert!(!eip2930_tx.is_eip1559());
1362        assert!(!eip2930_tx.is_eip4844());
1363        assert!(!eip2930_tx.is_eip7702());
1364
1365        assert!(eip1559_tx.is_eip1559());
1366        assert!(!eip1559_tx.is_legacy());
1367        assert!(!eip1559_tx.is_eip2930());
1368        assert!(!eip1559_tx.is_eip4844());
1369        assert!(!eip1559_tx.is_eip7702());
1370
1371        assert!(eip4844_tx.is_eip4844());
1372        assert!(!eip4844_tx.is_legacy());
1373        assert!(!eip4844_tx.is_eip2930());
1374        assert!(!eip4844_tx.is_eip1559());
1375        assert!(!eip4844_tx.is_eip7702());
1376
1377        assert!(eip7702_tx.is_eip7702());
1378        assert!(!eip7702_tx.is_legacy());
1379        assert!(!eip7702_tx.is_eip2930());
1380        assert!(!eip7702_tx.is_eip1559());
1381        assert!(!eip7702_tx.is_eip4844());
1382    }
1383
1384    #[test]
1385    fn test_tx_type() {
1386        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1387            TxLegacy::default(),
1388            Signature::test_signature(),
1389            Default::default(),
1390        ));
1391        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1392            TxEip2930::default(),
1393            Signature::test_signature(),
1394            Default::default(),
1395        ));
1396        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1397            TxEip1559::default(),
1398            Signature::test_signature(),
1399            Default::default(),
1400        ));
1401        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1402            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1403            Signature::test_signature(),
1404            Default::default(),
1405        ));
1406        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1407            TxEip7702::default(),
1408            Signature::test_signature(),
1409            Default::default(),
1410        ));
1411
1412        assert_eq!(legacy_tx.tx_type(), TxType::Legacy);
1413        assert_eq!(eip2930_tx.tx_type(), TxType::Eip2930);
1414        assert_eq!(eip1559_tx.tx_type(), TxType::Eip1559);
1415        assert_eq!(eip4844_tx.tx_type(), TxType::Eip4844);
1416        assert_eq!(eip7702_tx.tx_type(), TxType::Eip7702);
1417    }
1418
1419    // <https://sepolia.etherscan.io/getRawTx?tx=0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6>
1420    #[test]
1421    fn decode_raw_legacy() {
1422        let raw = hex!("f8aa0285018ef61d0a832dc6c094cb33aa5b38d79e3d9fa8b10aff38aa201399a7e380b844af7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce800000000000000000000000000000000000000000000000000000000000000641ca05e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664a02353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4");
1423        let tx = TxEnvelope::decode_2718(&mut raw.as_ref()).unwrap();
1424        assert!(tx.chain_id().is_none());
1425    }
1426
1427    #[test]
1428    #[cfg(feature = "serde")]
1429    fn can_deserialize_system_transaction_with_zero_signature_envelope() {
1430        let raw_tx = r#"{
1431            "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
1432            "blockNumber": "0x45a59bb",
1433            "from": "0x0000000000000000000000000000000000000000",
1434            "gas": "0x1e8480",
1435            "gasPrice": "0x0",
1436            "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
1437            "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
1438            "nonce": "0x469f7",
1439            "to": "0x4200000000000000000000000000000000000007",
1440            "transactionIndex": "0x0",
1441            "value": "0x0",
1442            "v": "0x0",
1443            "r": "0x0",
1444            "s": "0x0",
1445            "queueOrigin": "l1",
1446            "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
1447            "l1BlockNumber": "0xfd1a6c",
1448            "l1Timestamp": "0x63e434ff",
1449            "index": "0x45a59ba",
1450            "queueIndex": "0x469f7",
1451            "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
1452        }"#;
1453
1454        let tx = serde_json::from_str::<TxEnvelope>(raw_tx).unwrap();
1455
1456        assert_eq!(tx.signature().r(), U256::ZERO);
1457        assert_eq!(tx.signature().s(), U256::ZERO);
1458        assert!(!tx.signature().v());
1459
1460        assert_eq!(
1461            tx.hash(),
1462            &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
1463            "hash should match the transaction hash"
1464        );
1465    }
1466
1467    // <https://github.com/succinctlabs/kona/issues/31>
1468    #[test]
1469    #[cfg(feature = "serde")]
1470    fn serde_block_tx() {
1471        let rpc_tx = r#"{
1472      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1473      "blockNumber": "0x6edcde",
1474      "transactionIndex": "0x7",
1475      "hash": "0x2cb125e083d6d2631e3752bd2b3d757bf31bf02bfe21de0ffa46fbb118d28b19",
1476      "from": "0x03e5badf3bb1ade1a8f33f94536c827b6531948d",
1477      "to": "0x3267e72dc8780a1512fa69da7759ec66f30350e3",
1478      "input": "0x62e4c545000000000000000000000000464c8ec100f2f42fb4e42e07e203da2324f9fc6700000000000000000000000003e5badf3bb1ade1a8f33f94536c827b6531948d000000000000000000000000a064bfb5c7e81426647dc20a0d854da1538559dc00000000000000000000000000000000000000000000000000c6f3b40b6c0000",
1479      "nonce": "0x2a8",
1480      "value": "0x0",
1481      "gas": "0x28afd",
1482      "gasPrice": "0x23ec5dbc2",
1483      "accessList": [],
1484      "chainId": "0xaa36a7",
1485      "type": "0x0",
1486      "v": "0x1546d71",
1487      "r": "0x809b9f0a1777e376cd1ee5d2f551035643755edf26ea65b7a00c822a24504962",
1488      "s": "0x6a57bb8e21fe85c7e092868ee976fef71edca974d8c452fcf303f9180c764f64"
1489    }"#;
1490
1491        let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1492    }
1493
1494    // <https://github.com/succinctlabs/kona/issues/31>
1495    #[test]
1496    #[cfg(feature = "serde")]
1497    fn serde_block_tx_legacy_chain_id() {
1498        let rpc_tx = r#"{
1499      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1500      "blockNumber": "0x6edcde",
1501      "transactionIndex": "0x8",
1502      "hash": "0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6",
1503      "from": "0x8b87f0a788cc14b4f0f374da59920f5017ff05de",
1504      "to": "0xcb33aa5b38d79e3d9fa8b10aff38aa201399a7e3",
1505      "input": "0xaf7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce80000000000000000000000000000000000000000000000000000000000000064",
1506      "nonce": "0x2",
1507      "value": "0x0",
1508      "gas": "0x2dc6c0",
1509      "gasPrice": "0x18ef61d0a",
1510      "accessList": [],
1511      "chainId": "0xaa36a7",
1512      "type": "0x0",
1513      "v": "0x1c",
1514      "r": "0x5e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664",
1515      "s": "0x2353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4"
1516    }"#;
1517
1518        let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1519    }
1520}