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