alloy_consensus/transaction/
envelope.rs

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