alloy_consensus/transaction/
envelope.rs

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