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