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