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