alloy_consensus/transaction/
envelope.rs

1use super::SignableTransaction;
2use crate::{
3    error::ValueError,
4    transaction::{
5        eip4844::{TxEip4844, TxEip4844Variant},
6        RlpEcdsaEncodableTx,
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
448#[cfg(any(feature = "secp256k1", feature = "k256"))]
449impl<Eip4844> crate::transaction::SignerRecoverable for EthereumTxEnvelope<Eip4844>
450where
451    Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
452{
453    fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
454        match self {
455            Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
456            Self::Eip2930(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
457            Self::Eip1559(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
458            Self::Eip4844(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
459            Self::Eip7702(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
460        }
461    }
462
463    fn recover_signer_unchecked(
464        &self,
465    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
466        match self {
467            Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer_unchecked(tx),
468            Self::Eip2930(tx) => {
469                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
470            }
471            Self::Eip1559(tx) => {
472                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
473            }
474            Self::Eip4844(tx) => {
475                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
476            }
477            Self::Eip7702(tx) => {
478                crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
479            }
480        }
481    }
482
483    fn recover_unchecked_with_buf(
484        &self,
485        buf: &mut alloc::vec::Vec<u8>,
486    ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
487        match self {
488            Self::Legacy(tx) => {
489                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
490            }
491            Self::Eip2930(tx) => {
492                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
493            }
494            Self::Eip1559(tx) => {
495                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
496            }
497            Self::Eip4844(tx) => {
498                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
499            }
500            Self::Eip7702(tx) => {
501                crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
502            }
503        }
504    }
505}
506
507/// Bincode-compatible [`EthereumTxEnvelope`] serde implementation.
508#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
509pub mod serde_bincode_compat {
510    use crate::{EthereumTypedTransaction, Signed};
511    use alloc::borrow::Cow;
512    use alloy_primitives::Signature;
513    use serde::{Deserialize, Deserializer, Serialize, Serializer};
514    use serde_with::{DeserializeAs, SerializeAs};
515
516    /// Bincode-compatible [`super::EthereumTxEnvelope`] serde implementation.
517    ///
518    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
519    /// ```rust
520    /// use alloy_consensus::{serde_bincode_compat, EthereumTxEnvelope};
521    /// use serde::{de::DeserializeOwned, Deserialize, Serialize};
522    /// use serde_with::serde_as;
523    ///
524    /// #[serde_as]
525    /// #[derive(Serialize, Deserialize)]
526    /// struct Data<T: Serialize + DeserializeOwned + Clone + 'static> {
527    ///     #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_, T>")]
528    ///     receipt: EthereumTxEnvelope<T>,
529    /// }
530    /// ```
531    #[derive(Debug, Serialize, Deserialize)]
532    pub struct EthereumTxEnvelope<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
533        /// Transaction signature
534        signature: Signature,
535        /// bincode compatible transaction
536        transaction:
537            crate::serde_bincode_compat::transaction::EthereumTypedTransaction<'a, Eip4844>,
538    }
539
540    impl<'a, T: Clone> From<&'a super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'a, T> {
541        fn from(value: &'a super::EthereumTxEnvelope<T>) -> Self {
542            match value {
543                super::EthereumTxEnvelope::Legacy(tx) => Self {
544                    signature: *tx.signature(),
545                    transaction:
546                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Legacy(
547                            tx.tx().into(),
548                        ),
549                },
550                super::EthereumTxEnvelope::Eip2930(tx) => Self {
551                    signature: *tx.signature(),
552                    transaction:
553                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip2930(
554                            tx.tx().into(),
555                        ),
556                },
557                super::EthereumTxEnvelope::Eip1559(tx) => Self {
558                    signature: *tx.signature(),
559                    transaction:
560                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip1559(
561                            tx.tx().into(),
562                        ),
563                },
564                super::EthereumTxEnvelope::Eip4844(tx) => Self {
565                    signature: *tx.signature(),
566                    transaction:
567                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip4844(
568                            Cow::Borrowed(tx.tx()),
569                        ),
570                },
571                super::EthereumTxEnvelope::Eip7702(tx) => Self {
572                    signature: *tx.signature(),
573                    transaction:
574                        crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip7702(
575                            tx.tx().into(),
576                        ),
577                },
578            }
579        }
580    }
581
582    impl<'a, T: Clone> From<EthereumTxEnvelope<'a, T>> for super::EthereumTxEnvelope<T> {
583        fn from(value: EthereumTxEnvelope<'a, T>) -> Self {
584            let EthereumTxEnvelope { signature, transaction } = value;
585            let transaction: crate::transaction::typed::EthereumTypedTransaction<T> =
586                transaction.into();
587            match transaction {
588                EthereumTypedTransaction::Legacy(tx) => Signed::new_unhashed(tx, signature).into(),
589                EthereumTypedTransaction::Eip2930(tx) => Signed::new_unhashed(tx, signature).into(),
590                EthereumTypedTransaction::Eip1559(tx) => Signed::new_unhashed(tx, signature).into(),
591                EthereumTypedTransaction::Eip4844(tx) => {
592                    Self::Eip4844(Signed::new_unhashed(tx, signature))
593                }
594                EthereumTypedTransaction::Eip7702(tx) => Signed::new_unhashed(tx, signature).into(),
595            }
596        }
597    }
598
599    impl<T: Serialize + Clone> SerializeAs<super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'_, T> {
600        fn serialize_as<S>(
601            source: &super::EthereumTxEnvelope<T>,
602            serializer: S,
603        ) -> Result<S::Ok, S::Error>
604        where
605            S: Serializer,
606        {
607            EthereumTxEnvelope::<'_, T>::from(source).serialize(serializer)
608        }
609    }
610
611    impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTxEnvelope<T>>
612        for EthereumTxEnvelope<'de, T>
613    {
614        fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumTxEnvelope<T>, D::Error>
615        where
616            D: Deserializer<'de>,
617        {
618            EthereumTxEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
619        }
620    }
621
622    #[cfg(test)]
623    mod tests {
624        use super::super::{serde_bincode_compat, EthereumTxEnvelope};
625        use crate::TxEip4844;
626        use arbitrary::Arbitrary;
627        use bincode::config;
628        use rand::Rng;
629        use serde::{Deserialize, Serialize};
630        use serde_with::serde_as;
631
632        #[test]
633        fn test_typed_tx_envelope_bincode_roundtrip() {
634            #[serde_as]
635            #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
636            struct Data {
637                #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_>")]
638                transaction: EthereumTxEnvelope<TxEip4844>,
639            }
640
641            let mut bytes = [0u8; 1024];
642            rand::thread_rng().fill(bytes.as_mut_slice());
643            let data = Data {
644                transaction: EthereumTxEnvelope::arbitrary(&mut arbitrary::Unstructured::new(
645                    &bytes,
646                ))
647                .unwrap(),
648            };
649
650            let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
651            let (decoded, _) =
652                bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
653            assert_eq!(decoded, data);
654        }
655    }
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661    use crate::{
662        transaction::{Recovered, SignableTransaction},
663        Transaction, TxEip4844, TxEip4844WithSidecar,
664    };
665    use alloc::vec::Vec;
666    use alloy_eips::{
667        eip2930::{AccessList, AccessListItem},
668        eip4844::BlobTransactionSidecar,
669        eip7702::Authorization,
670    };
671    #[allow(unused_imports)]
672    use alloy_primitives::{b256, Bytes, TxKind};
673    use alloy_primitives::{hex, Address, Signature, U256};
674    use alloy_rlp::Decodable;
675    use std::{fs, path::PathBuf, str::FromStr, vec};
676
677    #[test]
678    fn assert_encodable() {
679        fn assert_encodable<T: Encodable2718>() {}
680
681        assert_encodable::<EthereumTxEnvelope<TxEip4844>>();
682        assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844>>>();
683        assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844Variant>>>();
684    }
685
686    #[test]
687    #[cfg(feature = "k256")]
688    // Test vector from https://etherscan.io/tx/0xce4dc6d7a7549a98ee3b071b67e970879ff51b5b95d1c340bacd80fa1e1aab31
689    fn test_decode_live_1559_tx() {
690        use alloy_primitives::address;
691
692        let raw_tx = alloy_primitives::hex::decode("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8").unwrap();
693        let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
694
695        assert_eq!(res.tx_type(), TxType::Eip1559);
696
697        let tx = match res {
698            TxEnvelope::Eip1559(tx) => tx,
699            _ => unreachable!(),
700        };
701
702        assert_eq!(tx.tx().to, TxKind::Call(address!("D9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
703        let from = tx.recover_signer().unwrap();
704        assert_eq!(from, address!("001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
705    }
706
707    #[test]
708    fn test_is_replay_protected_v() {
709        let sig = Signature::test_signature();
710        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
711            TxLegacy::default(),
712            sig,
713            Default::default(),
714        ))
715        .is_replay_protected());
716        let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565");
717        let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1");
718        let v = false;
719        let valid_sig = Signature::from_scalars_and_parity(r, s, v);
720        assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
721            TxLegacy::default(),
722            valid_sig,
723            Default::default(),
724        ))
725        .is_replay_protected());
726        assert!(&TxEnvelope::Eip2930(Signed::new_unchecked(
727            TxEip2930::default(),
728            sig,
729            Default::default(),
730        ))
731        .is_replay_protected());
732        assert!(&TxEnvelope::Eip1559(Signed::new_unchecked(
733            TxEip1559::default(),
734            sig,
735            Default::default(),
736        ))
737        .is_replay_protected());
738        assert!(&TxEnvelope::Eip4844(Signed::new_unchecked(
739            TxEip4844Variant::TxEip4844(TxEip4844::default()),
740            sig,
741            Default::default(),
742        ))
743        .is_replay_protected());
744        assert!(&TxEnvelope::Eip7702(Signed::new_unchecked(
745            TxEip7702::default(),
746            sig,
747            Default::default(),
748        ))
749        .is_replay_protected());
750    }
751
752    #[test]
753    #[cfg(feature = "k256")]
754    // Test vector from https://etherscan.io/tx/0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4
755    fn test_decode_live_legacy_tx() {
756        use alloy_primitives::address;
757
758        let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
759        let res = TxEnvelope::decode_2718(&mut raw_tx.as_ref()).unwrap();
760        assert_eq!(res.tx_type(), TxType::Legacy);
761
762        let tx = match res {
763            TxEnvelope::Legacy(tx) => tx,
764            _ => unreachable!(),
765        };
766
767        assert_eq!(tx.tx().chain_id(), Some(1));
768
769        assert_eq!(tx.tx().to, TxKind::Call(address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D")));
770        assert_eq!(
771            tx.hash().to_string(),
772            "0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4"
773        );
774        let from = tx.recover_signer().unwrap();
775        assert_eq!(from, address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
776    }
777
778    #[test]
779    #[cfg(feature = "k256")]
780    // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
781    // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
782    fn test_decode_live_4844_tx() {
783        use crate::Transaction;
784        use alloy_primitives::{address, b256};
785
786        // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
787        let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
788
789        let res = TxEnvelope::decode_2718(&mut raw_tx.as_slice()).unwrap();
790        assert_eq!(res.tx_type(), TxType::Eip4844);
791
792        let tx = match res {
793            TxEnvelope::Eip4844(tx) => tx,
794            _ => unreachable!(),
795        };
796
797        assert_eq!(
798            tx.tx().kind(),
799            TxKind::Call(address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064"))
800        );
801
802        // Assert this is the correct variant of the EIP-4844 enum, which only contains the tx.
803        assert!(matches!(tx.tx(), TxEip4844Variant::TxEip4844(_)));
804
805        assert_eq!(
806            tx.tx().tx().blob_versioned_hashes,
807            vec![
808                b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
809                b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
810                b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
811                b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
812                b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
813            ]
814        );
815
816        let from = tx.recover_signer().unwrap();
817        assert_eq!(from, address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
818    }
819
820    fn test_encode_decode_roundtrip<T: SignableTransaction<Signature>>(
821        tx: T,
822        signature: Option<Signature>,
823    ) where
824        Signed<T>: Into<TxEnvelope>,
825    {
826        let signature = signature.unwrap_or_else(Signature::test_signature);
827        let tx_signed = tx.into_signed(signature);
828        let tx_envelope: TxEnvelope = tx_signed.into();
829        let encoded = tx_envelope.encoded_2718();
830        let mut slice = encoded.as_slice();
831        let decoded = TxEnvelope::decode_2718(&mut slice).unwrap();
832        assert_eq!(encoded.len(), tx_envelope.encode_2718_len());
833        assert_eq!(decoded, tx_envelope);
834        assert_eq!(slice.len(), 0);
835    }
836
837    #[test]
838    fn test_encode_decode_legacy() {
839        let tx = TxLegacy {
840            chain_id: None,
841            nonce: 2,
842            gas_limit: 1000000,
843            gas_price: 10000000000,
844            to: Address::left_padding_from(&[6]).into(),
845            value: U256::from(7_u64),
846            ..Default::default()
847        };
848        test_encode_decode_roundtrip(tx, Some(Signature::test_signature().with_parity(true)));
849    }
850
851    #[test]
852    fn test_encode_decode_eip1559() {
853        let tx = TxEip1559 {
854            chain_id: 1u64,
855            nonce: 2,
856            max_fee_per_gas: 3,
857            max_priority_fee_per_gas: 4,
858            gas_limit: 5,
859            to: Address::left_padding_from(&[6]).into(),
860            value: U256::from(7_u64),
861            input: vec![8].into(),
862            access_list: Default::default(),
863        };
864        test_encode_decode_roundtrip(tx, None);
865    }
866
867    #[test]
868    fn test_encode_decode_eip1559_parity_eip155() {
869        let tx = TxEip1559 {
870            chain_id: 1u64,
871            nonce: 2,
872            max_fee_per_gas: 3,
873            max_priority_fee_per_gas: 4,
874            gas_limit: 5,
875            to: Address::left_padding_from(&[6]).into(),
876            value: U256::from(7_u64),
877            input: vec![8].into(),
878            access_list: Default::default(),
879        };
880        let signature = Signature::test_signature().with_parity(true);
881
882        test_encode_decode_roundtrip(tx, Some(signature));
883    }
884
885    #[test]
886    fn test_encode_decode_eip2930_parity_eip155() {
887        let tx = TxEip2930 {
888            chain_id: 1u64,
889            nonce: 2,
890            gas_price: 3,
891            gas_limit: 4,
892            to: Address::left_padding_from(&[5]).into(),
893            value: U256::from(6_u64),
894            input: vec![7].into(),
895            access_list: Default::default(),
896        };
897        let signature = Signature::test_signature().with_parity(true);
898        test_encode_decode_roundtrip(tx, Some(signature));
899    }
900
901    #[test]
902    fn test_encode_decode_eip4844_parity_eip155() {
903        let tx = TxEip4844 {
904            chain_id: 1,
905            nonce: 100,
906            max_fee_per_gas: 50_000_000_000,
907            max_priority_fee_per_gas: 1_000_000_000_000,
908            gas_limit: 1_000_000,
909            to: Address::random(),
910            value: U256::from(10e18),
911            input: Bytes::new(),
912            access_list: AccessList(vec![AccessListItem {
913                address: Address::random(),
914                storage_keys: vec![B256::random()],
915            }]),
916            blob_versioned_hashes: vec![B256::random()],
917            max_fee_per_blob_gas: 0,
918        };
919        let signature = Signature::test_signature().with_parity(true);
920        test_encode_decode_roundtrip(tx, Some(signature));
921    }
922
923    #[test]
924    fn test_encode_decode_eip4844_sidecar_parity_eip155() {
925        let tx = TxEip4844 {
926            chain_id: 1,
927            nonce: 100,
928            max_fee_per_gas: 50_000_000_000,
929            max_priority_fee_per_gas: 1_000_000_000_000,
930            gas_limit: 1_000_000,
931            to: Address::random(),
932            value: U256::from(10e18),
933            input: Bytes::new(),
934            access_list: AccessList(vec![AccessListItem {
935                address: Address::random(),
936                storage_keys: vec![B256::random()],
937            }]),
938            blob_versioned_hashes: vec![B256::random()],
939            max_fee_per_blob_gas: 0,
940        };
941        let sidecar = BlobTransactionSidecar {
942            blobs: vec![[2; 131072].into()],
943            commitments: vec![[3; 48].into()],
944            proofs: vec![[4; 48].into()],
945        };
946        let tx = TxEip4844WithSidecar { tx, sidecar };
947        let signature = Signature::test_signature().with_parity(true);
948
949        let tx_signed = tx.into_signed(signature);
950        let tx_envelope: TxEnvelope = tx_signed.into();
951
952        let mut out = Vec::new();
953        tx_envelope.network_encode(&mut out);
954        let mut slice = out.as_slice();
955        let decoded = TxEnvelope::network_decode(&mut slice).unwrap();
956        assert_eq!(slice.len(), 0);
957        assert_eq!(out.len(), tx_envelope.network_len());
958        assert_eq!(decoded, tx_envelope);
959    }
960
961    #[test]
962    fn test_encode_decode_eip4844_variant_parity_eip155() {
963        let tx = TxEip4844 {
964            chain_id: 1,
965            nonce: 100,
966            max_fee_per_gas: 50_000_000_000,
967            max_priority_fee_per_gas: 1_000_000_000_000,
968            gas_limit: 1_000_000,
969            to: Address::random(),
970            value: U256::from(10e18),
971            input: Bytes::new(),
972            access_list: AccessList(vec![AccessListItem {
973                address: Address::random(),
974                storage_keys: vec![B256::random()],
975            }]),
976            blob_versioned_hashes: vec![B256::random()],
977            max_fee_per_blob_gas: 0,
978        };
979        let tx = TxEip4844Variant::TxEip4844(tx);
980        let signature = Signature::test_signature().with_parity(true);
981        test_encode_decode_roundtrip(tx, Some(signature));
982    }
983
984    #[test]
985    fn test_encode_decode_eip2930() {
986        let tx = TxEip2930 {
987            chain_id: 1u64,
988            nonce: 2,
989            gas_price: 3,
990            gas_limit: 4,
991            to: Address::left_padding_from(&[5]).into(),
992            value: U256::from(6_u64),
993            input: vec![7].into(),
994            access_list: AccessList(vec![AccessListItem {
995                address: Address::left_padding_from(&[8]),
996                storage_keys: vec![B256::left_padding_from(&[9])],
997            }]),
998        };
999        test_encode_decode_roundtrip(tx, None);
1000    }
1001
1002    #[test]
1003    fn test_encode_decode_eip7702() {
1004        let tx = TxEip7702 {
1005            chain_id: 1u64,
1006            nonce: 2,
1007            gas_limit: 3,
1008            max_fee_per_gas: 4,
1009            max_priority_fee_per_gas: 5,
1010            to: Address::left_padding_from(&[5]),
1011            value: U256::from(6_u64),
1012            input: vec![7].into(),
1013            access_list: AccessList(vec![AccessListItem {
1014                address: Address::left_padding_from(&[8]),
1015                storage_keys: vec![B256::left_padding_from(&[9])],
1016            }]),
1017            authorization_list: vec![(Authorization {
1018                chain_id: U256::from(1),
1019                address: Address::left_padding_from(&[10]),
1020                nonce: 1u64,
1021            })
1022            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1023        };
1024        test_encode_decode_roundtrip(tx, None);
1025    }
1026
1027    #[test]
1028    fn test_encode_decode_transaction_list() {
1029        let signature = Signature::test_signature();
1030        let tx = TxEnvelope::Eip1559(
1031            TxEip1559 {
1032                chain_id: 1u64,
1033                nonce: 2,
1034                max_fee_per_gas: 3,
1035                max_priority_fee_per_gas: 4,
1036                gas_limit: 5,
1037                to: Address::left_padding_from(&[6]).into(),
1038                value: U256::from(7_u64),
1039                input: vec![8].into(),
1040                access_list: Default::default(),
1041            }
1042            .into_signed(signature),
1043        );
1044        let transactions = vec![tx.clone(), tx];
1045        let encoded = alloy_rlp::encode(&transactions);
1046        let decoded = Vec::<TxEnvelope>::decode(&mut &encoded[..]).unwrap();
1047        assert_eq!(transactions, decoded);
1048    }
1049
1050    #[test]
1051    fn decode_encode_known_rpc_transaction() {
1052        // test data pulled from hive test that sends blob transactions
1053        let network_data_path =
1054            PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/rpc_blob_transaction.rlp");
1055        let data = fs::read_to_string(network_data_path).expect("Unable to read file");
1056        let hex_data = hex::decode(data.trim()).unwrap();
1057
1058        let tx: TxEnvelope = TxEnvelope::decode_2718(&mut hex_data.as_slice()).unwrap();
1059        let encoded = tx.encoded_2718();
1060        assert_eq!(encoded, hex_data);
1061        assert_eq!(tx.encode_2718_len(), hex_data.len());
1062    }
1063
1064    #[cfg(feature = "serde")]
1065    fn test_serde_roundtrip<T: SignableTransaction<Signature>>(tx: T)
1066    where
1067        Signed<T>: Into<TxEnvelope>,
1068    {
1069        let signature = Signature::test_signature();
1070        let tx_envelope: TxEnvelope = tx.into_signed(signature).into();
1071
1072        let serialized = serde_json::to_string(&tx_envelope).unwrap();
1073
1074        let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap();
1075
1076        assert_eq!(tx_envelope, deserialized);
1077    }
1078
1079    #[test]
1080    #[cfg(feature = "serde")]
1081    fn test_serde_roundtrip_legacy() {
1082        let tx = TxLegacy {
1083            chain_id: Some(1),
1084            nonce: 100,
1085            gas_price: 3_000_000_000,
1086            gas_limit: 50_000,
1087            to: Address::default().into(),
1088            value: U256::from(10e18),
1089            input: Bytes::new(),
1090        };
1091        test_serde_roundtrip(tx);
1092    }
1093
1094    #[test]
1095    #[cfg(feature = "serde")]
1096    fn test_serde_roundtrip_eip1559() {
1097        let tx = TxEip1559 {
1098            chain_id: 1,
1099            nonce: 100,
1100            max_fee_per_gas: 50_000_000_000,
1101            max_priority_fee_per_gas: 1_000_000_000_000,
1102            gas_limit: 1_000_000,
1103            to: TxKind::Create,
1104            value: U256::from(10e18),
1105            input: Bytes::new(),
1106            access_list: AccessList(vec![AccessListItem {
1107                address: Address::random(),
1108                storage_keys: vec![B256::random()],
1109            }]),
1110        };
1111        test_serde_roundtrip(tx);
1112    }
1113
1114    #[test]
1115    #[cfg(feature = "serde")]
1116    fn test_serde_roundtrip_eip2930() {
1117        let tx = TxEip2930 {
1118            chain_id: u64::MAX,
1119            nonce: u64::MAX,
1120            gas_price: u128::MAX,
1121            gas_limit: u64::MAX,
1122            to: Address::random().into(),
1123            value: U256::MAX,
1124            input: Bytes::new(),
1125            access_list: Default::default(),
1126        };
1127        test_serde_roundtrip(tx);
1128    }
1129
1130    #[test]
1131    #[cfg(feature = "serde")]
1132    fn test_serde_roundtrip_eip4844() {
1133        let tx = TxEip4844Variant::TxEip4844(TxEip4844 {
1134            chain_id: 1,
1135            nonce: 100,
1136            max_fee_per_gas: 50_000_000_000,
1137            max_priority_fee_per_gas: 1_000_000_000_000,
1138            gas_limit: 1_000_000,
1139            to: Address::random(),
1140            value: U256::from(10e18),
1141            input: Bytes::new(),
1142            access_list: AccessList(vec![AccessListItem {
1143                address: Address::random(),
1144                storage_keys: vec![B256::random()],
1145            }]),
1146            blob_versioned_hashes: vec![B256::random()],
1147            max_fee_per_blob_gas: 0,
1148        });
1149        test_serde_roundtrip(tx);
1150
1151        let tx = TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar {
1152            tx: TxEip4844 {
1153                chain_id: 1,
1154                nonce: 100,
1155                max_fee_per_gas: 50_000_000_000,
1156                max_priority_fee_per_gas: 1_000_000_000_000,
1157                gas_limit: 1_000_000,
1158                to: Address::random(),
1159                value: U256::from(10e18),
1160                input: Bytes::new(),
1161                access_list: AccessList(vec![AccessListItem {
1162                    address: Address::random(),
1163                    storage_keys: vec![B256::random()],
1164                }]),
1165                blob_versioned_hashes: vec![B256::random()],
1166                max_fee_per_blob_gas: 0,
1167            },
1168            sidecar: Default::default(),
1169        });
1170        test_serde_roundtrip(tx);
1171    }
1172
1173    #[test]
1174    #[cfg(feature = "serde")]
1175    fn test_serde_roundtrip_eip7702() {
1176        let tx = TxEip7702 {
1177            chain_id: u64::MAX,
1178            nonce: u64::MAX,
1179            gas_limit: u64::MAX,
1180            max_fee_per_gas: u128::MAX,
1181            max_priority_fee_per_gas: u128::MAX,
1182            to: Address::random(),
1183            value: U256::MAX,
1184            input: Bytes::new(),
1185            access_list: AccessList(vec![AccessListItem {
1186                address: Address::random(),
1187                storage_keys: vec![B256::random()],
1188            }]),
1189            authorization_list: vec![(Authorization {
1190                chain_id: U256::from(1),
1191                address: Address::left_padding_from(&[1]),
1192                nonce: 1u64,
1193            })
1194            .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1195        };
1196        test_serde_roundtrip(tx);
1197    }
1198
1199    #[test]
1200    #[cfg(feature = "serde")]
1201    fn serde_tx_from_contract_call() {
1202        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"}"#;
1203
1204        let te = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1205
1206        assert_eq!(
1207            *te.tx_hash(),
1208            alloy_primitives::b256!(
1209                "018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f"
1210            )
1211        );
1212    }
1213
1214    #[test]
1215    #[cfg(feature = "k256")]
1216    fn test_arbitrary_envelope() {
1217        use crate::transaction::SignerRecoverable;
1218        use arbitrary::Arbitrary;
1219        let mut unstructured = arbitrary::Unstructured::new(b"arbitrary tx envelope");
1220        let tx = TxEnvelope::arbitrary(&mut unstructured).unwrap();
1221
1222        assert!(tx.recover_signer().is_ok());
1223    }
1224
1225    #[test]
1226    #[cfg(feature = "serde")]
1227    fn test_serde_untagged_legacy() {
1228        let data = r#"{
1229            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1230            "input": "0x",
1231            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1232            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1233            "v": "0x1c",
1234            "gas": "0x15f90",
1235            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1236            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1237            "value": "0xf606682badd7800",
1238            "nonce": "0x11f398",
1239            "gasPrice": "0x4a817c800"
1240        }"#;
1241
1242        let tx: TxEnvelope = serde_json::from_str(data).unwrap();
1243
1244        assert!(matches!(tx, TxEnvelope::Legacy(_)));
1245
1246        let data_with_wrong_type = r#"{
1247            "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1248            "input": "0x",
1249            "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1250            "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1251            "v": "0x1c",
1252            "gas": "0x15f90",
1253            "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1254            "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1255            "value": "0xf606682badd7800",
1256            "nonce": "0x11f398",
1257            "gasPrice": "0x4a817c800",
1258            "type": "0x12"
1259        }"#;
1260
1261        assert!(serde_json::from_str::<TxEnvelope>(data_with_wrong_type).is_err());
1262    }
1263
1264    #[test]
1265    fn test_tx_type_try_from_u8() {
1266        assert_eq!(TxType::try_from(0u8).unwrap(), TxType::Legacy);
1267        assert_eq!(TxType::try_from(1u8).unwrap(), TxType::Eip2930);
1268        assert_eq!(TxType::try_from(2u8).unwrap(), TxType::Eip1559);
1269        assert_eq!(TxType::try_from(3u8).unwrap(), TxType::Eip4844);
1270        assert_eq!(TxType::try_from(4u8).unwrap(), TxType::Eip7702);
1271        assert!(TxType::try_from(5u8).is_err()); // Invalid case
1272    }
1273
1274    #[test]
1275    fn test_tx_type_try_from_u64() {
1276        assert_eq!(TxType::try_from(0u64).unwrap(), TxType::Legacy);
1277        assert_eq!(TxType::try_from(1u64).unwrap(), TxType::Eip2930);
1278        assert_eq!(TxType::try_from(2u64).unwrap(), TxType::Eip1559);
1279        assert_eq!(TxType::try_from(3u64).unwrap(), TxType::Eip4844);
1280        assert_eq!(TxType::try_from(4u64).unwrap(), TxType::Eip7702);
1281        assert!(TxType::try_from(10u64).is_err()); // Invalid case
1282    }
1283
1284    #[test]
1285    fn test_tx_type_from_conversions() {
1286        let legacy_tx = Signed::new_unchecked(
1287            TxLegacy::default(),
1288            Signature::test_signature(),
1289            Default::default(),
1290        );
1291        let eip2930_tx = Signed::new_unchecked(
1292            TxEip2930::default(),
1293            Signature::test_signature(),
1294            Default::default(),
1295        );
1296        let eip1559_tx = Signed::new_unchecked(
1297            TxEip1559::default(),
1298            Signature::test_signature(),
1299            Default::default(),
1300        );
1301        let eip4844_variant = Signed::new_unchecked(
1302            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1303            Signature::test_signature(),
1304            Default::default(),
1305        );
1306        let eip7702_tx = Signed::new_unchecked(
1307            TxEip7702::default(),
1308            Signature::test_signature(),
1309            Default::default(),
1310        );
1311
1312        assert!(matches!(TxEnvelope::from(legacy_tx), TxEnvelope::Legacy(_)));
1313        assert!(matches!(TxEnvelope::from(eip2930_tx), TxEnvelope::Eip2930(_)));
1314        assert!(matches!(TxEnvelope::from(eip1559_tx), TxEnvelope::Eip1559(_)));
1315        assert!(matches!(TxEnvelope::from(eip4844_variant), TxEnvelope::Eip4844(_)));
1316        assert!(matches!(TxEnvelope::from(eip7702_tx), TxEnvelope::Eip7702(_)));
1317    }
1318
1319    #[test]
1320    fn test_tx_type_is_methods() {
1321        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1322            TxLegacy::default(),
1323            Signature::test_signature(),
1324            Default::default(),
1325        ));
1326        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1327            TxEip2930::default(),
1328            Signature::test_signature(),
1329            Default::default(),
1330        ));
1331        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1332            TxEip1559::default(),
1333            Signature::test_signature(),
1334            Default::default(),
1335        ));
1336        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1337            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1338            Signature::test_signature(),
1339            Default::default(),
1340        ));
1341        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1342            TxEip7702::default(),
1343            Signature::test_signature(),
1344            Default::default(),
1345        ));
1346
1347        assert!(legacy_tx.is_legacy());
1348        assert!(!legacy_tx.is_eip2930());
1349        assert!(!legacy_tx.is_eip1559());
1350        assert!(!legacy_tx.is_eip4844());
1351        assert!(!legacy_tx.is_eip7702());
1352
1353        assert!(eip2930_tx.is_eip2930());
1354        assert!(!eip2930_tx.is_legacy());
1355        assert!(!eip2930_tx.is_eip1559());
1356        assert!(!eip2930_tx.is_eip4844());
1357        assert!(!eip2930_tx.is_eip7702());
1358
1359        assert!(eip1559_tx.is_eip1559());
1360        assert!(!eip1559_tx.is_legacy());
1361        assert!(!eip1559_tx.is_eip2930());
1362        assert!(!eip1559_tx.is_eip4844());
1363        assert!(!eip1559_tx.is_eip7702());
1364
1365        assert!(eip4844_tx.is_eip4844());
1366        assert!(!eip4844_tx.is_legacy());
1367        assert!(!eip4844_tx.is_eip2930());
1368        assert!(!eip4844_tx.is_eip1559());
1369        assert!(!eip4844_tx.is_eip7702());
1370
1371        assert!(eip7702_tx.is_eip7702());
1372        assert!(!eip7702_tx.is_legacy());
1373        assert!(!eip7702_tx.is_eip2930());
1374        assert!(!eip7702_tx.is_eip1559());
1375        assert!(!eip7702_tx.is_eip4844());
1376    }
1377
1378    #[test]
1379    fn test_tx_type() {
1380        let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1381            TxLegacy::default(),
1382            Signature::test_signature(),
1383            Default::default(),
1384        ));
1385        let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1386            TxEip2930::default(),
1387            Signature::test_signature(),
1388            Default::default(),
1389        ));
1390        let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1391            TxEip1559::default(),
1392            Signature::test_signature(),
1393            Default::default(),
1394        ));
1395        let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1396            TxEip4844Variant::TxEip4844(TxEip4844::default()),
1397            Signature::test_signature(),
1398            Default::default(),
1399        ));
1400        let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1401            TxEip7702::default(),
1402            Signature::test_signature(),
1403            Default::default(),
1404        ));
1405
1406        assert_eq!(legacy_tx.tx_type(), TxType::Legacy);
1407        assert_eq!(eip2930_tx.tx_type(), TxType::Eip2930);
1408        assert_eq!(eip1559_tx.tx_type(), TxType::Eip1559);
1409        assert_eq!(eip4844_tx.tx_type(), TxType::Eip4844);
1410        assert_eq!(eip7702_tx.tx_type(), TxType::Eip7702);
1411    }
1412
1413    // <https://sepolia.etherscan.io/getRawTx?tx=0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6>
1414    #[test]
1415    fn decode_raw_legacy() {
1416        let raw = hex!("f8aa0285018ef61d0a832dc6c094cb33aa5b38d79e3d9fa8b10aff38aa201399a7e380b844af7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce800000000000000000000000000000000000000000000000000000000000000641ca05e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664a02353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4");
1417        let tx = TxEnvelope::decode_2718(&mut raw.as_ref()).unwrap();
1418        assert!(tx.chain_id().is_none());
1419    }
1420
1421    #[test]
1422    #[cfg(feature = "serde")]
1423    fn can_deserialize_system_transaction_with_zero_signature_envelope() {
1424        let raw_tx = r#"{
1425            "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
1426            "blockNumber": "0x45a59bb",
1427            "from": "0x0000000000000000000000000000000000000000",
1428            "gas": "0x1e8480",
1429            "gasPrice": "0x0",
1430            "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
1431            "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
1432            "nonce": "0x469f7",
1433            "to": "0x4200000000000000000000000000000000000007",
1434            "transactionIndex": "0x0",
1435            "value": "0x0",
1436            "v": "0x0",
1437            "r": "0x0",
1438            "s": "0x0",
1439            "queueOrigin": "l1",
1440            "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
1441            "l1BlockNumber": "0xfd1a6c",
1442            "l1Timestamp": "0x63e434ff",
1443            "index": "0x45a59ba",
1444            "queueIndex": "0x469f7",
1445            "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
1446        }"#;
1447
1448        let tx = serde_json::from_str::<TxEnvelope>(raw_tx).unwrap();
1449
1450        assert_eq!(tx.signature().r(), U256::ZERO);
1451        assert_eq!(tx.signature().s(), U256::ZERO);
1452        assert!(!tx.signature().v());
1453
1454        assert_eq!(
1455            tx.hash(),
1456            &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
1457            "hash should match the transaction hash"
1458        );
1459    }
1460
1461    // <https://github.com/succinctlabs/kona/issues/31>
1462    #[test]
1463    #[cfg(feature = "serde")]
1464    fn serde_block_tx() {
1465        let rpc_tx = r#"{
1466      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1467      "blockNumber": "0x6edcde",
1468      "transactionIndex": "0x7",
1469      "hash": "0x2cb125e083d6d2631e3752bd2b3d757bf31bf02bfe21de0ffa46fbb118d28b19",
1470      "from": "0x03e5badf3bb1ade1a8f33f94536c827b6531948d",
1471      "to": "0x3267e72dc8780a1512fa69da7759ec66f30350e3",
1472      "input": "0x62e4c545000000000000000000000000464c8ec100f2f42fb4e42e07e203da2324f9fc6700000000000000000000000003e5badf3bb1ade1a8f33f94536c827b6531948d000000000000000000000000a064bfb5c7e81426647dc20a0d854da1538559dc00000000000000000000000000000000000000000000000000c6f3b40b6c0000",
1473      "nonce": "0x2a8",
1474      "value": "0x0",
1475      "gas": "0x28afd",
1476      "gasPrice": "0x23ec5dbc2",
1477      "accessList": [],
1478      "chainId": "0xaa36a7",
1479      "type": "0x0",
1480      "v": "0x1546d71",
1481      "r": "0x809b9f0a1777e376cd1ee5d2f551035643755edf26ea65b7a00c822a24504962",
1482      "s": "0x6a57bb8e21fe85c7e092868ee976fef71edca974d8c452fcf303f9180c764f64"
1483    }"#;
1484
1485        let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1486    }
1487
1488    // <https://github.com/succinctlabs/kona/issues/31>
1489    #[test]
1490    #[cfg(feature = "serde")]
1491    fn serde_block_tx_legacy_chain_id() {
1492        let rpc_tx = r#"{
1493      "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1494      "blockNumber": "0x6edcde",
1495      "transactionIndex": "0x8",
1496      "hash": "0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6",
1497      "from": "0x8b87f0a788cc14b4f0f374da59920f5017ff05de",
1498      "to": "0xcb33aa5b38d79e3d9fa8b10aff38aa201399a7e3",
1499      "input": "0xaf7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce80000000000000000000000000000000000000000000000000000000000000064",
1500      "nonce": "0x2",
1501      "value": "0x0",
1502      "gas": "0x2dc6c0",
1503      "gasPrice": "0x18ef61d0a",
1504      "accessList": [],
1505      "chainId": "0xaa36a7",
1506      "type": "0x0",
1507      "v": "0x1c",
1508      "r": "0x5e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664",
1509      "s": "0x2353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4"
1510    }"#;
1511
1512        let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1513    }
1514}