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