Skip to main content

alloy_network/any/
either.rs

1use crate::{UnknownTxEnvelope, UnknownTypedTransaction};
2use alloy_consensus::{
3    error::ValueError, transaction::Either, SignableTransaction, Signed,
4    Transaction as TransactionTrait, TxEip1559, TxEip2930, TxEip4844Variant, TxEip7702, TxEnvelope,
5    TxLegacy, Typed2718, TypedTransaction,
6};
7use alloy_eips::{
8    eip2718::{Decodable2718, Encodable2718},
9    eip7702::SignedAuthorization,
10};
11use alloy_primitives::{bytes::BufMut, Bytes, ChainId, Signature, B256, U256};
12use alloy_rpc_types_eth::{AccessList, TransactionRequest};
13use alloy_serde::WithOtherFields;
14
15/// Unsigned transaction type for a catch-all network.
16#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[serde(untagged)]
18#[doc(alias = "AnyTypedTx")]
19pub enum AnyTypedTransaction {
20    /// An Ethereum transaction.
21    Ethereum(TypedTransaction),
22    /// A transaction with unknown type.
23    Unknown(UnknownTypedTransaction),
24}
25
26#[cold]
27#[track_caller]
28fn panic_unknown_transaction_signing(ty: u8) -> ! {
29    panic!("unknown transaction type 0x{ty:02x} cannot be signed via AnyTypedTransaction")
30}
31
32#[cold]
33#[track_caller]
34fn panic_unknown_transaction_encoding(ty: u8) -> ! {
35    panic!(
36        "Attempted to encode unknown transaction type 0x{ty:02x}. This is not a bug in alloy. To encode or decode unknown transaction types, use a custom Transaction type and a custom Network implementation. See https://docs.rs/alloy-network/latest/alloy_network/ for network documentation."
37    )
38}
39
40impl From<UnknownTypedTransaction> for AnyTypedTransaction {
41    fn from(value: UnknownTypedTransaction) -> Self {
42        Self::Unknown(value)
43    }
44}
45
46impl From<TypedTransaction> for AnyTypedTransaction {
47    fn from(value: TypedTransaction) -> Self {
48        Self::Ethereum(value)
49    }
50}
51
52impl From<AnyTxEnvelope> for AnyTypedTransaction {
53    fn from(value: AnyTxEnvelope) -> Self {
54        match value {
55            AnyTxEnvelope::Ethereum(tx) => Self::Ethereum(tx.into()),
56            AnyTxEnvelope::Unknown(UnknownTxEnvelope { inner, .. }) => inner.into(),
57        }
58    }
59}
60
61impl From<AnyTypedTransaction> for WithOtherFields<TransactionRequest> {
62    fn from(value: AnyTypedTransaction) -> Self {
63        match value {
64            AnyTypedTransaction::Ethereum(tx) => Self::new(tx.into()),
65            AnyTypedTransaction::Unknown(UnknownTypedTransaction { ty, mut fields, .. }) => {
66                fields.insert("type".to_string(), serde_json::Value::Number(ty.0.into()));
67                Self { inner: Default::default(), other: fields }
68            }
69        }
70    }
71}
72
73impl From<AnyTxEnvelope> for WithOtherFields<TransactionRequest> {
74    fn from(value: AnyTxEnvelope) -> Self {
75        AnyTypedTransaction::from(value).into()
76    }
77}
78
79impl TransactionTrait for AnyTypedTransaction {
80    #[inline]
81    fn chain_id(&self) -> Option<ChainId> {
82        match self {
83            Self::Ethereum(inner) => inner.chain_id(),
84            Self::Unknown(inner) => inner.chain_id(),
85        }
86    }
87
88    #[inline]
89    fn nonce(&self) -> u64 {
90        match self {
91            Self::Ethereum(inner) => inner.nonce(),
92            Self::Unknown(inner) => inner.nonce(),
93        }
94    }
95
96    #[inline]
97    fn gas_limit(&self) -> u64 {
98        match self {
99            Self::Ethereum(inner) => inner.gas_limit(),
100            Self::Unknown(inner) => inner.gas_limit(),
101        }
102    }
103
104    #[inline]
105    fn gas_price(&self) -> Option<u128> {
106        match self {
107            Self::Ethereum(inner) => inner.gas_price(),
108            Self::Unknown(inner) => inner.gas_price(),
109        }
110    }
111
112    #[inline]
113    fn max_fee_per_gas(&self) -> u128 {
114        match self {
115            Self::Ethereum(inner) => inner.max_fee_per_gas(),
116            Self::Unknown(inner) => inner.max_fee_per_gas(),
117        }
118    }
119
120    #[inline]
121    fn max_priority_fee_per_gas(&self) -> Option<u128> {
122        match self {
123            Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
124            Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
125        }
126    }
127
128    #[inline]
129    fn max_fee_per_blob_gas(&self) -> Option<u128> {
130        match self {
131            Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
132            Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
133        }
134    }
135
136    #[inline]
137    fn priority_fee_or_price(&self) -> u128 {
138        self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
139    }
140
141    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
142        match self {
143            Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
144            Self::Unknown(inner) => inner.effective_gas_price(base_fee),
145        }
146    }
147
148    #[inline]
149    fn is_dynamic_fee(&self) -> bool {
150        match self {
151            Self::Ethereum(inner) => inner.is_dynamic_fee(),
152            Self::Unknown(inner) => inner.is_dynamic_fee(),
153        }
154    }
155
156    fn kind(&self) -> alloy_primitives::TxKind {
157        match self {
158            Self::Ethereum(inner) => inner.kind(),
159            Self::Unknown(inner) => inner.kind(),
160        }
161    }
162
163    #[inline]
164    fn is_create(&self) -> bool {
165        match self {
166            Self::Ethereum(inner) => inner.is_create(),
167            Self::Unknown(inner) => inner.is_create(),
168        }
169    }
170
171    #[inline]
172    fn value(&self) -> U256 {
173        match self {
174            Self::Ethereum(inner) => inner.value(),
175            Self::Unknown(inner) => inner.value(),
176        }
177    }
178
179    #[inline]
180    fn input(&self) -> &Bytes {
181        match self {
182            Self::Ethereum(inner) => inner.input(),
183            Self::Unknown(inner) => inner.input(),
184        }
185    }
186
187    #[inline]
188    fn access_list(&self) -> Option<&AccessList> {
189        match self {
190            Self::Ethereum(inner) => inner.access_list(),
191            Self::Unknown(inner) => inner.access_list(),
192        }
193    }
194
195    #[inline]
196    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
197        match self {
198            Self::Ethereum(inner) => inner.blob_versioned_hashes(),
199            Self::Unknown(inner) => inner.blob_versioned_hashes(),
200        }
201    }
202
203    #[inline]
204    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
205        match self {
206            Self::Ethereum(inner) => inner.authorization_list(),
207            Self::Unknown(inner) => inner.authorization_list(),
208        }
209    }
210}
211
212impl Typed2718 for AnyTypedTransaction {
213    fn ty(&self) -> u8 {
214        match self {
215            Self::Ethereum(inner) => inner.ty(),
216            Self::Unknown(inner) => inner.ty(),
217        }
218    }
219}
220
221impl SignableTransaction<Signature> for AnyTypedTransaction {
222    fn set_chain_id(&mut self, chain_id: ChainId) {
223        match self {
224            Self::Ethereum(typed_tx) => typed_tx.set_chain_id(chain_id),
225            Self::Unknown(unknown_tx) => panic_unknown_transaction_signing(unknown_tx.ty()),
226        }
227    }
228
229    fn encode_for_signing(&self, out: &mut dyn BufMut) {
230        match self {
231            Self::Ethereum(typed_tx) => typed_tx.encode_for_signing(out),
232            Self::Unknown(unknown_tx) => panic_unknown_transaction_signing(unknown_tx.ty()),
233        }
234    }
235
236    fn payload_len_for_signature(&self) -> usize {
237        match self {
238            Self::Ethereum(typed_tx) => typed_tx.payload_len_for_signature(),
239            Self::Unknown(unknown_tx) => panic_unknown_transaction_signing(unknown_tx.ty()),
240        }
241    }
242}
243
244/// Transaction envelope for a catch-all network.
245#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
246#[serde(untagged)]
247#[doc(alias = "AnyTransactionEnvelope")]
248pub enum AnyTxEnvelope {
249    /// An Ethereum transaction.
250    Ethereum(TxEnvelope),
251    /// A transaction with unknown type.
252    Unknown(UnknownTxEnvelope),
253}
254
255impl From<Signed<AnyTypedTransaction>> for AnyTxEnvelope {
256    fn from(value: Signed<AnyTypedTransaction>) -> Self {
257        let sig = *value.signature();
258        let tx = value.strip_signature();
259        match tx {
260            AnyTypedTransaction::Ethereum(typed_tx) => Self::Ethereum(match typed_tx {
261                TypedTransaction::Legacy(tx) => TxEnvelope::Legacy(tx.into_signed(sig)),
262                TypedTransaction::Eip2930(tx) => TxEnvelope::Eip2930(tx.into_signed(sig)),
263                TypedTransaction::Eip1559(tx) => TxEnvelope::Eip1559(tx.into_signed(sig)),
264                TypedTransaction::Eip4844(tx) => TxEnvelope::Eip4844(tx.into_signed(sig)),
265                TypedTransaction::Eip7702(tx) => TxEnvelope::Eip7702(tx.into_signed(sig)),
266            }),
267            AnyTypedTransaction::Unknown(unknown_tx) => {
268                panic_unknown_transaction_signing(unknown_tx.ty())
269            }
270        }
271    }
272}
273
274impl AnyTxEnvelope {
275    /// Returns true if this is the ethereum transaction variant
276    pub const fn is_ethereum(&self) -> bool {
277        matches!(self, Self::Ethereum(_))
278    }
279
280    /// Returns true if this is the unknown transaction variant
281    pub const fn is_unknown(&self) -> bool {
282        matches!(self, Self::Unknown(_))
283    }
284
285    /// Returns the inner Ethereum transaction envelope, if it is an Ethereum transaction.
286    pub const fn as_envelope(&self) -> Option<&TxEnvelope> {
287        match self {
288            Self::Ethereum(inner) => Some(inner),
289            Self::Unknown(_) => None,
290        }
291    }
292
293    /// Returns the inner [`UnknownTxEnvelope`] if it is an unknown variant.
294    pub const fn as_unknown(&self) -> Option<&UnknownTxEnvelope> {
295        match self {
296            Self::Unknown(inner) => Some(inner),
297            Self::Ethereum(_) => None,
298        }
299    }
300
301    /// Returns the inner Ethereum transaction envelope, if it is an Ethereum transaction.
302    /// If the transaction is not an Ethereum transaction, it is returned as an error.
303    pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<Self>> {
304        match self {
305            Self::Ethereum(inner) => Ok(inner),
306            this => Err(ValueError::new_static(this, "unknown transaction envelope")),
307        }
308    }
309
310    /// Returns the inner [`UnknownTxEnvelope`], if it is an unknown transaction.
311    /// If the transaction is not an unknown transaction, it is returned as an error.
312    pub fn try_into_unknown(self) -> Result<UnknownTxEnvelope, Self> {
313        match self {
314            Self::Unknown(inner) => Ok(inner),
315            this => Err(this),
316        }
317    }
318
319    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an unknown
320    /// variant.
321    ///
322    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
323    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
324    ///
325    /// # Examples
326    ///
327    /// ```no_run
328    /// # use alloy_network::any::AnyTxEnvelope;
329    /// # use alloy_consensus::transaction::Either;
330    /// # // Assuming you have a custom type: struct CustomTx;
331    /// # // impl TryFrom<alloy_network::any::UnknownTxEnvelope> for CustomTx { ... }
332    /// # fn example(envelope: AnyTxEnvelope) -> Result<(), Box<dyn std::error::Error>> {
333    /// # struct CustomTx;
334    /// # impl TryFrom<alloy_network::any::UnknownTxEnvelope> for CustomTx {
335    /// #     type Error = String;
336    /// #     fn try_from(_: alloy_network::any::UnknownTxEnvelope) -> Result<Self, Self::Error> {
337    /// #         Ok(CustomTx)
338    /// #     }
339    /// # }
340    /// match envelope.try_into_either::<CustomTx>()? {
341    ///     Either::Left(eth_tx) => { /* Ethereum transaction */ }
342    ///     Either::Right(custom_tx) => { /* Custom transaction */ }
343    /// }
344    /// # Ok(())
345    /// # }
346    /// ```
347    pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
348    where
349        T: TryFrom<UnknownTxEnvelope>,
350    {
351        self.try_map_unknown(|inner| inner.try_into())
352    }
353
354    /// Attempts to convert the [`UnknownTxEnvelope`] into `Either::Right` if this is an
355    /// [`AnyTxEnvelope::Unknown`].
356    ///
357    /// Returns `Either::Left` with the ethereum `TxEnvelope` if this is the
358    /// [`AnyTxEnvelope::Ethereum`] variant and [`Either::Right`] with the converted variant.
359    ///
360    /// Use this for custom conversion logic when [`try_into_either`](Self::try_into_either) is
361    /// insufficient.
362    ///
363    /// # Examples
364    ///
365    /// ```no_run
366    /// # use alloy_network::any::AnyTxEnvelope;
367    /// # use alloy_consensus::transaction::Either;
368    /// # use alloy_primitives::B256;
369    /// # fn example(envelope: AnyTxEnvelope) -> Result<(), Box<dyn std::error::Error>> {
370    /// let result = envelope.try_map_unknown(|unknown| Ok::<B256, String>(unknown.hash))?;
371    /// match result {
372    ///     Either::Left(eth_tx) => { /* Ethereum */ }
373    ///     Either::Right(hash) => { /* Unknown tx hash */ }
374    /// }
375    /// # Ok(())
376    /// # }
377    /// ```
378    pub fn try_map_unknown<T, E>(
379        self,
380        f: impl FnOnce(UnknownTxEnvelope) -> Result<T, E>,
381    ) -> Result<Either<TxEnvelope, T>, E> {
382        match self {
383            Self::Ethereum(tx) => Ok(Either::Left(tx)),
384            Self::Unknown(tx) => Ok(Either::Right(f(tx)?)),
385        }
386    }
387
388    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
389    pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
390        match self.as_envelope() {
391            Some(TxEnvelope::Legacy(tx)) => Some(tx),
392            _ => None,
393        }
394    }
395
396    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
397    pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
398        match self.as_envelope() {
399            Some(TxEnvelope::Eip2930(tx)) => Some(tx),
400            _ => None,
401        }
402    }
403
404    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
405    pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
406        match self.as_envelope() {
407            Some(TxEnvelope::Eip1559(tx)) => Some(tx),
408            _ => None,
409        }
410    }
411
412    /// Returns the [`TxEip4844Variant`] variant if the transaction is an EIP-4844 transaction.
413    pub const fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
414        match self.as_envelope() {
415            Some(TxEnvelope::Eip4844(tx)) => Some(tx),
416            _ => None,
417        }
418    }
419
420    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
421    pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
422        match self.as_envelope() {
423            Some(TxEnvelope::Eip7702(tx)) => Some(tx),
424            _ => None,
425        }
426    }
427
428    /// Returns true if the transaction is a legacy transaction.
429    #[inline]
430    pub const fn is_legacy(&self) -> bool {
431        matches!(self.as_envelope(), Some(TxEnvelope::Legacy(_)))
432    }
433
434    /// Returns true if the transaction is an EIP-2930 transaction.
435    #[inline]
436    pub const fn is_eip2930(&self) -> bool {
437        matches!(self.as_envelope(), Some(TxEnvelope::Eip2930(_)))
438    }
439
440    /// Returns true if the transaction is an EIP-1559 transaction.
441    #[inline]
442    pub const fn is_eip1559(&self) -> bool {
443        matches!(self.as_envelope(), Some(TxEnvelope::Eip1559(_)))
444    }
445
446    /// Returns true if the transaction is an EIP-4844 transaction.
447    #[inline]
448    pub const fn is_eip4844(&self) -> bool {
449        matches!(self.as_envelope(), Some(TxEnvelope::Eip4844(_)))
450    }
451
452    /// Returns true if the transaction is an EIP-7702 transaction.
453    #[inline]
454    pub const fn is_eip7702(&self) -> bool {
455        matches!(self.as_envelope(), Some(TxEnvelope::Eip7702(_)))
456    }
457}
458
459impl Typed2718 for AnyTxEnvelope {
460    fn ty(&self) -> u8 {
461        match self {
462            Self::Ethereum(inner) => inner.ty(),
463            Self::Unknown(inner) => inner.ty(),
464        }
465    }
466}
467
468impl Encodable2718 for AnyTxEnvelope {
469    fn encode_2718_len(&self) -> usize {
470        match self {
471            Self::Ethereum(t) => t.encode_2718_len(),
472            Self::Unknown(inner) => panic_unknown_transaction_encoding(inner.ty()),
473        }
474    }
475
476    #[track_caller]
477    fn encode_2718(&self, out: &mut dyn alloy_primitives::bytes::BufMut) {
478        match self {
479            Self::Ethereum(t) => t.encode_2718(out),
480            Self::Unknown(inner) => panic_unknown_transaction_encoding(inner.ty()),
481        }
482    }
483
484    fn trie_hash(&self) -> B256 {
485        match self {
486            Self::Ethereum(tx) => tx.trie_hash(),
487            Self::Unknown(inner) => inner.hash,
488        }
489    }
490}
491
492impl Decodable2718 for AnyTxEnvelope {
493    fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
494        TxEnvelope::typed_decode(ty, buf).map(Self::Ethereum)
495    }
496
497    fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
498        TxEnvelope::fallback_decode(buf).map(Self::Ethereum)
499    }
500}
501
502impl TransactionTrait for AnyTxEnvelope {
503    #[inline]
504    fn chain_id(&self) -> Option<ChainId> {
505        match self {
506            Self::Ethereum(inner) => inner.chain_id(),
507            Self::Unknown(inner) => inner.chain_id(),
508        }
509    }
510
511    #[inline]
512    fn nonce(&self) -> u64 {
513        match self {
514            Self::Ethereum(inner) => inner.nonce(),
515            Self::Unknown(inner) => inner.nonce(),
516        }
517    }
518
519    #[inline]
520    fn gas_limit(&self) -> u64 {
521        match self {
522            Self::Ethereum(inner) => inner.gas_limit(),
523            Self::Unknown(inner) => inner.gas_limit(),
524        }
525    }
526
527    #[inline]
528    fn gas_price(&self) -> Option<u128> {
529        match self {
530            Self::Ethereum(inner) => inner.gas_price(),
531            Self::Unknown(inner) => inner.gas_price(),
532        }
533    }
534
535    #[inline]
536    fn max_fee_per_gas(&self) -> u128 {
537        match self {
538            Self::Ethereum(inner) => inner.max_fee_per_gas(),
539            Self::Unknown(inner) => inner.max_fee_per_gas(),
540        }
541    }
542
543    #[inline]
544    fn max_priority_fee_per_gas(&self) -> Option<u128> {
545        match self {
546            Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
547            Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
548        }
549    }
550
551    #[inline]
552    fn max_fee_per_blob_gas(&self) -> Option<u128> {
553        match self {
554            Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
555            Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
556        }
557    }
558
559    #[inline]
560    fn priority_fee_or_price(&self) -> u128 {
561        self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
562    }
563
564    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
565        match self {
566            Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
567            Self::Unknown(inner) => inner.effective_gas_price(base_fee),
568        }
569    }
570
571    #[inline]
572    fn is_dynamic_fee(&self) -> bool {
573        match self {
574            Self::Ethereum(inner) => inner.is_dynamic_fee(),
575            Self::Unknown(inner) => inner.is_dynamic_fee(),
576        }
577    }
578
579    fn kind(&self) -> alloy_primitives::TxKind {
580        match self {
581            Self::Ethereum(inner) => inner.kind(),
582            Self::Unknown(inner) => inner.kind(),
583        }
584    }
585
586    #[inline]
587    fn is_create(&self) -> bool {
588        match self {
589            Self::Ethereum(inner) => inner.is_create(),
590            Self::Unknown(inner) => inner.is_create(),
591        }
592    }
593
594    #[inline]
595    fn value(&self) -> U256 {
596        match self {
597            Self::Ethereum(inner) => inner.value(),
598            Self::Unknown(inner) => inner.value(),
599        }
600    }
601
602    #[inline]
603    fn input(&self) -> &Bytes {
604        match self {
605            Self::Ethereum(inner) => inner.input(),
606            Self::Unknown(inner) => inner.input(),
607        }
608    }
609
610    #[inline]
611    fn access_list(&self) -> Option<&AccessList> {
612        match self {
613            Self::Ethereum(inner) => inner.access_list(),
614            Self::Unknown(inner) => inner.access_list(),
615        }
616    }
617
618    #[inline]
619    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
620        match self {
621            Self::Ethereum(inner) => inner.blob_versioned_hashes(),
622            Self::Unknown(inner) => inner.blob_versioned_hashes(),
623        }
624    }
625
626    #[inline]
627    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
628        match self {
629            Self::Ethereum(inner) => inner.authorization_list(),
630            Self::Unknown(inner) => inner.authorization_list(),
631        }
632    }
633}
634
635#[cfg(test)]
636mod tests {
637    use super::*;
638    use crate::AnyTxType;
639    use std::str::FromStr;
640
641    fn unknown_inner_tx() -> UnknownTypedTransaction {
642        UnknownTypedTransaction {
643            ty: AnyTxType(0x7e),
644            fields: Default::default(),
645            memo: Default::default(),
646        }
647    }
648
649    fn unknown_tx() -> AnyTypedTransaction {
650        AnyTypedTransaction::Unknown(unknown_inner_tx())
651    }
652
653    fn unknown_envelope() -> AnyTxEnvelope {
654        AnyTxEnvelope::Unknown(UnknownTxEnvelope { hash: B256::ZERO, inner: unknown_inner_tx() })
655    }
656
657    #[test]
658    #[should_panic(expected = "cannot be signed via AnyTypedTransaction")]
659    fn unknown_transaction_signature_hash_panics() {
660        let tx = unknown_tx();
661        let _ = tx.signature_hash();
662    }
663
664    #[test]
665    #[should_panic(expected = "cannot be signed via AnyTypedTransaction")]
666    fn signed_unknown_transaction_cannot_convert_to_envelope() {
667        let tx = unknown_tx();
668        let sig = Signature::from_str(
669            "48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b",
670        )
671        .unwrap();
672
673        let _: AnyTxEnvelope = tx.into_signed(sig).into();
674    }
675
676    #[test]
677    #[should_panic(expected = "Attempted to encode unknown transaction type")]
678    fn unknown_transaction_encode_2718_len_panics() {
679        let envelope = unknown_envelope();
680        let _ = envelope.encode_2718_len();
681    }
682
683    #[test]
684    #[should_panic(expected = "Attempted to encode unknown transaction type")]
685    fn unknown_transaction_encoded_2718_panics() {
686        let envelope = unknown_envelope();
687        let _ = envelope.encoded_2718();
688    }
689}