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