1use super::SignableTransaction;
2use crate::{
3 error::ValueError,
4 transaction::{
5 eip4844::{TxEip4844, TxEip4844Variant},
6 RlpEcdsaEncodableTx,
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
448#[cfg(any(feature = "secp256k1", feature = "k256"))]
449impl<Eip4844> crate::transaction::SignerRecoverable for EthereumTxEnvelope<Eip4844>
450where
451 Eip4844: RlpEcdsaEncodableTx + SignableTransaction<Signature>,
452{
453 fn recover_signer(&self) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
454 match self {
455 Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
456 Self::Eip2930(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
457 Self::Eip1559(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
458 Self::Eip4844(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
459 Self::Eip7702(tx) => crate::transaction::SignerRecoverable::recover_signer(tx),
460 }
461 }
462
463 fn recover_signer_unchecked(
464 &self,
465 ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
466 match self {
467 Self::Legacy(tx) => crate::transaction::SignerRecoverable::recover_signer_unchecked(tx),
468 Self::Eip2930(tx) => {
469 crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
470 }
471 Self::Eip1559(tx) => {
472 crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
473 }
474 Self::Eip4844(tx) => {
475 crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
476 }
477 Self::Eip7702(tx) => {
478 crate::transaction::SignerRecoverable::recover_signer_unchecked(tx)
479 }
480 }
481 }
482
483 fn recover_unchecked_with_buf(
484 &self,
485 buf: &mut alloc::vec::Vec<u8>,
486 ) -> Result<alloy_primitives::Address, crate::crypto::RecoveryError> {
487 match self {
488 Self::Legacy(tx) => {
489 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
490 }
491 Self::Eip2930(tx) => {
492 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
493 }
494 Self::Eip1559(tx) => {
495 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
496 }
497 Self::Eip4844(tx) => {
498 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
499 }
500 Self::Eip7702(tx) => {
501 crate::transaction::SignerRecoverable::recover_unchecked_with_buf(tx, buf)
502 }
503 }
504 }
505}
506
507#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
509pub mod serde_bincode_compat {
510 use crate::{EthereumTypedTransaction, Signed};
511 use alloc::borrow::Cow;
512 use alloy_primitives::Signature;
513 use serde::{Deserialize, Deserializer, Serialize, Serializer};
514 use serde_with::{DeserializeAs, SerializeAs};
515
516 #[derive(Debug, Serialize, Deserialize)]
532 pub struct EthereumTxEnvelope<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
533 signature: Signature,
535 transaction:
537 crate::serde_bincode_compat::transaction::EthereumTypedTransaction<'a, Eip4844>,
538 }
539
540 impl<'a, T: Clone> From<&'a super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'a, T> {
541 fn from(value: &'a super::EthereumTxEnvelope<T>) -> Self {
542 match value {
543 super::EthereumTxEnvelope::Legacy(tx) => Self {
544 signature: *tx.signature(),
545 transaction:
546 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Legacy(
547 tx.tx().into(),
548 ),
549 },
550 super::EthereumTxEnvelope::Eip2930(tx) => Self {
551 signature: *tx.signature(),
552 transaction:
553 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip2930(
554 tx.tx().into(),
555 ),
556 },
557 super::EthereumTxEnvelope::Eip1559(tx) => Self {
558 signature: *tx.signature(),
559 transaction:
560 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip1559(
561 tx.tx().into(),
562 ),
563 },
564 super::EthereumTxEnvelope::Eip4844(tx) => Self {
565 signature: *tx.signature(),
566 transaction:
567 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip4844(
568 Cow::Borrowed(tx.tx()),
569 ),
570 },
571 super::EthereumTxEnvelope::Eip7702(tx) => Self {
572 signature: *tx.signature(),
573 transaction:
574 crate::serde_bincode_compat::transaction::EthereumTypedTransaction::Eip7702(
575 tx.tx().into(),
576 ),
577 },
578 }
579 }
580 }
581
582 impl<'a, T: Clone> From<EthereumTxEnvelope<'a, T>> for super::EthereumTxEnvelope<T> {
583 fn from(value: EthereumTxEnvelope<'a, T>) -> Self {
584 let EthereumTxEnvelope { signature, transaction } = value;
585 let transaction: crate::transaction::typed::EthereumTypedTransaction<T> =
586 transaction.into();
587 match transaction {
588 EthereumTypedTransaction::Legacy(tx) => Signed::new_unhashed(tx, signature).into(),
589 EthereumTypedTransaction::Eip2930(tx) => Signed::new_unhashed(tx, signature).into(),
590 EthereumTypedTransaction::Eip1559(tx) => Signed::new_unhashed(tx, signature).into(),
591 EthereumTypedTransaction::Eip4844(tx) => {
592 Self::Eip4844(Signed::new_unhashed(tx, signature))
593 }
594 EthereumTypedTransaction::Eip7702(tx) => Signed::new_unhashed(tx, signature).into(),
595 }
596 }
597 }
598
599 impl<T: Serialize + Clone> SerializeAs<super::EthereumTxEnvelope<T>> for EthereumTxEnvelope<'_, T> {
600 fn serialize_as<S>(
601 source: &super::EthereumTxEnvelope<T>,
602 serializer: S,
603 ) -> Result<S::Ok, S::Error>
604 where
605 S: Serializer,
606 {
607 EthereumTxEnvelope::<'_, T>::from(source).serialize(serializer)
608 }
609 }
610
611 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTxEnvelope<T>>
612 for EthereumTxEnvelope<'de, T>
613 {
614 fn deserialize_as<D>(deserializer: D) -> Result<super::EthereumTxEnvelope<T>, D::Error>
615 where
616 D: Deserializer<'de>,
617 {
618 EthereumTxEnvelope::<'_, T>::deserialize(deserializer).map(Into::into)
619 }
620 }
621
622 #[cfg(test)]
623 mod tests {
624 use super::super::{serde_bincode_compat, EthereumTxEnvelope};
625 use crate::TxEip4844;
626 use arbitrary::Arbitrary;
627 use bincode::config;
628 use rand::Rng;
629 use serde::{Deserialize, Serialize};
630 use serde_with::serde_as;
631
632 #[test]
633 fn test_typed_tx_envelope_bincode_roundtrip() {
634 #[serde_as]
635 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
636 struct Data {
637 #[serde_as(as = "serde_bincode_compat::EthereumTxEnvelope<'_>")]
638 transaction: EthereumTxEnvelope<TxEip4844>,
639 }
640
641 let mut bytes = [0u8; 1024];
642 rand::thread_rng().fill(bytes.as_mut_slice());
643 let data = Data {
644 transaction: EthereumTxEnvelope::arbitrary(&mut arbitrary::Unstructured::new(
645 &bytes,
646 ))
647 .unwrap(),
648 };
649
650 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
651 let (decoded, _) =
652 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
653 assert_eq!(decoded, data);
654 }
655 }
656}
657
658#[cfg(test)]
659mod tests {
660 use super::*;
661 use crate::{
662 transaction::{Recovered, SignableTransaction},
663 Transaction, TxEip4844, TxEip4844WithSidecar,
664 };
665 use alloc::vec::Vec;
666 use alloy_eips::{
667 eip2930::{AccessList, AccessListItem},
668 eip4844::BlobTransactionSidecar,
669 eip7702::Authorization,
670 };
671 #[allow(unused_imports)]
672 use alloy_primitives::{b256, Bytes, TxKind};
673 use alloy_primitives::{hex, Address, Signature, U256};
674 use alloy_rlp::Decodable;
675 use std::{fs, path::PathBuf, str::FromStr, vec};
676
677 #[test]
678 fn assert_encodable() {
679 fn assert_encodable<T: Encodable2718>() {}
680
681 assert_encodable::<EthereumTxEnvelope<TxEip4844>>();
682 assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844>>>();
683 assert_encodable::<Recovered<EthereumTxEnvelope<TxEip4844Variant>>>();
684 }
685
686 #[test]
687 #[cfg(feature = "k256")]
688 fn test_decode_live_1559_tx() {
690 use alloy_primitives::address;
691
692 let raw_tx = alloy_primitives::hex::decode("02f86f0102843b9aca0085029e7822d68298f094d9e1459a7a482635700cbc20bbaf52d495ab9c9680841b55ba3ac080a0c199674fcb29f353693dd779c017823b954b3c69dffa3cd6b2a6ff7888798039a028ca912de909e7e6cdef9cdcaf24c54dd8c1032946dfa1d85c206b32a9064fe8").unwrap();
693 let res = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
694
695 assert_eq!(res.tx_type(), TxType::Eip1559);
696
697 let tx = match res {
698 TxEnvelope::Eip1559(tx) => tx,
699 _ => unreachable!(),
700 };
701
702 assert_eq!(tx.tx().to, TxKind::Call(address!("D9e1459A7A482635700cBc20BBAF52D495Ab9C96")));
703 let from = tx.recover_signer().unwrap();
704 assert_eq!(from, address!("001e2b7dE757bA469a57bF6b23d982458a07eFcE"));
705 }
706
707 #[test]
708 fn test_is_replay_protected_v() {
709 let sig = Signature::test_signature();
710 assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
711 TxLegacy::default(),
712 sig,
713 Default::default(),
714 ))
715 .is_replay_protected());
716 let r = b256!("840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565");
717 let s = b256!("25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1");
718 let v = false;
719 let valid_sig = Signature::from_scalars_and_parity(r, s, v);
720 assert!(!&TxEnvelope::Legacy(Signed::new_unchecked(
721 TxLegacy::default(),
722 valid_sig,
723 Default::default(),
724 ))
725 .is_replay_protected());
726 assert!(&TxEnvelope::Eip2930(Signed::new_unchecked(
727 TxEip2930::default(),
728 sig,
729 Default::default(),
730 ))
731 .is_replay_protected());
732 assert!(&TxEnvelope::Eip1559(Signed::new_unchecked(
733 TxEip1559::default(),
734 sig,
735 Default::default(),
736 ))
737 .is_replay_protected());
738 assert!(&TxEnvelope::Eip4844(Signed::new_unchecked(
739 TxEip4844Variant::TxEip4844(TxEip4844::default()),
740 sig,
741 Default::default(),
742 ))
743 .is_replay_protected());
744 assert!(&TxEnvelope::Eip7702(Signed::new_unchecked(
745 TxEip7702::default(),
746 sig,
747 Default::default(),
748 ))
749 .is_replay_protected());
750 }
751
752 #[test]
753 #[cfg(feature = "k256")]
754 fn test_decode_live_legacy_tx() {
756 use alloy_primitives::address;
757
758 let raw_tx = alloy_primitives::bytes!("f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8");
759 let res = TxEnvelope::decode_2718(&mut raw_tx.as_ref()).unwrap();
760 assert_eq!(res.tx_type(), TxType::Legacy);
761
762 let tx = match res {
763 TxEnvelope::Legacy(tx) => tx,
764 _ => unreachable!(),
765 };
766
767 assert_eq!(tx.tx().chain_id(), Some(1));
768
769 assert_eq!(tx.tx().to, TxKind::Call(address!("7a250d5630B4cF539739dF2C5dAcb4c659F2488D")));
770 assert_eq!(
771 tx.hash().to_string(),
772 "0x280cde7cdefe4b188750e76c888f13bd05ce9a4d7767730feefe8a0e50ca6fc4"
773 );
774 let from = tx.recover_signer().unwrap();
775 assert_eq!(from, address!("a12e1462d0ceD572f396F58B6E2D03894cD7C8a4"));
776 }
777
778 #[test]
779 #[cfg(feature = "k256")]
780 fn test_decode_live_4844_tx() {
783 use crate::Transaction;
784 use alloy_primitives::{address, b256};
785
786 let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
788
789 let res = TxEnvelope::decode_2718(&mut raw_tx.as_slice()).unwrap();
790 assert_eq!(res.tx_type(), TxType::Eip4844);
791
792 let tx = match res {
793 TxEnvelope::Eip4844(tx) => tx,
794 _ => unreachable!(),
795 };
796
797 assert_eq!(
798 tx.tx().kind(),
799 TxKind::Call(address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064"))
800 );
801
802 assert!(matches!(tx.tx(), TxEip4844Variant::TxEip4844(_)));
804
805 assert_eq!(
806 tx.tx().tx().blob_versioned_hashes,
807 vec![
808 b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"),
809 b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"),
810 b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"),
811 b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"),
812 b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549")
813 ]
814 );
815
816 let from = tx.recover_signer().unwrap();
817 assert_eq!(from, address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2"));
818 }
819
820 fn test_encode_decode_roundtrip<T: SignableTransaction<Signature>>(
821 tx: T,
822 signature: Option<Signature>,
823 ) where
824 Signed<T>: Into<TxEnvelope>,
825 {
826 let signature = signature.unwrap_or_else(Signature::test_signature);
827 let tx_signed = tx.into_signed(signature);
828 let tx_envelope: TxEnvelope = tx_signed.into();
829 let encoded = tx_envelope.encoded_2718();
830 let mut slice = encoded.as_slice();
831 let decoded = TxEnvelope::decode_2718(&mut slice).unwrap();
832 assert_eq!(encoded.len(), tx_envelope.encode_2718_len());
833 assert_eq!(decoded, tx_envelope);
834 assert_eq!(slice.len(), 0);
835 }
836
837 #[test]
838 fn test_encode_decode_legacy() {
839 let tx = TxLegacy {
840 chain_id: None,
841 nonce: 2,
842 gas_limit: 1000000,
843 gas_price: 10000000000,
844 to: Address::left_padding_from(&[6]).into(),
845 value: U256::from(7_u64),
846 ..Default::default()
847 };
848 test_encode_decode_roundtrip(tx, Some(Signature::test_signature().with_parity(true)));
849 }
850
851 #[test]
852 fn test_encode_decode_eip1559() {
853 let tx = TxEip1559 {
854 chain_id: 1u64,
855 nonce: 2,
856 max_fee_per_gas: 3,
857 max_priority_fee_per_gas: 4,
858 gas_limit: 5,
859 to: Address::left_padding_from(&[6]).into(),
860 value: U256::from(7_u64),
861 input: vec![8].into(),
862 access_list: Default::default(),
863 };
864 test_encode_decode_roundtrip(tx, None);
865 }
866
867 #[test]
868 fn test_encode_decode_eip1559_parity_eip155() {
869 let tx = TxEip1559 {
870 chain_id: 1u64,
871 nonce: 2,
872 max_fee_per_gas: 3,
873 max_priority_fee_per_gas: 4,
874 gas_limit: 5,
875 to: Address::left_padding_from(&[6]).into(),
876 value: U256::from(7_u64),
877 input: vec![8].into(),
878 access_list: Default::default(),
879 };
880 let signature = Signature::test_signature().with_parity(true);
881
882 test_encode_decode_roundtrip(tx, Some(signature));
883 }
884
885 #[test]
886 fn test_encode_decode_eip2930_parity_eip155() {
887 let tx = TxEip2930 {
888 chain_id: 1u64,
889 nonce: 2,
890 gas_price: 3,
891 gas_limit: 4,
892 to: Address::left_padding_from(&[5]).into(),
893 value: U256::from(6_u64),
894 input: vec![7].into(),
895 access_list: Default::default(),
896 };
897 let signature = Signature::test_signature().with_parity(true);
898 test_encode_decode_roundtrip(tx, Some(signature));
899 }
900
901 #[test]
902 fn test_encode_decode_eip4844_parity_eip155() {
903 let tx = TxEip4844 {
904 chain_id: 1,
905 nonce: 100,
906 max_fee_per_gas: 50_000_000_000,
907 max_priority_fee_per_gas: 1_000_000_000_000,
908 gas_limit: 1_000_000,
909 to: Address::random(),
910 value: U256::from(10e18),
911 input: Bytes::new(),
912 access_list: AccessList(vec![AccessListItem {
913 address: Address::random(),
914 storage_keys: vec![B256::random()],
915 }]),
916 blob_versioned_hashes: vec![B256::random()],
917 max_fee_per_blob_gas: 0,
918 };
919 let signature = Signature::test_signature().with_parity(true);
920 test_encode_decode_roundtrip(tx, Some(signature));
921 }
922
923 #[test]
924 fn test_encode_decode_eip4844_sidecar_parity_eip155() {
925 let tx = TxEip4844 {
926 chain_id: 1,
927 nonce: 100,
928 max_fee_per_gas: 50_000_000_000,
929 max_priority_fee_per_gas: 1_000_000_000_000,
930 gas_limit: 1_000_000,
931 to: Address::random(),
932 value: U256::from(10e18),
933 input: Bytes::new(),
934 access_list: AccessList(vec![AccessListItem {
935 address: Address::random(),
936 storage_keys: vec![B256::random()],
937 }]),
938 blob_versioned_hashes: vec![B256::random()],
939 max_fee_per_blob_gas: 0,
940 };
941 let sidecar = BlobTransactionSidecar {
942 blobs: vec![[2; 131072].into()],
943 commitments: vec![[3; 48].into()],
944 proofs: vec![[4; 48].into()],
945 };
946 let tx = TxEip4844WithSidecar { tx, sidecar };
947 let signature = Signature::test_signature().with_parity(true);
948
949 let tx_signed = tx.into_signed(signature);
950 let tx_envelope: TxEnvelope = tx_signed.into();
951
952 let mut out = Vec::new();
953 tx_envelope.network_encode(&mut out);
954 let mut slice = out.as_slice();
955 let decoded = TxEnvelope::network_decode(&mut slice).unwrap();
956 assert_eq!(slice.len(), 0);
957 assert_eq!(out.len(), tx_envelope.network_len());
958 assert_eq!(decoded, tx_envelope);
959 }
960
961 #[test]
962 fn test_encode_decode_eip4844_variant_parity_eip155() {
963 let tx = TxEip4844 {
964 chain_id: 1,
965 nonce: 100,
966 max_fee_per_gas: 50_000_000_000,
967 max_priority_fee_per_gas: 1_000_000_000_000,
968 gas_limit: 1_000_000,
969 to: Address::random(),
970 value: U256::from(10e18),
971 input: Bytes::new(),
972 access_list: AccessList(vec![AccessListItem {
973 address: Address::random(),
974 storage_keys: vec![B256::random()],
975 }]),
976 blob_versioned_hashes: vec![B256::random()],
977 max_fee_per_blob_gas: 0,
978 };
979 let tx = TxEip4844Variant::TxEip4844(tx);
980 let signature = Signature::test_signature().with_parity(true);
981 test_encode_decode_roundtrip(tx, Some(signature));
982 }
983
984 #[test]
985 fn test_encode_decode_eip2930() {
986 let tx = TxEip2930 {
987 chain_id: 1u64,
988 nonce: 2,
989 gas_price: 3,
990 gas_limit: 4,
991 to: Address::left_padding_from(&[5]).into(),
992 value: U256::from(6_u64),
993 input: vec![7].into(),
994 access_list: AccessList(vec![AccessListItem {
995 address: Address::left_padding_from(&[8]),
996 storage_keys: vec![B256::left_padding_from(&[9])],
997 }]),
998 };
999 test_encode_decode_roundtrip(tx, None);
1000 }
1001
1002 #[test]
1003 fn test_encode_decode_eip7702() {
1004 let tx = TxEip7702 {
1005 chain_id: 1u64,
1006 nonce: 2,
1007 gas_limit: 3,
1008 max_fee_per_gas: 4,
1009 max_priority_fee_per_gas: 5,
1010 to: Address::left_padding_from(&[5]),
1011 value: U256::from(6_u64),
1012 input: vec![7].into(),
1013 access_list: AccessList(vec![AccessListItem {
1014 address: Address::left_padding_from(&[8]),
1015 storage_keys: vec![B256::left_padding_from(&[9])],
1016 }]),
1017 authorization_list: vec![(Authorization {
1018 chain_id: U256::from(1),
1019 address: Address::left_padding_from(&[10]),
1020 nonce: 1u64,
1021 })
1022 .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1023 };
1024 test_encode_decode_roundtrip(tx, None);
1025 }
1026
1027 #[test]
1028 fn test_encode_decode_transaction_list() {
1029 let signature = Signature::test_signature();
1030 let tx = TxEnvelope::Eip1559(
1031 TxEip1559 {
1032 chain_id: 1u64,
1033 nonce: 2,
1034 max_fee_per_gas: 3,
1035 max_priority_fee_per_gas: 4,
1036 gas_limit: 5,
1037 to: Address::left_padding_from(&[6]).into(),
1038 value: U256::from(7_u64),
1039 input: vec![8].into(),
1040 access_list: Default::default(),
1041 }
1042 .into_signed(signature),
1043 );
1044 let transactions = vec![tx.clone(), tx];
1045 let encoded = alloy_rlp::encode(&transactions);
1046 let decoded = Vec::<TxEnvelope>::decode(&mut &encoded[..]).unwrap();
1047 assert_eq!(transactions, decoded);
1048 }
1049
1050 #[test]
1051 fn decode_encode_known_rpc_transaction() {
1052 let network_data_path =
1054 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/rpc_blob_transaction.rlp");
1055 let data = fs::read_to_string(network_data_path).expect("Unable to read file");
1056 let hex_data = hex::decode(data.trim()).unwrap();
1057
1058 let tx: TxEnvelope = TxEnvelope::decode_2718(&mut hex_data.as_slice()).unwrap();
1059 let encoded = tx.encoded_2718();
1060 assert_eq!(encoded, hex_data);
1061 assert_eq!(tx.encode_2718_len(), hex_data.len());
1062 }
1063
1064 #[cfg(feature = "serde")]
1065 fn test_serde_roundtrip<T: SignableTransaction<Signature>>(tx: T)
1066 where
1067 Signed<T>: Into<TxEnvelope>,
1068 {
1069 let signature = Signature::test_signature();
1070 let tx_envelope: TxEnvelope = tx.into_signed(signature).into();
1071
1072 let serialized = serde_json::to_string(&tx_envelope).unwrap();
1073
1074 let deserialized: TxEnvelope = serde_json::from_str(&serialized).unwrap();
1075
1076 assert_eq!(tx_envelope, deserialized);
1077 }
1078
1079 #[test]
1080 #[cfg(feature = "serde")]
1081 fn test_serde_roundtrip_legacy() {
1082 let tx = TxLegacy {
1083 chain_id: Some(1),
1084 nonce: 100,
1085 gas_price: 3_000_000_000,
1086 gas_limit: 50_000,
1087 to: Address::default().into(),
1088 value: U256::from(10e18),
1089 input: Bytes::new(),
1090 };
1091 test_serde_roundtrip(tx);
1092 }
1093
1094 #[test]
1095 #[cfg(feature = "serde")]
1096 fn test_serde_roundtrip_eip1559() {
1097 let tx = TxEip1559 {
1098 chain_id: 1,
1099 nonce: 100,
1100 max_fee_per_gas: 50_000_000_000,
1101 max_priority_fee_per_gas: 1_000_000_000_000,
1102 gas_limit: 1_000_000,
1103 to: TxKind::Create,
1104 value: U256::from(10e18),
1105 input: Bytes::new(),
1106 access_list: AccessList(vec![AccessListItem {
1107 address: Address::random(),
1108 storage_keys: vec![B256::random()],
1109 }]),
1110 };
1111 test_serde_roundtrip(tx);
1112 }
1113
1114 #[test]
1115 #[cfg(feature = "serde")]
1116 fn test_serde_roundtrip_eip2930() {
1117 let tx = TxEip2930 {
1118 chain_id: u64::MAX,
1119 nonce: u64::MAX,
1120 gas_price: u128::MAX,
1121 gas_limit: u64::MAX,
1122 to: Address::random().into(),
1123 value: U256::MAX,
1124 input: Bytes::new(),
1125 access_list: Default::default(),
1126 };
1127 test_serde_roundtrip(tx);
1128 }
1129
1130 #[test]
1131 #[cfg(feature = "serde")]
1132 fn test_serde_roundtrip_eip4844() {
1133 let tx = TxEip4844Variant::TxEip4844(TxEip4844 {
1134 chain_id: 1,
1135 nonce: 100,
1136 max_fee_per_gas: 50_000_000_000,
1137 max_priority_fee_per_gas: 1_000_000_000_000,
1138 gas_limit: 1_000_000,
1139 to: Address::random(),
1140 value: U256::from(10e18),
1141 input: Bytes::new(),
1142 access_list: AccessList(vec![AccessListItem {
1143 address: Address::random(),
1144 storage_keys: vec![B256::random()],
1145 }]),
1146 blob_versioned_hashes: vec![B256::random()],
1147 max_fee_per_blob_gas: 0,
1148 });
1149 test_serde_roundtrip(tx);
1150
1151 let tx = TxEip4844Variant::TxEip4844WithSidecar(TxEip4844WithSidecar {
1152 tx: TxEip4844 {
1153 chain_id: 1,
1154 nonce: 100,
1155 max_fee_per_gas: 50_000_000_000,
1156 max_priority_fee_per_gas: 1_000_000_000_000,
1157 gas_limit: 1_000_000,
1158 to: Address::random(),
1159 value: U256::from(10e18),
1160 input: Bytes::new(),
1161 access_list: AccessList(vec![AccessListItem {
1162 address: Address::random(),
1163 storage_keys: vec![B256::random()],
1164 }]),
1165 blob_versioned_hashes: vec![B256::random()],
1166 max_fee_per_blob_gas: 0,
1167 },
1168 sidecar: Default::default(),
1169 });
1170 test_serde_roundtrip(tx);
1171 }
1172
1173 #[test]
1174 #[cfg(feature = "serde")]
1175 fn test_serde_roundtrip_eip7702() {
1176 let tx = TxEip7702 {
1177 chain_id: u64::MAX,
1178 nonce: u64::MAX,
1179 gas_limit: u64::MAX,
1180 max_fee_per_gas: u128::MAX,
1181 max_priority_fee_per_gas: u128::MAX,
1182 to: Address::random(),
1183 value: U256::MAX,
1184 input: Bytes::new(),
1185 access_list: AccessList(vec![AccessListItem {
1186 address: Address::random(),
1187 storage_keys: vec![B256::random()],
1188 }]),
1189 authorization_list: vec![(Authorization {
1190 chain_id: U256::from(1),
1191 address: Address::left_padding_from(&[1]),
1192 nonce: 1u64,
1193 })
1194 .into_signed(Signature::from_str("48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b").unwrap())],
1195 };
1196 test_serde_roundtrip(tx);
1197 }
1198
1199 #[test]
1200 #[cfg(feature = "serde")]
1201 fn serde_tx_from_contract_call() {
1202 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"}"#;
1203
1204 let te = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1205
1206 assert_eq!(
1207 *te.tx_hash(),
1208 alloy_primitives::b256!(
1209 "018b2331d461a4aeedf6a1f9cc37463377578244e6a35216057a8370714e798f"
1210 )
1211 );
1212 }
1213
1214 #[test]
1215 #[cfg(feature = "k256")]
1216 fn test_arbitrary_envelope() {
1217 use crate::transaction::SignerRecoverable;
1218 use arbitrary::Arbitrary;
1219 let mut unstructured = arbitrary::Unstructured::new(b"arbitrary tx envelope");
1220 let tx = TxEnvelope::arbitrary(&mut unstructured).unwrap();
1221
1222 assert!(tx.recover_signer().is_ok());
1223 }
1224
1225 #[test]
1226 #[cfg(feature = "serde")]
1227 fn test_serde_untagged_legacy() {
1228 let data = r#"{
1229 "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1230 "input": "0x",
1231 "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1232 "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1233 "v": "0x1c",
1234 "gas": "0x15f90",
1235 "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1236 "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1237 "value": "0xf606682badd7800",
1238 "nonce": "0x11f398",
1239 "gasPrice": "0x4a817c800"
1240 }"#;
1241
1242 let tx: TxEnvelope = serde_json::from_str(data).unwrap();
1243
1244 assert!(matches!(tx, TxEnvelope::Legacy(_)));
1245
1246 let data_with_wrong_type = r#"{
1247 "hash": "0x97efb58d2b42df8d68ab5899ff42b16c7e0af35ed86ae4adb8acaad7e444220c",
1248 "input": "0x",
1249 "r": "0x5d71a4a548503f2916d10c6b1a1557a0e7352eb041acb2bac99d1ad6bb49fd45",
1250 "s": "0x2627bf6d35be48b0e56c61733f63944c0ebcaa85cb4ed6bc7cba3161ba85e0e8",
1251 "v": "0x1c",
1252 "gas": "0x15f90",
1253 "from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
1254 "to": "0x8fbeb4488a08d60979b5aa9e13dd00b2726320b2",
1255 "value": "0xf606682badd7800",
1256 "nonce": "0x11f398",
1257 "gasPrice": "0x4a817c800",
1258 "type": "0x12"
1259 }"#;
1260
1261 assert!(serde_json::from_str::<TxEnvelope>(data_with_wrong_type).is_err());
1262 }
1263
1264 #[test]
1265 fn test_tx_type_try_from_u8() {
1266 assert_eq!(TxType::try_from(0u8).unwrap(), TxType::Legacy);
1267 assert_eq!(TxType::try_from(1u8).unwrap(), TxType::Eip2930);
1268 assert_eq!(TxType::try_from(2u8).unwrap(), TxType::Eip1559);
1269 assert_eq!(TxType::try_from(3u8).unwrap(), TxType::Eip4844);
1270 assert_eq!(TxType::try_from(4u8).unwrap(), TxType::Eip7702);
1271 assert!(TxType::try_from(5u8).is_err()); }
1273
1274 #[test]
1275 fn test_tx_type_try_from_u64() {
1276 assert_eq!(TxType::try_from(0u64).unwrap(), TxType::Legacy);
1277 assert_eq!(TxType::try_from(1u64).unwrap(), TxType::Eip2930);
1278 assert_eq!(TxType::try_from(2u64).unwrap(), TxType::Eip1559);
1279 assert_eq!(TxType::try_from(3u64).unwrap(), TxType::Eip4844);
1280 assert_eq!(TxType::try_from(4u64).unwrap(), TxType::Eip7702);
1281 assert!(TxType::try_from(10u64).is_err()); }
1283
1284 #[test]
1285 fn test_tx_type_from_conversions() {
1286 let legacy_tx = Signed::new_unchecked(
1287 TxLegacy::default(),
1288 Signature::test_signature(),
1289 Default::default(),
1290 );
1291 let eip2930_tx = Signed::new_unchecked(
1292 TxEip2930::default(),
1293 Signature::test_signature(),
1294 Default::default(),
1295 );
1296 let eip1559_tx = Signed::new_unchecked(
1297 TxEip1559::default(),
1298 Signature::test_signature(),
1299 Default::default(),
1300 );
1301 let eip4844_variant = Signed::new_unchecked(
1302 TxEip4844Variant::TxEip4844(TxEip4844::default()),
1303 Signature::test_signature(),
1304 Default::default(),
1305 );
1306 let eip7702_tx = Signed::new_unchecked(
1307 TxEip7702::default(),
1308 Signature::test_signature(),
1309 Default::default(),
1310 );
1311
1312 assert!(matches!(TxEnvelope::from(legacy_tx), TxEnvelope::Legacy(_)));
1313 assert!(matches!(TxEnvelope::from(eip2930_tx), TxEnvelope::Eip2930(_)));
1314 assert!(matches!(TxEnvelope::from(eip1559_tx), TxEnvelope::Eip1559(_)));
1315 assert!(matches!(TxEnvelope::from(eip4844_variant), TxEnvelope::Eip4844(_)));
1316 assert!(matches!(TxEnvelope::from(eip7702_tx), TxEnvelope::Eip7702(_)));
1317 }
1318
1319 #[test]
1320 fn test_tx_type_is_methods() {
1321 let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1322 TxLegacy::default(),
1323 Signature::test_signature(),
1324 Default::default(),
1325 ));
1326 let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1327 TxEip2930::default(),
1328 Signature::test_signature(),
1329 Default::default(),
1330 ));
1331 let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1332 TxEip1559::default(),
1333 Signature::test_signature(),
1334 Default::default(),
1335 ));
1336 let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1337 TxEip4844Variant::TxEip4844(TxEip4844::default()),
1338 Signature::test_signature(),
1339 Default::default(),
1340 ));
1341 let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1342 TxEip7702::default(),
1343 Signature::test_signature(),
1344 Default::default(),
1345 ));
1346
1347 assert!(legacy_tx.is_legacy());
1348 assert!(!legacy_tx.is_eip2930());
1349 assert!(!legacy_tx.is_eip1559());
1350 assert!(!legacy_tx.is_eip4844());
1351 assert!(!legacy_tx.is_eip7702());
1352
1353 assert!(eip2930_tx.is_eip2930());
1354 assert!(!eip2930_tx.is_legacy());
1355 assert!(!eip2930_tx.is_eip1559());
1356 assert!(!eip2930_tx.is_eip4844());
1357 assert!(!eip2930_tx.is_eip7702());
1358
1359 assert!(eip1559_tx.is_eip1559());
1360 assert!(!eip1559_tx.is_legacy());
1361 assert!(!eip1559_tx.is_eip2930());
1362 assert!(!eip1559_tx.is_eip4844());
1363 assert!(!eip1559_tx.is_eip7702());
1364
1365 assert!(eip4844_tx.is_eip4844());
1366 assert!(!eip4844_tx.is_legacy());
1367 assert!(!eip4844_tx.is_eip2930());
1368 assert!(!eip4844_tx.is_eip1559());
1369 assert!(!eip4844_tx.is_eip7702());
1370
1371 assert!(eip7702_tx.is_eip7702());
1372 assert!(!eip7702_tx.is_legacy());
1373 assert!(!eip7702_tx.is_eip2930());
1374 assert!(!eip7702_tx.is_eip1559());
1375 assert!(!eip7702_tx.is_eip4844());
1376 }
1377
1378 #[test]
1379 fn test_tx_type() {
1380 let legacy_tx = TxEnvelope::Legacy(Signed::new_unchecked(
1381 TxLegacy::default(),
1382 Signature::test_signature(),
1383 Default::default(),
1384 ));
1385 let eip2930_tx = TxEnvelope::Eip2930(Signed::new_unchecked(
1386 TxEip2930::default(),
1387 Signature::test_signature(),
1388 Default::default(),
1389 ));
1390 let eip1559_tx = TxEnvelope::Eip1559(Signed::new_unchecked(
1391 TxEip1559::default(),
1392 Signature::test_signature(),
1393 Default::default(),
1394 ));
1395 let eip4844_tx = TxEnvelope::Eip4844(Signed::new_unchecked(
1396 TxEip4844Variant::TxEip4844(TxEip4844::default()),
1397 Signature::test_signature(),
1398 Default::default(),
1399 ));
1400 let eip7702_tx = TxEnvelope::Eip7702(Signed::new_unchecked(
1401 TxEip7702::default(),
1402 Signature::test_signature(),
1403 Default::default(),
1404 ));
1405
1406 assert_eq!(legacy_tx.tx_type(), TxType::Legacy);
1407 assert_eq!(eip2930_tx.tx_type(), TxType::Eip2930);
1408 assert_eq!(eip1559_tx.tx_type(), TxType::Eip1559);
1409 assert_eq!(eip4844_tx.tx_type(), TxType::Eip4844);
1410 assert_eq!(eip7702_tx.tx_type(), TxType::Eip7702);
1411 }
1412
1413 #[test]
1415 fn decode_raw_legacy() {
1416 let raw = hex!("f8aa0285018ef61d0a832dc6c094cb33aa5b38d79e3d9fa8b10aff38aa201399a7e380b844af7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce800000000000000000000000000000000000000000000000000000000000000641ca05e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664a02353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4");
1417 let tx = TxEnvelope::decode_2718(&mut raw.as_ref()).unwrap();
1418 assert!(tx.chain_id().is_none());
1419 }
1420
1421 #[test]
1422 #[cfg(feature = "serde")]
1423 fn can_deserialize_system_transaction_with_zero_signature_envelope() {
1424 let raw_tx = r#"{
1425 "blockHash": "0x5307b5c812a067f8bc1ed1cc89d319ae6f9a0c9693848bd25c36b5191de60b85",
1426 "blockNumber": "0x45a59bb",
1427 "from": "0x0000000000000000000000000000000000000000",
1428 "gas": "0x1e8480",
1429 "gasPrice": "0x0",
1430 "hash": "0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06",
1431 "input": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000",
1432 "nonce": "0x469f7",
1433 "to": "0x4200000000000000000000000000000000000007",
1434 "transactionIndex": "0x0",
1435 "value": "0x0",
1436 "v": "0x0",
1437 "r": "0x0",
1438 "s": "0x0",
1439 "queueOrigin": "l1",
1440 "l1TxOrigin": "0x36bde71c97b33cc4729cf772ae268934f7ab70b2",
1441 "l1BlockNumber": "0xfd1a6c",
1442 "l1Timestamp": "0x63e434ff",
1443 "index": "0x45a59ba",
1444 "queueIndex": "0x469f7",
1445 "rawTransaction": "0xcbd4ece900000000000000000000000032155c9d39084f040ba17890fe8134dbe2a0453f0000000000000000000000004a0126ee88018393b1ad2455060bc350ead9908a000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000469f700000000000000000000000000000000000000000000000000000000000000644ff746f60000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002043e908a4e862aebb10e7e27db0b892b58a7e32af11d64387a414dabc327b00e200000000000000000000000000000000000000000000000000000000"
1446 }"#;
1447
1448 let tx = serde_json::from_str::<TxEnvelope>(raw_tx).unwrap();
1449
1450 assert_eq!(tx.signature().r(), U256::ZERO);
1451 assert_eq!(tx.signature().s(), U256::ZERO);
1452 assert!(!tx.signature().v());
1453
1454 assert_eq!(
1455 tx.hash(),
1456 &b256!("0x16ef68aa8f35add3a03167a12b5d1268e344f6605a64ecc3f1c3aa68e98e4e06"),
1457 "hash should match the transaction hash"
1458 );
1459 }
1460
1461 #[test]
1463 #[cfg(feature = "serde")]
1464 fn serde_block_tx() {
1465 let rpc_tx = r#"{
1466 "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1467 "blockNumber": "0x6edcde",
1468 "transactionIndex": "0x7",
1469 "hash": "0x2cb125e083d6d2631e3752bd2b3d757bf31bf02bfe21de0ffa46fbb118d28b19",
1470 "from": "0x03e5badf3bb1ade1a8f33f94536c827b6531948d",
1471 "to": "0x3267e72dc8780a1512fa69da7759ec66f30350e3",
1472 "input": "0x62e4c545000000000000000000000000464c8ec100f2f42fb4e42e07e203da2324f9fc6700000000000000000000000003e5badf3bb1ade1a8f33f94536c827b6531948d000000000000000000000000a064bfb5c7e81426647dc20a0d854da1538559dc00000000000000000000000000000000000000000000000000c6f3b40b6c0000",
1473 "nonce": "0x2a8",
1474 "value": "0x0",
1475 "gas": "0x28afd",
1476 "gasPrice": "0x23ec5dbc2",
1477 "accessList": [],
1478 "chainId": "0xaa36a7",
1479 "type": "0x0",
1480 "v": "0x1546d71",
1481 "r": "0x809b9f0a1777e376cd1ee5d2f551035643755edf26ea65b7a00c822a24504962",
1482 "s": "0x6a57bb8e21fe85c7e092868ee976fef71edca974d8c452fcf303f9180c764f64"
1483 }"#;
1484
1485 let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1486 }
1487
1488 #[test]
1490 #[cfg(feature = "serde")]
1491 fn serde_block_tx_legacy_chain_id() {
1492 let rpc_tx = r#"{
1493 "blockHash": "0xc0c3190292a82c2ee148774e37e5665f6a205f5ef0cd0885e84701d90ebd442e",
1494 "blockNumber": "0x6edcde",
1495 "transactionIndex": "0x8",
1496 "hash": "0xe5b458ba9de30b47cb7c0ea836bec7b072053123a7416c5082c97f959a4eebd6",
1497 "from": "0x8b87f0a788cc14b4f0f374da59920f5017ff05de",
1498 "to": "0xcb33aa5b38d79e3d9fa8b10aff38aa201399a7e3",
1499 "input": "0xaf7b421018842e4628f3d9ee0e2c7679e29ed5dbaa75be75efecd392943503c9c68adce80000000000000000000000000000000000000000000000000000000000000064",
1500 "nonce": "0x2",
1501 "value": "0x0",
1502 "gas": "0x2dc6c0",
1503 "gasPrice": "0x18ef61d0a",
1504 "accessList": [],
1505 "chainId": "0xaa36a7",
1506 "type": "0x0",
1507 "v": "0x1c",
1508 "r": "0x5e28679806caa50d25e9cb16aef8c0c08b235241b8f6e9d86faadf70421ba664",
1509 "s": "0x2353bba82ef2c7ce4dd6695942399163160000272b14f9aa6cbadf011b76efa4"
1510 }"#;
1511
1512 let _ = serde_json::from_str::<TxEnvelope>(rpc_tx).unwrap();
1513 }
1514}