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#[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 #[default]
38 Legacy = 0,
39 Eip2930 = 1,
41 Eip1559 = 2,
43 Eip4844 = 3,
45 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#[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 Legacy(Signed<TxLegacy>),
170 Eip2930(Signed<TxEip2930>),
172 Eip1559(Signed<TxEip1559>),
174 Eip4844(Signed<TxEip4844Variant>),
182 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 #[inline]
267 pub const fn is_legacy(&self) -> bool {
268 matches!(self, Self::Legacy(_))
269 }
270
271 #[inline]
273 pub const fn is_eip2930(&self) -> bool {
274 matches!(self, Self::Eip2930(_))
275 }
276
277 #[inline]
279 pub const fn is_eip1559(&self) -> bool {
280 matches!(self, Self::Eip1559(_))
281 }
282
283 #[inline]
285 pub const fn is_eip4844(&self) -> bool {
286 matches!(self, Self::Eip4844(_))
287 }
288
289 #[inline]
291 pub const fn is_eip7702(&self) -> bool {
292 matches!(self, Self::Eip7702(_))
293 }
294
295 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 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 #[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 pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
338 match self {
339 Self::Legacy(tx) => Some(tx),
340 _ => None,
341 }
342 }
343
344 pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
346 match self {
347 Self::Eip2930(tx) => Some(tx),
348 _ => None,
349 }
350 }
351
352 pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
354 match self {
355 Self::Eip1559(tx) => Some(tx),
356 _ => None,
357 }
358 }
359
360 pub const fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
362 match self {
363 Self::Eip4844(tx) => Some(tx),
364 _ => None,
365 }
366 }
367
368 pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
370 match self {
371 Self::Eip7702(tx) => Some(tx),
372 _ => None,
373 }
374 }
375
376 #[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 #[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 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 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 #[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 #[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 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 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 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 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_res;
767 }
768
769 if let Ok(val) =
771 UntaggedLegacy::deserialize(deserializer).map(MaybeTaggedTxEnvelope::Untagged)
772 {
773 return Ok(val);
774 }
775
776 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 #[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 #[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 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 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 fn test_decode_live_4844_tx() {
1004 use crate::Transaction;
1005 use alloy_primitives::{address, b256};
1006
1007 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!(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 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()); }
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()); }
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 #[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}