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