alloy_consensus/transaction/
envelope.rs

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