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