1use super::SignableTransaction;
2use crate::{
3 error::ValueError,
4 transaction::{
5 eip4844::{TxEip4844, TxEip4844Variant},
6 RlpEcdsaEncodableTx, TxHashRef,
7 },
8 EthereumTypedTransaction, Signed, TransactionEnvelope, TxEip1559, TxEip2930,
9 TxEip4844WithSidecar, TxEip7702, TxLegacy,
10};
11use alloy_eips::{eip2718::Encodable2718, eip7594::Encodable7594};
12use alloy_primitives::{Bytes, Signature, B256};
13use core::fmt::Debug;
14
15pub type TxEnvelope = EthereumTxEnvelope<TxEip4844Variant>;
27
28impl<T: Encodable7594> EthereumTxEnvelope<TxEip4844Variant<T>> {
29 pub fn try_into_pooled(
34 self,
35 ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
36 match self {
37 Self::Legacy(tx) => Ok(tx.into()),
38 Self::Eip2930(tx) => Ok(tx.into()),
39 Self::Eip1559(tx) => Ok(tx.into()),
40 Self::Eip4844(tx) => EthereumTxEnvelope::try_from(tx).map_err(ValueError::convert),
41 Self::Eip7702(tx) => Ok(tx.into()),
42 }
43 }
44}
45
46impl EthereumTxEnvelope<TxEip4844> {
47 pub fn try_into_pooled<T>(
52 self,
53 ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
54 match self {
55 Self::Legacy(tx) => Ok(tx.into()),
56 Self::Eip2930(tx) => Ok(tx.into()),
57 Self::Eip1559(tx) => Ok(tx.into()),
58 Self::Eip4844(tx) => {
59 Err(ValueError::new(tx.into(), "pooled transaction requires 4844 sidecar"))
60 }
61 Self::Eip7702(tx) => Ok(tx.into()),
62 }
63 }
64
65 pub fn try_into_pooled_eip4844<T>(
71 self,
72 sidecar: T,
73 ) -> Result<EthereumTxEnvelope<TxEip4844WithSidecar<T>>, ValueError<Self>> {
74 match self {
75 Self::Eip4844(tx) => {
76 Ok(EthereumTxEnvelope::Eip4844(tx.map(|tx| tx.with_sidecar(sidecar))))
77 }
78 this => Err(ValueError::new_static(this, "Expected 4844 transaction")),
79 }
80 }
81}
82
83impl<T> EthereumTxEnvelope<T> {
84 pub fn new_unchecked(
88 transaction: EthereumTypedTransaction<T>,
89 signature: Signature,
90 hash: B256,
91 ) -> Self
92 where
93 T: RlpEcdsaEncodableTx,
94 {
95 Signed::new_unchecked(transaction, signature, hash).into()
96 }
97
98 #[deprecated(note = "Use new_unchecked() instead")]
102 pub fn new(transaction: EthereumTypedTransaction<T>, signature: Signature, hash: B256) -> Self
103 where
104 T: RlpEcdsaEncodableTx,
105 {
106 Self::new_unchecked(transaction, signature, hash)
107 }
108
109 pub fn new_unhashed(transaction: EthereumTypedTransaction<T>, signature: Signature) -> Self
114 where
115 T: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
116 {
117 transaction.into_signed(signature).into()
118 }
119
120 #[inline]
122 pub fn into_typed_transaction(self) -> EthereumTypedTransaction<T>
123 where
124 T: RlpEcdsaEncodableTx,
125 {
126 match self {
127 Self::Legacy(tx) => EthereumTypedTransaction::Legacy(tx.into_parts().0),
128 Self::Eip2930(tx) => EthereumTypedTransaction::Eip2930(tx.into_parts().0),
129 Self::Eip1559(tx) => EthereumTypedTransaction::Eip1559(tx.into_parts().0),
130 Self::Eip4844(tx) => EthereumTypedTransaction::Eip4844(tx.into_parts().0),
131 Self::Eip7702(tx) => EthereumTypedTransaction::Eip7702(tx.into_parts().0),
132 }
133 }
134
135 #[doc(hidden)]
137 pub fn input_mut(&mut self) -> &mut Bytes
138 where
139 T: AsMut<TxEip4844>,
140 {
141 match self {
142 Self::Eip1559(tx) => &mut tx.tx_mut().input,
143 Self::Eip2930(tx) => &mut tx.tx_mut().input,
144 Self::Legacy(tx) => &mut tx.tx_mut().input,
145 Self::Eip7702(tx) => &mut tx.tx_mut().input,
146 Self::Eip4844(tx) => &mut tx.tx_mut().as_mut().input,
147 }
148 }
149}
150
151#[derive(Clone, Debug, TransactionEnvelope)]
163#[envelope(alloy_consensus = crate, tx_type_name = TxType, arbitrary_cfg(feature = "arbitrary"))]
164#[doc(alias = "TransactionEnvelope")]
165pub enum EthereumTxEnvelope<Eip4844> {
166 #[envelope(ty = 0)]
168 Legacy(Signed<TxLegacy>),
169 #[envelope(ty = 1)]
171 Eip2930(Signed<TxEip2930>),
172 #[envelope(ty = 2)]
174 Eip1559(Signed<TxEip1559>),
175 #[envelope(ty = 3)]
183 Eip4844(Signed<Eip4844>),
184 #[envelope(ty = 4)]
186 Eip7702(Signed<TxEip7702>),
187}
188
189impl<T, Eip4844> From<Signed<T>> for EthereumTxEnvelope<Eip4844>
190where
191 EthereumTypedTransaction<Eip4844>: From<T>,
192 T: RlpEcdsaEncodableTx,
193{
194 fn from(v: Signed<T>) -> Self {
195 let (tx, sig, hash) = v.into_parts();
196 let typed = EthereumTypedTransaction::from(tx);
197 match typed {
198 EthereumTypedTransaction::Legacy(tx_legacy) => {
199 let tx = Signed::new_unchecked(tx_legacy, sig, hash);
200 Self::Legacy(tx)
201 }
202 EthereumTypedTransaction::Eip2930(tx_eip2930) => {
203 let tx = Signed::new_unchecked(tx_eip2930, sig, hash);
204 Self::Eip2930(tx)
205 }
206 EthereumTypedTransaction::Eip1559(tx_eip1559) => {
207 let tx = Signed::new_unchecked(tx_eip1559, sig, hash);
208 Self::Eip1559(tx)
209 }
210 EthereumTypedTransaction::Eip4844(tx_eip4844_variant) => {
211 let tx = Signed::new_unchecked(tx_eip4844_variant, sig, hash);
212 Self::Eip4844(tx)
213 }
214 EthereumTypedTransaction::Eip7702(tx_eip7702) => {
215 let tx = Signed::new_unchecked(tx_eip7702, sig, hash);
216 Self::Eip7702(tx)
217 }
218 }
219 }
220}
221
222impl<Eip4844: RlpEcdsaEncodableTx> From<EthereumTxEnvelope<Eip4844>>
223 for Signed<EthereumTypedTransaction<Eip4844>>
224where
225 EthereumTypedTransaction<Eip4844>: From<Eip4844>,
226{
227 fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
228 value.into_signed()
229 }
230}
231
232impl<Eip4844> From<(EthereumTypedTransaction<Eip4844>, Signature)> for EthereumTxEnvelope<Eip4844>
233where
234 Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
235{
236 fn from(value: (EthereumTypedTransaction<Eip4844>, Signature)) -> Self {
237 value.0.into_signed(value.1).into()
238 }
239}
240
241impl<T> From<EthereumTxEnvelope<TxEip4844WithSidecar<T>>> for EthereumTxEnvelope<TxEip4844> {
242 fn from(value: EthereumTxEnvelope<TxEip4844WithSidecar<T>>) -> Self {
243 value.map_eip4844(|eip4844| eip4844.into())
244 }
245}
246
247impl<T> From<EthereumTxEnvelope<TxEip4844Variant<T>>> for EthereumTxEnvelope<TxEip4844> {
248 fn from(value: EthereumTxEnvelope<TxEip4844Variant<T>>) -> Self {
249 value.map_eip4844(|eip4844| eip4844.into())
250 }
251}
252
253impl<T> From<EthereumTxEnvelope<TxEip4844>> for EthereumTxEnvelope<TxEip4844Variant<T>> {
254 fn from(value: EthereumTxEnvelope<TxEip4844>) -> Self {
255 value.map_eip4844(|eip4844| eip4844.into())
256 }
257}
258
259impl<Eip4844> EthereumTxEnvelope<Eip4844> {
260 pub fn map_eip4844<U>(self, f: impl FnMut(Eip4844) -> U) -> EthereumTxEnvelope<U> {
265 match self {
266 Self::Legacy(tx) => EthereumTxEnvelope::Legacy(tx),
267 Self::Eip2930(tx) => EthereumTxEnvelope::Eip2930(tx),
268 Self::Eip1559(tx) => EthereumTxEnvelope::Eip1559(tx),
269 Self::Eip4844(tx) => EthereumTxEnvelope::Eip4844(tx.map(f)),
270 Self::Eip7702(tx) => EthereumTxEnvelope::Eip7702(tx),
271 }
272 }
273
274 #[doc(alias = "transaction_type")]
276 pub const fn tx_type(&self) -> TxType {
277 match self {
278 Self::Legacy(_) => TxType::Legacy,
279 Self::Eip2930(_) => TxType::Eip2930,
280 Self::Eip1559(_) => TxType::Eip1559,
281 Self::Eip4844(_) => TxType::Eip4844,
282 Self::Eip7702(_) => TxType::Eip7702,
283 }
284 }
285
286 pub fn into_signed(self) -> Signed<EthereumTypedTransaction<Eip4844>>
288 where
289 EthereumTypedTransaction<Eip4844>: From<Eip4844>,
290 {
291 match self {
292 Self::Legacy(tx) => tx.convert(),
293 Self::Eip2930(tx) => tx.convert(),
294 Self::Eip1559(tx) => tx.convert(),
295 Self::Eip4844(tx) => tx.convert(),
296 Self::Eip7702(tx) => tx.convert(),
297 }
298 }
299}
300
301impl<Eip4844: RlpEcdsaEncodableTx> EthereumTxEnvelope<Eip4844> {
302 #[inline]
304 pub const fn is_legacy(&self) -> bool {
305 matches!(self, Self::Legacy(_))
306 }
307
308 #[inline]
310 pub const fn is_eip2930(&self) -> bool {
311 matches!(self, Self::Eip2930(_))
312 }
313
314 #[inline]
316 pub const fn is_eip1559(&self) -> bool {
317 matches!(self, Self::Eip1559(_))
318 }
319
320 #[inline]
322 pub const fn is_eip4844(&self) -> bool {
323 matches!(self, Self::Eip4844(_))
324 }
325
326 #[inline]
328 pub const fn is_eip7702(&self) -> bool {
329 matches!(self, Self::Eip7702(_))
330 }
331
332 #[inline]
341 pub const fn is_replay_protected(&self) -> bool {
342 match self {
343 Self::Legacy(tx) => tx.tx().chain_id.is_some(),
344 _ => true,
345 }
346 }
347
348 pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
350 match self {
351 Self::Legacy(tx) => Some(tx),
352 _ => None,
353 }
354 }
355
356 pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
358 match self {
359 Self::Eip2930(tx) => Some(tx),
360 _ => None,
361 }
362 }
363
364 pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
366 match self {
367 Self::Eip1559(tx) => Some(tx),
368 _ => None,
369 }
370 }
371
372 pub const fn as_eip4844(&self) -> Option<&Signed<Eip4844>> {
374 match self {
375 Self::Eip4844(tx) => Some(tx),
376 _ => None,
377 }
378 }
379
380 pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
382 match self {
383 Self::Eip7702(tx) => Some(tx),
384 _ => None,
385 }
386 }
387
388 pub fn signature_hash(&self) -> B256
390 where
391 Eip4844: SignableTransaction<Signature>,
392 {
393 match self {
394 Self::Legacy(tx) => tx.signature_hash(),
395 Self::Eip2930(tx) => tx.signature_hash(),
396 Self::Eip1559(tx) => tx.signature_hash(),
397 Self::Eip4844(tx) => tx.signature_hash(),
398 Self::Eip7702(tx) => tx.signature_hash(),
399 }
400 }
401
402 pub const fn signature(&self) -> &Signature {
404 match self {
405 Self::Legacy(tx) => tx.signature(),
406 Self::Eip2930(tx) => tx.signature(),
407 Self::Eip1559(tx) => tx.signature(),
408 Self::Eip4844(tx) => tx.signature(),
409 Self::Eip7702(tx) => tx.signature(),
410 }
411 }
412
413 #[doc(alias = "transaction_hash")]
415 pub fn tx_hash(&self) -> &B256 {
416 match self {
417 Self::Legacy(tx) => tx.hash(),
418 Self::Eip2930(tx) => tx.hash(),
419 Self::Eip1559(tx) => tx.hash(),
420 Self::Eip4844(tx) => tx.hash(),
421 Self::Eip7702(tx) => tx.hash(),
422 }
423 }
424
425 pub fn hash(&self) -> &B256 {
427 match self {
428 Self::Legacy(tx) => tx.hash(),
429 Self::Eip2930(tx) => tx.hash(),
430 Self::Eip1559(tx) => tx.hash(),
431 Self::Eip7702(tx) => tx.hash(),
432 Self::Eip4844(tx) => tx.hash(),
433 }
434 }
435
436 pub fn eip2718_encoded_length(&self) -> usize {
438 match self {
439 Self::Legacy(t) => t.eip2718_encoded_length(),
440 Self::Eip2930(t) => t.eip2718_encoded_length(),
441 Self::Eip1559(t) => t.eip2718_encoded_length(),
442 Self::Eip4844(t) => t.eip2718_encoded_length(),
443 Self::Eip7702(t) => t.eip2718_encoded_length(),
444 }
445 }
446}
447
448impl<Eip4844: RlpEcdsaEncodableTx> TxHashRef for EthereumTxEnvelope<Eip4844> {
449 fn tx_hash(&self) -> &B256 {
450 Self::tx_hash(self)
451 }
452}
453
454#[cfg(any(feature = "secp256k1", feature = "k256"))]
455impl<Eip4844> crate::transaction::SignerRecoverable for EthereumTxEnvelope<Eip4844>
456where
457 Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
458{
459 fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
460 match self {
461 Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
462 Self::Eip2930(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
463 Self::Eip1559(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
464 Self::Eip4844(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
465 Self::Eip7702(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
466 }
467 }
468
469 fn recover_signer_unchecked(
470 &self,
471 ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
472 match self {
473 Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer_unchecked(tx),
474 Self::Eip2930(tx) => {
475 crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
476 }
477 Self::Eip1559(tx) => {
478 crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
479 }
480 Self::Eip4844(tx) => {
481 crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
482 }
483 Self::Eip7702(tx) => {
484 crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
485 }
486 }
487 }
488
489 fn recover_unchecked_with_buf(
490 &self,
491 buf: &mut alloc::vec::Vec<u8>,
492 ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
493 match self {
494 Self::Legacy(tx) => {
495 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
496 }
497 Self::Eip2930(tx) => {
498 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
499 }
500 Self::Eip1559(tx) => {
501 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
502 }
503 Self::Eip4844(tx) => {
504 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
505 }
506 Self::Eip7702(tx) => {
507 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
508 }
509 }
510 }
511}
512
513#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
515pub mod serde_bincode_compat {
516 use crate::{EthereumTypedTransaction, Signed};
517 use alloc::borrow::Cow;
518 use alloy_primitives::Signature;
519 use serde::{Deserialize, Deserializer, Serialize, Serializer};
520 use serde_with::{DeserializeAs, SerializeAs};
521
522 #[derive(Debug, Serialize, Deserialize)]
538 pub struct EthereumTxEnvelope<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
539 signature: Signature,
541 transaction:
543 crate::serde_bincode_compat::transaction::EthereumTypedTransaction<'a, Eip4844>,
544 }
545
546 impl<'a, T: Clone> From<&'a super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'a, T> {
547 fn from(value: &'a super::EthereumTxEnvelope<T>) -> Self {
548 match value {
549 super::EthereumTxEnvelope::Legacy(tx) => Self {
550 signature: *tx.signature(),
551 transaction:
552 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Legacy(
553 tx.tx().into(),
554 ),
555 },
556 super::EthereumTxEnvelope::Eip2930(tx) => Self {
557 signature: *tx.signature(),
558 transaction:
559 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip2930(
560 tx.tx().into(),
561 ),
562 },
563 super::EthereumTxEnvelope::Eip1559(tx) => Self {
564 signature: *tx.signature(),
565 transaction:
566 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip1559(
567 tx.tx().into(),
568 ),
569 },
570 super::EthereumTxEnvelope::Eip4844(tx) => Self {
571 signature: *tx.signature(),
572 transaction:
573 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip4844(
574 Cow::Borrowed(tx.tx()),
575 ),
576 },
577 super::EthereumTxEnvelope::Eip7702(tx) => Self {
578 signature: *tx.signature(),
579 transaction:
580 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip7702(
581 tx.tx().into(),
582 ),
583 },
584 }
585 }
586 }
587
588 impl<'a, T: Clone> From<EthereumTxEnvelope<'a, T>> for super::EthereumTxEnvelope<T> {
589 fn from(value: EthereumTxEnvelope<'a, T>) -> Self {
590 let EthereumTxEnvelope { signature, transaction } = value;
591 let transaction: crate::transaction::typed::EthereumTypedTransaction<T> =
592 transaction.into();
593 match transaction {
594 EthereumTypedTransaction::Legacy(tx) => Signed::new_unhashed(tx, signature).into(),
595 EthereumTypedTransaction::Eip2930(tx) => Signed::new_unhashed(tx, signature).into(),
596 EthereumTypedTransaction::Eip1559(tx) => Signed::new_unhashed(tx, signature).into(),
597 EthereumTypedTransaction::Eip4844(tx) => {
598 Self::Eip4844(Signed::new_unhashed(tx, signature))
599 }
600 EthereumTypedTransaction::Eip7702(tx) => Signed::new_unhashed(tx, signature).into(),
601 }
602 }
603 }
604
605 impl<T: Serialize + Clone> SerializeAs<super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'_, T> {
606 fn serialize_as<S>(
607 source: &super::EthereumTxEnvelope<T>,
608 serializer: S,
609 ) -> Result<S::Ok, S::Error>
610 where
611 S: Serializer,
612 {
613 EthereumTxEnvelope::<'_, T>::from(source).serialize(serializer)
614 }
615 }
616
617 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTxEnvelope<T>>
618 for EthereumTxEnvelope<'de, T>
619 {
620 fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumTxEnvelope<T>, D::Error>
621 where
622 D: Deserializer<'de>,
623 {
624 EthereumTxEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
625 }
626 }
627
628 #[cfg(test)]
629 mod tests {
630 use super::super::{serde_bincode_compat, EthereumTxEnvelope};
631 use crate::TxEip4844;
632 use arbitrary::Arbitrary;
633 use bincode::config;
634 use rand::Rng;
635 use serde::{Deserialize, Serialize};
636 use serde_with::serde_as;
637
638 #[test]
639 fn test_typed_tx_envelope_bincode_roundtrip() {
640 #[serde_as]
641 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
642 struct Data {
643 #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_>")]
644 transaction: EthereumTxEnvelope<TxEip4844>,
645 }
646
647 let mut bytes = [0u8; 1024];
648 rand::thread_rng().fill(bytes.as_mut_slice());
649 let data = Data {
650 transaction: EthereumTxEnvelope::arbitrary(&mut arbitrary::Unstructured::new(
651 &bytes,
652 ))
653 .unwrap(),
654 };
655
656 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
657 let (decoded, _) =
658 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
659 assert_eq!(decoded, data);
660 }
661 }
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667 use crate::{
668 transaction::{Recovered, SignableTransaction},
669 Transaction, TxEip4844, TxEip4844WithSidecar,
670 };
671 use alloc::vec::Vec;
672 use alloy_eips::{
673 eip2930::{AccessList, AccessListItem},
674 eip4844::BlobTransactionSidecar,
675 eip7702::Authorization,
676 };
677 #[allow(unused_imports)]
678 use alloy_primitives::{b256, Bytes, TxKind};
679 use alloy_primitives::{hex, Address, Signature, U256};
680 use alloy_rlp::Decodable;
681 use std::{fs, path::PathBuf, str::FromStr, vec};
682
683 #[test]
684 fn assert_encodable() {
685 fn assert_encodable<T: Encodable2718>() {}
686
687 assert_encodable::<EthereumTxEnvelope<TxEip4844>>();
688 assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844>>>();
689 assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844Variant>>>();
690 }
691
692 #[test]
693 #[cfg(feature = "k256")]
694 fn test_decode_live_1559_tx() {
696 use alloy_primitives::address;
697
698 let raw_tx = alloy_primitives::hex::decode("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8").unwrap();
699 let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
700
701 assert_eq!(res.tx_type(), TxType::Eip1559);
702
703 let tx = match res {
704 TxEnvelope::Eip1559(tx) => tx,
705 _ => unreachable!(),
706 };
707
708 assert_eq!(tx.tx().to, TxKind::Call(address!("D9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
709 let from = tx.recover_signer().unwrap();
710 assert_eq!(from, address!("001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
711 }
712
713 #[test]
714 fn test_is_replay_protected_v() {
715 let sig = Signature::test_signature();
716 assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
717 TxLegacy::default(),
718 sig,
719 Default::default(),
720 ))
721 .is_replay_protected());
722 let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565");
723 let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1");
724 let v = false;
725 let valid_sig = Signature::from_scalars_and_parity(r, s, v);
726 assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
727 TxLegacy::default(),
728 valid_sig,
729 Default::default(),
730 ))
731 .is_replay_protected());
732 assert!(&TxEnvelope::Eip2930(Signed::new_unchecked(
733 TxEip2930::default(),
734 sig,
735 Default::default(),
736 ))
737 .is_replay_protected());
738 assert!(&TxEnvelope::Eip1559(Signed::new_unchecked(
739 TxEip1559::default(),
740 sig,
741 Default::default(),
742 ))
743 .is_replay_protected());
744 assert!(&TxEnvelope::Eip4844(Signed::new_unchecked(
745 TxEip4844Variant::TxEip4844(TxEip4844::default()),
746 sig,
747 Default::default(),
748 ))
749 .is_replay_protected());
750 assert!(&TxEnvelope::Eip7702(Signed::new_unchecked(
751 TxEip7702::default(),
752 sig,
753 Default::default(),
754 ))
755 .is_replay_protected());
756 }
757
758 #[test]
759 #[cfg(feature = "k256")]
760 fn test_decode_live_legacy_tx() {
762 use alloy_primitives::address;
763
764 let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
765 let res = TxEnvelope::decode_2718(&mut raw_tx.as_ref()).unwrap();
766 assert_eq!(res.tx_type(), TxType::Legacy);
767
768 let tx = match res {
769 TxEnvelope::Legacy(tx) => tx,
770 _ => unreachable!(),
771 };
772
773 assert_eq!(tx.tx().chain_id(), Some(1));
774
775 assert_eq!(tx.tx().to, TxKind::Call(address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D")));
776 assert_eq!(
777 tx.hash().to_string(),
778 "0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4"
779 );
780 let from = tx.recover_signer().unwrap();
781 assert_eq!(from, address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
782 }
783
784 #[test]
785 #[cfg(feature = "k256")]
786 fn test_decode_live_4844_tx() {
789 use crate::Transaction;
790 use alloy_primitives::{address, b256};
791
792 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
794
795 let res = TxEnvelope::decode_2718(&mut raw_tx.as_slice()).unwrap();
796 assert_eq!(res.tx_type(), TxType::Eip4844);
797
798 let tx = match res {
799 TxEnvelope::Eip4844(tx) => tx,
800 _ => unreachable!(),
801 };
802
803 assert_eq!(
804 tx.tx().kind(),
805 TxKind::Call(address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064"))
806 );
807
808 assert!(matches!(tx.tx(), TxEip4844Variant::TxEip4844(_)));
810
811 assert_eq!(
812 tx.tx().tx().blob_versioned_hashes,
813 vec![
814 b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
815 b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
816 b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
817 b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
818 b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
819 ]
820 );
821
822 let from = tx.recover_signer().unwrap();
823 assert_eq!(from, address!("0xA83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
824 }
825
826 fn test_encode_decode_roundtrip<T: SignableTransaction<Signature>>(
827 tx: T,
828 signature: Option<Signature>,
829 ) where
830 Signed<T>: Into<TxEnvelope>,
831 {
832 let signature = signature.unwrap_or_else(Signature::test_signature);
833 let tx_signed = tx.into_signed(signature);
834 let tx_envelope: TxEnvelope = tx_signed.into();
835 let encoded = tx_envelope.encoded_2718();
836 let mut slice = encoded.as_slice();
837 let decoded = TxEnvelope::decode_2718(&mut slice).unwrap();
838 assert_eq!(encoded.len(), tx_envelope.encode_2718_len());
839 assert_eq!(decoded, tx_envelope);
840 assert_eq!(slice.len(), 0);
841 }
842
843 #[test]
844 fn test_encode_decode_legacy() {
845 let tx = TxLegacy {
846 chain_id: None,
847 nonce: 2,
848 gas_limit: 1000000,
849 gas_price: 10000000000,
850 to: Address::left_padding_from(&[6]).into(),
851 value: U256::from(7_u64),
852 ..Default::default()
853 };
854 test_encode_decode_roundtrip(tx, Some(Signature::test_signature().with_parity(true)));
855 }
856
857 #[test]
858 fn test_encode_decode_eip1559() {
859 let tx = TxEip1559 {
860 chain_id: 1u64,
861 nonce: 2,
862 max_fee_per_gas: 3,
863 max_priority_fee_per_gas: 4,
864 gas_limit: 5,
865 to: Address::left_padding_from(&[6]).into(),
866 value: U256::from(7_u64),
867 input: vec![8].into(),
868 access_list: Default::default(),
869 };
870 test_encode_decode_roundtrip(tx, None);
871 }
872
873 #[test]
874 fn test_encode_decode_eip1559_parity_eip155() {
875 let tx = TxEip1559 {
876 chain_id: 1u64,
877 nonce: 2,
878 max_fee_per_gas: 3,
879 max_priority_fee_per_gas: 4,
880 gas_limit: 5,
881 to: Address::left_padding_from(&[6]).into(),
882 value: U256::from(7_u64),
883 input: vec![8].into(),
884 access_list: Default::default(),
885 };
886 let signature = Signature::test_signature().with_parity(true);
887
888 test_encode_decode_roundtrip(tx, Some(signature));
889 }
890
891 #[test]
892 fn test_encode_decode_eip2930_parity_eip155() {
893 let tx = TxEip2930 {
894 chain_id: 1u64,
895 nonce: 2,
896 gas_price: 3,
897 gas_limit: 4,
898 to: Address::left_padding_from(&[5]).into(),
899 value: U256::from(6_u64),
900 input: vec![7].into(),
901 access_list: Default::default(),
902 };
903 let signature = Signature::test_signature().with_parity(true);
904 test_encode_decode_roundtrip(tx, Some(signature));
905 }
906
907 #[test]
908 fn test_encode_decode_eip4844_parity_eip155() {
909 let tx = TxEip4844 {
910 chain_id: 1,
911 nonce: 100,
912 max_fee_per_gas: 50_000_000_000,
913 max_priority_fee_per_gas: 1_000_000_000_000,
914 gas_limit: 1_000_000,
915 to: Address::random(),
916 value: U256::from(10e18),
917 input: Bytes::new(),
918 access_list: AccessList(vec![AccessListItem {
919 address: Address::random(),
920 storage_keys: vec![B256::random()],
921 }]),
922 blob_versioned_hashes: vec![B256::random()],
923 max_fee_per_blob_gas: 0,
924 };
925 let signature = Signature::test_signature().with_parity(true);
926 test_encode_decode_roundtrip(tx, Some(signature));
927 }
928
929 #[test]
930 fn test_encode_decode_eip4844_sidecar_parity_eip155() {
931 let tx = TxEip4844 {
932 chain_id: 1,
933 nonce: 100,
934 max_fee_per_gas: 50_000_000_000,
935 max_priority_fee_per_gas: 1_000_000_000_000,
936 gas_limit: 1_000_000,
937 to: Address::random(),
938 value: U256::from(10e18),
939 input: Bytes::new(),
940 access_list: AccessList(vec![AccessListItem {
941 address: Address::random(),
942 storage_keys: vec![B256::random()],
943 }]),
944 blob_versioned_hashes: vec![B256::random()],
945 max_fee_per_blob_gas: 0,
946 };
947 let sidecar = BlobTransactionSidecar {
948 blobs: vec![[2; 131072].into()],
949 commitments: vec![[3; 48].into()],
950 proofs: vec![[4; 48].into()],
951 };
952 let tx = TxEip4844WithSidecar { tx, sidecar };
953 let signature = Signature::test_signature().with_parity(true);
954
955 let tx_signed = tx.into_signed(signature);
956 let tx_envelope: TxEnvelope = tx_signed.into();
957
958 let mut out = Vec::new();
959 tx_envelope.network_encode(&mut out);
960 let mut slice = out.as_slice();
961 let decoded = TxEnvelope::network_decode(&mut slice).unwrap();
962 assert_eq!(slice.len(), 0);
963 assert_eq!(out.len(), tx_envelope.network_len());
964 assert_eq!(decoded, tx_envelope);
965 }
966
967 #[test]
968 fn test_encode_decode_eip4844_variant_parity_eip155() {
969 let tx = TxEip4844 {
970 chain_id: 1,
971 nonce: 100,
972 max_fee_per_gas: 50_000_000_000,
973 max_priority_fee_per_gas: 1_000_000_000_000,
974 gas_limit: 1_000_000,
975 to: Address::random(),
976 value: U256::from(10e18),
977 input: Bytes::new(),
978 access_list: AccessList(vec![AccessListItem {
979 address: Address::random(),
980 storage_keys: vec![B256::random()],
981 }]),
982 blob_versioned_hashes: vec![B256::random()],
983 max_fee_per_blob_gas: 0,
984 };
985 let tx = TxEip4844Variant::TxEip4844(tx);
986 let signature = Signature::test_signature().with_parity(true);
987 test_encode_decode_roundtrip(tx, Some(signature));
988 }
989
990 #[test]
991 fn test_encode_decode_eip2930() {
992 let tx = TxEip2930 {
993 chain_id: 1u64,
994 nonce: 2,
995 gas_price: 3,
996 gas_limit: 4,
997 to: Address::left_padding_from(&[5]).into(),
998 value: U256::from(6_u64),
999 input: vec![7].into(),
1000 access_list: AccessList(vec![AccessListItem {
1001 address: Address::left_padding_from(&[8]),
1002 storage_keys: vec![B256::left_padding_from(&[9])],
1003 }]),
1004 };
1005 test_encode_decode_roundtrip(tx, None);
1006 }
1007
1008 #[test]
1009 fn test_encode_decode_eip7702() {
1010 let tx = TxEip7702 {
1011 chain_id: 1u64,
1012 nonce: 2,
1013 gas_limit: 3,
1014 max_fee_per_gas: 4,
1015 max_priority_fee_per_gas: 5,
1016 to: Address::left_padding_from(&[5]),
1017 value: U256::from(6_u64),
1018 input: vec![7].into(),
1019 access_list: AccessList(vec![AccessListItem {
1020 address: Address::left_padding_from(&[8]),
1021 storage_keys: vec![B256::left_padding_from(&[9])],
1022 }]),
1023 authorization_list: vec![(Authorization {
1024 chain_id: U256::from(1),
1025 address: Address::left_padding_from(&[10]),
1026 nonce: 1u64,
1027 })
1028 .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1029 };
1030 test_encode_decode_roundtrip(tx, None);
1031 }
1032
1033 #[test]
1034 fn test_encode_decode_transaction_list() {
1035 let signature = Signature::test_signature();
1036 let tx = TxEnvelope::Eip1559(
1037 TxEip1559 {
1038 chain_id: 1u64,
1039 nonce: 2,
1040 max_fee_per_gas: 3,
1041 max_priority_fee_per_gas: 4,
1042 gas_limit: 5,
1043 to: Address::left_padding_from(&[6]).into(),
1044 value: U256::from(7_u64),
1045 input: vec![8].into(),
1046 access_list: Default::default(),
1047 }
1048 .into_signed(signature),
1049 );
1050 let transactions = vec![tx.clone(), tx];
1051 let encoded = alloy_rlp::encode(&transactions);
1052 let decoded = Vec::<TxEnvelope>::decode(&mut &encoded[..]).unwrap();
1053 assert_eq!(transactions, decoded);
1054 }
1055
1056 #[test]
1057 fn decode_encode_known_rpc_transaction() {
1058 let network_data_path =
1060 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/rpc_blob_transaction.rlp");
1061 let data = fs::read_to_string(network_data_path).expect("Unable to read file");
1062 let hex_data = hex::decode(data.trim()).unwrap();
1063
1064 let tx: TxEnvelope = TxEnvelope::decode_2718(&mut hex_data.as_slice()).unwrap();
1065 let encoded = tx.encoded_2718();
1066 assert_eq!(encoded, hex_data);
1067 assert_eq!(tx.encode_2718_len(), hex_data.len());
1068 }
1069
1070 #[cfg(feature = "serde")]
1071 fn test_serde_roundtrip<T: SignableTransaction<Signature>>(tx: T)
1072 where
1073 Signed<T>: Into<TxEnvelope>,
1074 {
1075 let signature = Signature::test_signature();
1076 let tx_envelope: TxEnvelope = tx.into_signed(signature).into();
1077
1078 let serialized = serde_json::to_string(&tx_envelope).unwrap();
1079
1080 let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap();
1081
1082 assert_eq!(tx_envelope, deserialized);
1083 }
1084
1085 #[test]
1086 #[cfg(feature = "serde")]
1087 fn test_serde_roundtrip_legacy() {
1088 let tx = TxLegacy {
1089 chain_id: Some(1),
1090 nonce: 100,
1091 gas_price: 3_000_000_000,
1092 gas_limit: 50_000,
1093 to: Address::default().into(),
1094 value: U256::from(10e18),
1095 input: Bytes::new(),
1096 };
1097 test_serde_roundtrip(tx);
1098 }
1099
1100 #[test]
1101 #[cfg(feature = "serde")]
1102 fn test_serde_roundtrip_eip1559() {
1103 let tx = TxEip1559 {
1104 chain_id: 1,
1105 nonce: 100,
1106 max_fee_per_gas: 50_000_000_000,
1107 max_priority_fee_per_gas: 1_000_000_000_000,
1108 gas_limit: 1_000_000,
1109 to: TxKind::Create,
1110 value: U256::from(10e18),
1111 input: Bytes::new(),
1112 access_list: AccessList(vec![AccessListItem {
1113 address: Address::random(),
1114 storage_keys: vec![B256::random()],
1115 }]),
1116 };
1117 test_serde_roundtrip(tx);
1118 }
1119
1120 #[test]
1121 #[cfg(feature = "serde")]
1122 fn test_serde_roundtrip_eip2930() {
1123 let tx = TxEip2930 {
1124 chain_id: u64::MAX,
1125 nonce: u64::MAX,
1126 gas_price: u128::MAX,
1127 gas_limit: u64::MAX,
1128 to: Address::random().into(),
1129 value: U256::MAX,
1130 input: Bytes::new(),
1131 access_list: Default::default(),
1132 };
1133 test_serde_roundtrip(tx);
1134 }
1135
1136 #[test]
1137 #[cfg(feature = "serde")]
1138 fn test_serde_roundtrip_eip4844() {
1139 let tx = TxEip4844Variant::TxEip4844(TxEip4844 {
1140 chain_id: 1,
1141 nonce: 100,
1142 max_fee_per_gas: 50_000_000_000,
1143 max_priority_fee_per_gas: 1_000_000_000_000,
1144 gas_limit: 1_000_000,
1145 to: Address::random(),
1146 value: U256::from(10e18),
1147 input: Bytes::new(),
1148 access_list: AccessList(vec![AccessListItem {
1149 address: Address::random(),
1150 storage_keys: vec![B256::random()],
1151 }]),
1152 blob_versioned_hashes: vec![B256::random()],
1153 max_fee_per_blob_gas: 0,
1154 });
1155 test_serde_roundtrip(tx);
1156
1157 let tx = TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar {
1158 tx: TxEip4844 {
1159 chain_id: 1,
1160 nonce: 100,
1161 max_fee_per_gas: 50_000_000_000,
1162 max_priority_fee_per_gas: 1_000_000_000_000,
1163 gas_limit: 1_000_000,
1164 to: Address::random(),
1165 value: U256::from(10e18),
1166 input: Bytes::new(),
1167 access_list: AccessList(vec![AccessListItem {
1168 address: Address::random(),
1169 storage_keys: vec![B256::random()],
1170 }]),
1171 blob_versioned_hashes: vec![B256::random()],
1172 max_fee_per_blob_gas: 0,
1173 },
1174 sidecar: Default::default(),
1175 });
1176 test_serde_roundtrip(tx);
1177 }
1178
1179 #[test]
1180 #[cfg(feature = "serde")]
1181 fn test_serde_roundtrip_eip7702() {
1182 let tx = TxEip7702 {
1183 chain_id: u64::MAX,
1184 nonce: u64::MAX,
1185 gas_limit: u64::MAX,
1186 max_fee_per_gas: u128::MAX,
1187 max_priority_fee_per_gas: u128::MAX,
1188 to: Address::random(),
1189 value: U256::MAX,
1190 input: Bytes::new(),
1191 access_list: AccessList(vec![AccessListItem {
1192 address: Address::random(),
1193 storage_keys: vec![B256::random()],
1194 }]),
1195 authorization_list: vec![(Authorization {
1196 chain_id: U256::from(1),
1197 address: Address::left_padding_from(&[1]),
1198 nonce: 1u64,
1199 })
1200 .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1201 };
1202 test_serde_roundtrip(tx);
1203 }
1204
1205 #[test]
1206 #[cfg(feature = "serde")]
1207 fn serde_tx_from_contract_call() {
1208 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"}"#;
1209
1210 let te = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1211
1212 assert_eq!(
1213 *te.tx_hash(),
1214 alloy_primitives::b256!(
1215 "018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f"
1216 )
1217 );
1218 }
1219
1220 #[test]
1221 #[cfg(feature = "k256")]
1222 fn test_arbitrary_envelope() {
1223 use crate::transaction::SignerRecoverable;
1224 use arbitrary::Arbitrary;
1225 let mut unstructured = arbitrary::Unstructured::new(b"arbitrary tx envelope");
1226 let tx = TxEnvelope::arbitrary(&mut unstructured).unwrap();
1227
1228 assert!(tx.recover_signer().is_ok());
1229 }
1230
1231 #[test]
1232 #[cfg(feature = "serde")]
1233 fn test_serde_untagged_legacy() {
1234 let data = r#"{
1235 "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1236 "input": "0x",
1237 "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1238 "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1239 "v": "0x1c",
1240 "gas": "0x15f90",
1241 "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1242 "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1243 "value": "0xf606682badd7800",
1244 "nonce": "0x11f398",
1245 "gasPrice": "0x4a817c800"
1246 }"#;
1247
1248 let tx: TxEnvelope = serde_json::from_str(data).unwrap();
1249
1250 assert!(matches!(tx, TxEnvelope::Legacy(_)));
1251
1252 let data_with_wrong_type = r#"{
1253 "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1254 "input": "0x",
1255 "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1256 "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1257 "v": "0x1c",
1258 "gas": "0x15f90",
1259 "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1260 "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1261 "value": "0xf606682badd7800",
1262 "nonce": "0x11f398",
1263 "gasPrice": "0x4a817c800",
1264 "type": "0x12"
1265 }"#;
1266
1267 assert!(serde_json::from_str::<TxEnvelope>(data_with_wrong_type).is_err());
1268 }
1269
1270 #[test]
1271 fn test_tx_type_try_from_u8() {
1272 assert_eq!(TxType::try_from(0u8).unwrap(), TxType::Legacy);
1273 assert_eq!(TxType::try_from(1u8).unwrap(), TxType::Eip2930);
1274 assert_eq!(TxType::try_from(2u8).unwrap(), TxType::Eip1559);
1275 assert_eq!(TxType::try_from(3u8).unwrap(), TxType::Eip4844);
1276 assert_eq!(TxType::try_from(4u8).unwrap(), TxType::Eip7702);
1277 assert!(TxType::try_from(5u8).is_err()); }
1279
1280 #[test]
1281 fn test_tx_type_try_from_u64() {
1282 assert_eq!(TxType::try_from(0u64).unwrap(), TxType::Legacy);
1283 assert_eq!(TxType::try_from(1u64).unwrap(), TxType::Eip2930);
1284 assert_eq!(TxType::try_from(2u64).unwrap(), TxType::Eip1559);
1285 assert_eq!(TxType::try_from(3u64).unwrap(), TxType::Eip4844);
1286 assert_eq!(TxType::try_from(4u64).unwrap(), TxType::Eip7702);
1287 assert!(TxType::try_from(10u64).is_err()); }
1289
1290 #[test]
1291 fn test_tx_type_from_conversions() {
1292 let legacy_tx = Signed::new_unchecked(
1293 TxLegacy::default(),
1294 Signature::test_signature(),
1295 Default::default(),
1296 );
1297 let eip2930_tx = Signed::new_unchecked(
1298 TxEip2930::default(),
1299 Signature::test_signature(),
1300 Default::default(),
1301 );
1302 let eip1559_tx = Signed::new_unchecked(
1303 TxEip1559::default(),
1304 Signature::test_signature(),
1305 Default::default(),
1306 );
1307 let eip4844_variant = Signed::new_unchecked(
1308 TxEip4844Variant::TxEip4844(TxEip4844::default()),
1309 Signature::test_signature(),
1310 Default::default(),
1311 );
1312 let eip7702_tx = Signed::new_unchecked(
1313 TxEip7702::default(),
1314 Signature::test_signature(),
1315 Default::default(),
1316 );
1317
1318 assert!(matches!(TxEnvelope::from(legacy_tx), TxEnvelope::Legacy(_)));
1319 assert!(matches!(TxEnvelope::from(eip2930_tx), TxEnvelope::Eip2930(_)));
1320 assert!(matches!(TxEnvelope::from(eip1559_tx), TxEnvelope::Eip1559(_)));
1321 assert!(matches!(TxEnvelope::from(eip4844_variant), TxEnvelope::Eip4844(_)));
1322 assert!(matches!(TxEnvelope::from(eip7702_tx), TxEnvelope::Eip7702(_)));
1323 }
1324
1325 #[test]
1326 fn test_tx_type_is_methods() {
1327 let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1328 TxLegacy::default(),
1329 Signature::test_signature(),
1330 Default::default(),
1331 ));
1332 let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1333 TxEip2930::default(),
1334 Signature::test_signature(),
1335 Default::default(),
1336 ));
1337 let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1338 TxEip1559::default(),
1339 Signature::test_signature(),
1340 Default::default(),
1341 ));
1342 let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1343 TxEip4844Variant::TxEip4844(TxEip4844::default()),
1344 Signature::test_signature(),
1345 Default::default(),
1346 ));
1347 let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1348 TxEip7702::default(),
1349 Signature::test_signature(),
1350 Default::default(),
1351 ));
1352
1353 assert!(legacy_tx.is_legacy());
1354 assert!(!legacy_tx.is_eip2930());
1355 assert!(!legacy_tx.is_eip1559());
1356 assert!(!legacy_tx.is_eip4844());
1357 assert!(!legacy_tx.is_eip7702());
1358
1359 assert!(eip2930_tx.is_eip2930());
1360 assert!(!eip2930_tx.is_legacy());
1361 assert!(!eip2930_tx.is_eip1559());
1362 assert!(!eip2930_tx.is_eip4844());
1363 assert!(!eip2930_tx.is_eip7702());
1364
1365 assert!(eip1559_tx.is_eip1559());
1366 assert!(!eip1559_tx.is_legacy());
1367 assert!(!eip1559_tx.is_eip2930());
1368 assert!(!eip1559_tx.is_eip4844());
1369 assert!(!eip1559_tx.is_eip7702());
1370
1371 assert!(eip4844_tx.is_eip4844());
1372 assert!(!eip4844_tx.is_legacy());
1373 assert!(!eip4844_tx.is_eip2930());
1374 assert!(!eip4844_tx.is_eip1559());
1375 assert!(!eip4844_tx.is_eip7702());
1376
1377 assert!(eip7702_tx.is_eip7702());
1378 assert!(!eip7702_tx.is_legacy());
1379 assert!(!eip7702_tx.is_eip2930());
1380 assert!(!eip7702_tx.is_eip1559());
1381 assert!(!eip7702_tx.is_eip4844());
1382 }
1383
1384 #[test]
1385 fn test_tx_type() {
1386 let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1387 TxLegacy::default(),
1388 Signature::test_signature(),
1389 Default::default(),
1390 ));
1391 let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1392 TxEip2930::default(),
1393 Signature::test_signature(),
1394 Default::default(),
1395 ));
1396 let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1397 TxEip1559::default(),
1398 Signature::test_signature(),
1399 Default::default(),
1400 ));
1401 let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1402 TxEip4844Variant::TxEip4844(TxEip4844::default()),
1403 Signature::test_signature(),
1404 Default::default(),
1405 ));
1406 let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1407 TxEip7702::default(),
1408 Signature::test_signature(),
1409 Default::default(),
1410 ));
1411
1412 assert_eq!(legacy_tx.tx_type(), TxType::Legacy);
1413 assert_eq!(eip2930_tx.tx_type(), TxType::Eip2930);
1414 assert_eq!(eip1559_tx.tx_type(), TxType::Eip1559);
1415 assert_eq!(eip4844_tx.tx_type(), TxType::Eip4844);
1416 assert_eq!(eip7702_tx.tx_type(), TxType::Eip7702);
1417 }
1418
1419 #[test]
1421 fn decode_raw_legacy() {
1422 let raw = hex!("f8aa0285018ef61d0a832dc6c094cb33aa5b38d79e3d9fa8b10aff38aa201399a7e380b844af7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce800000000000000000000000000000000000000000000000000000000000000641ca05e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664a02353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4");
1423 let tx = TxEnvelope::decode_2718(&mut raw.as_ref()).unwrap();
1424 assert!(tx.chain_id().is_none());
1425 }
1426
1427 #[test]
1428 #[cfg(feature = "serde")]
1429 fn can_deserialize_system_transaction_with_zero_signature_envelope() {
1430 let raw_tx = r#"{
1431 "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
1432 "blockNumber": "0x45a59bb",
1433 "from": "0x0000000000000000000000000000000000000000",
1434 "gas": "0x1e8480",
1435 "gasPrice": "0x0",
1436 "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
1437 "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
1438 "nonce": "0x469f7",
1439 "to": "0x4200000000000000000000000000000000000007",
1440 "transactionIndex": "0x0",
1441 "value": "0x0",
1442 "v": "0x0",
1443 "r": "0x0",
1444 "s": "0x0",
1445 "queueOrigin": "l1",
1446 "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
1447 "l1BlockNumber": "0xfd1a6c",
1448 "l1Timestamp": "0x63e434ff",
1449 "index": "0x45a59ba",
1450 "queueIndex": "0x469f7",
1451 "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
1452 }"#;
1453
1454 let tx = serde_json::from_str::<TxEnvelope>(raw_tx).unwrap();
1455
1456 assert_eq!(tx.signature().r(), U256::ZERO);
1457 assert_eq!(tx.signature().s(), U256::ZERO);
1458 assert!(!tx.signature().v());
1459
1460 assert_eq!(
1461 tx.hash(),
1462 &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
1463 "hash should match the transaction hash"
1464 );
1465 }
1466
1467 #[test]
1469 #[cfg(feature = "serde")]
1470 fn serde_block_tx() {
1471 let rpc_tx = r#"{
1472 "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1473 "blockNumber": "0x6edcde",
1474 "transactionIndex": "0x7",
1475 "hash": "0x2cb125e083d6d2631e3752bd2b3d757bf31bf02bfe21de0ffa46fbb118d28b19",
1476 "from": "0x03e5badf3bb1ade1a8f33f94536c827b6531948d",
1477 "to": "0x3267e72dc8780a1512fa69da7759ec66f30350e3",
1478 "input": "0x62e4c545000000000000000000000000464c8ec100f2f42fb4e42e07e203da2324f9fc6700000000000000000000000003e5badf3bb1ade1a8f33f94536c827b6531948d000000000000000000000000a064bfb5c7e81426647dc20a0d854da1538559dc00000000000000000000000000000000000000000000000000c6f3b40b6c0000",
1479 "nonce": "0x2a8",
1480 "value": "0x0",
1481 "gas": "0x28afd",
1482 "gasPrice": "0x23ec5dbc2",
1483 "accessList": [],
1484 "chainId": "0xaa36a7",
1485 "type": "0x0",
1486 "v": "0x1546d71",
1487 "r": "0x809b9f0a1777e376cd1ee5d2f551035643755edf26ea65b7a00c822a24504962",
1488 "s": "0x6a57bb8e21fe85c7e092868ee976fef71edca974d8c452fcf303f9180c764f64"
1489 }"#;
1490
1491 let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1492 }
1493
1494 #[test]
1496 #[cfg(feature = "serde")]
1497 fn serde_block_tx_legacy_chain_id() {
1498 let rpc_tx = r#"{
1499 "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1500 "blockNumber": "0x6edcde",
1501 "transactionIndex": "0x8",
1502 "hash": "0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6",
1503 "from": "0x8b87f0a788cc14b4f0f374da59920f5017ff05de",
1504 "to": "0xcb33aa5b38d79e3d9fa8b10aff38aa201399a7e3",
1505 "input": "0xaf7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce80000000000000000000000000000000000000000000000000000000000000064",
1506 "nonce": "0x2",
1507 "value": "0x0",
1508 "gas": "0x2dc6c0",
1509 "gasPrice": "0x18ef61d0a",
1510 "accessList": [],
1511 "chainId": "0xaa36a7",
1512 "type": "0x0",
1513 "v": "0x1c",
1514 "r": "0x5e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664",
1515 "s": "0x2353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4"
1516 }"#;
1517
1518 let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1519 }
1520}