alloy_consensus/transaction/
pooled.rs

1//! Defines the exact transaction variant that are allowed to be propagated over the eth p2p
2//! protocol.
3
4use crate::{
5    error::ValueError,
6    transaction::{RlpEcdsaDecodableTx, TxEip1559, TxEip2930, TxEip4844, TxLegacy},
7    SignableTransaction, Signed, Transaction, TxEip4844Variant, TxEip4844WithSidecar, TxEip7702,
8    TxEnvelope, TxType,
9};
10use alloy_eips::{
11    eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
12    eip2930::AccessList,
13    eip7702::SignedAuthorization,
14    Typed2718,
15};
16use alloy_primitives::{
17    bytes, Bytes, ChainId, PrimitiveSignature as Signature, TxHash, TxKind, B256, U256,
18};
19use alloy_rlp::{Decodable, Encodable, Header};
20use core::hash::{Hash, Hasher};
21
22/// All possible transactions that can be included in a response to `GetPooledTransactions`.
23/// A response to `GetPooledTransactions`. This can include either a blob transaction, or a
24/// non-4844 signed transaction.
25///
26/// The difference between this and the [`TxEnvelope`] is that this type always requires the
27/// [`TxEip4844WithSidecar`] variant, because EIP-4844 transaction can only be propagated with the
28/// sidecar over p2p.
29#[derive(Clone, Debug, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))]
32pub enum PooledTransaction {
33    /// An untagged [`TxLegacy`].
34    Legacy(Signed<TxLegacy>),
35    /// A [`TxEip2930`] tagged with type 1.
36    Eip2930(Signed<TxEip2930>),
37    /// A [`TxEip1559`] tagged with type 2.
38    Eip1559(Signed<TxEip1559>),
39    /// A EIP-4844 transaction, which includes the transaction, blob data, commitments, and proofs.
40    Eip4844(Signed<TxEip4844WithSidecar>),
41    /// A [`TxEip7702`] tagged with type 4.
42    Eip7702(Signed<TxEip7702>),
43}
44
45impl PooledTransaction {
46    /// Heavy operation that return signature hash over rlp encoded transaction.
47    /// It is only for signature signing or signer recovery.
48    pub fn signature_hash(&self) -> B256 {
49        match self {
50            Self::Legacy(tx) => tx.signature_hash(),
51            Self::Eip2930(tx) => tx.signature_hash(),
52            Self::Eip1559(tx) => tx.signature_hash(),
53            Self::Eip7702(tx) => tx.signature_hash(),
54            Self::Eip4844(tx) => tx.signature_hash(),
55        }
56    }
57
58    /// Reference to transaction hash. Used to identify transaction.
59    pub fn hash(&self) -> &TxHash {
60        match self {
61            Self::Legacy(tx) => tx.hash(),
62            Self::Eip2930(tx) => tx.hash(),
63            Self::Eip1559(tx) => tx.hash(),
64            Self::Eip7702(tx) => tx.hash(),
65            Self::Eip4844(tx) => tx.hash(),
66        }
67    }
68
69    /// Returns the signature of the transaction.
70    pub const fn signature(&self) -> &Signature {
71        match self {
72            Self::Legacy(tx) => tx.signature(),
73            Self::Eip2930(tx) => tx.signature(),
74            Self::Eip1559(tx) => tx.signature(),
75            Self::Eip7702(tx) => tx.signature(),
76            Self::Eip4844(tx) => tx.signature(),
77        }
78    }
79
80    /// The length of the 2718 encoded envelope in network format. This is the
81    /// length of the header + the length of the type flag and inner encoding.
82    fn network_len(&self) -> usize {
83        let mut payload_length = self.encode_2718_len();
84        if !self.is_legacy() {
85            payload_length += Header { list: false, payload_length }.length();
86        }
87
88        payload_length
89    }
90
91    /// Recover the signer of the transaction.
92    #[cfg(feature = "k256")]
93    pub fn recover_signer(
94        &self,
95    ) -> Result<alloy_primitives::Address, alloy_primitives::SignatureError> {
96        match self {
97            Self::Legacy(tx) => tx.recover_signer(),
98            Self::Eip2930(tx) => tx.recover_signer(),
99            Self::Eip1559(tx) => tx.recover_signer(),
100            Self::Eip4844(tx) => tx.recover_signer(),
101            Self::Eip7702(tx) => tx.recover_signer(),
102        }
103    }
104
105    /// This encodes the transaction _without_ the signature, and is only suitable for creating a
106    /// hash intended for signing.
107    pub fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
108        match self {
109            Self::Legacy(tx) => tx.tx().encode_for_signing(out),
110            Self::Eip2930(tx) => tx.tx().encode_for_signing(out),
111            Self::Eip1559(tx) => tx.tx().encode_for_signing(out),
112            Self::Eip4844(tx) => tx.tx().encode_for_signing(out),
113            Self::Eip7702(tx) => tx.tx().encode_for_signing(out),
114        }
115    }
116
117    /// Converts the transaction into [`TxEnvelope`].
118    pub fn into_envelope(self) -> TxEnvelope {
119        match self {
120            Self::Legacy(tx) => tx.into(),
121            Self::Eip2930(tx) => tx.into(),
122            Self::Eip1559(tx) => tx.into(),
123            Self::Eip7702(tx) => tx.into(),
124            Self::Eip4844(tx) => tx.into(),
125        }
126    }
127
128    /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
129    pub const fn as_legacy(&self) -> Option<&TxLegacy> {
130        match self {
131            Self::Legacy(tx) => Some(tx.tx()),
132            _ => None,
133        }
134    }
135
136    /// Returns the [`TxEip2930`] variant if the transaction is an EIP-2930 transaction.
137    pub const fn as_eip2930(&self) -> Option<&TxEip2930> {
138        match self {
139            Self::Eip2930(tx) => Some(tx.tx()),
140            _ => None,
141        }
142    }
143
144    /// Returns the [`TxEip1559`] variant if the transaction is an EIP-1559 transaction.
145    pub const fn as_eip1559(&self) -> Option<&TxEip1559> {
146        match self {
147            Self::Eip1559(tx) => Some(tx.tx()),
148            _ => None,
149        }
150    }
151
152    /// Returns the [`TxEip4844WithSidecar`] variant if the transaction is an EIP-4844 transaction.
153    pub const fn as_eip4844_with_sidecar(&self) -> Option<&TxEip4844WithSidecar> {
154        match self {
155            Self::Eip4844(tx) => Some(tx.tx()),
156            _ => None,
157        }
158    }
159
160    /// Returns the [`TxEip4844`] variant if the transaction is an EIP-4844 transaction.
161    pub const fn as_eip4844(&self) -> Option<&TxEip4844> {
162        match self {
163            Self::Eip4844(tx) => Some(tx.tx().tx()),
164            _ => None,
165        }
166    }
167
168    /// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
169    pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
170        match self {
171            Self::Eip7702(tx) => Some(tx.tx()),
172            _ => None,
173        }
174    }
175
176    /// Attempts to unwrap the transaction into a legacy transaction variant.
177    /// If the transaction is not a legacy transaction, it will return `Err(self)`.
178    pub fn try_into_legacy(self) -> Result<Signed<TxLegacy>, Self> {
179        match self {
180            Self::Legacy(tx) => Ok(tx),
181            tx => Err(tx),
182        }
183    }
184
185    /// Attempts to unwrap the transaction into an EIP-2930 transaction variant.
186    /// If the transaction is not an EIP-2930 transaction, it will return `Err(self)`.
187    pub fn try_into_eip2930(self) -> Result<Signed<TxEip2930>, Self> {
188        match self {
189            Self::Eip2930(tx) => Ok(tx),
190            tx => Err(tx),
191        }
192    }
193
194    /// Attempts to unwrap the transaction into an EIP-1559 transaction variant.
195    /// If the transaction is not an EIP-1559 transaction, it will return `Err(self)`.
196    pub fn try_into_eip1559(self) -> Result<Signed<TxEip1559>, Self> {
197        match self {
198            Self::Eip1559(tx) => Ok(tx),
199            tx => Err(tx),
200        }
201    }
202
203    /// Attempts to unwrap the transaction into an EIP-4844 transaction variant.
204    /// If the transaction is not an EIP-4844 transaction, it will return `Err(self)`.
205    pub fn try_into_eip4844(self) -> Result<Signed<TxEip4844WithSidecar>, Self> {
206        match self {
207            Self::Eip4844(tx) => Ok(tx),
208            tx => Err(tx),
209        }
210    }
211
212    /// Attempts to unwrap the transaction into an EIP-7702 transaction variant.
213    /// If the transaction is not an EIP-7702 transaction, it will return `Err(self)`.
214    pub fn try_into_eip7702(self) -> Result<Signed<TxEip7702>, Self> {
215        match self {
216            Self::Eip7702(tx) => Ok(tx),
217            tx => Err(tx),
218        }
219    }
220}
221
222impl From<Signed<TxLegacy>> for PooledTransaction {
223    fn from(v: Signed<TxLegacy>) -> Self {
224        Self::Legacy(v)
225    }
226}
227
228impl From<Signed<TxEip2930>> for PooledTransaction {
229    fn from(v: Signed<TxEip2930>) -> Self {
230        Self::Eip2930(v)
231    }
232}
233
234impl From<Signed<TxEip1559>> for PooledTransaction {
235    fn from(v: Signed<TxEip1559>) -> Self {
236        Self::Eip1559(v)
237    }
238}
239
240impl From<Signed<TxEip4844WithSidecar>> for PooledTransaction {
241    fn from(v: Signed<TxEip4844WithSidecar>) -> Self {
242        let (tx, signature, hash) = v.into_parts();
243        Self::Eip4844(Signed::new_unchecked(tx, signature, hash))
244    }
245}
246
247impl TryFrom<Signed<TxEip4844Variant>> for PooledTransaction {
248    type Error = ValueError<Signed<TxEip4844Variant>>;
249
250    fn try_from(value: Signed<TxEip4844Variant>) -> Result<Self, Self::Error> {
251        let (value, signature, hash) = value.into_parts();
252        match value {
253            tx @ TxEip4844Variant::TxEip4844(_) => Err(ValueError::new_static(
254                Signed::new_unchecked(tx, signature, hash),
255                "pooled transaction requires 4844 sidecar",
256            )),
257            TxEip4844Variant::TxEip4844WithSidecar(tx) => {
258                Ok(Signed::new_unchecked(tx, signature, hash).into())
259            }
260        }
261    }
262}
263
264impl TryFrom<TxEnvelope> for PooledTransaction {
265    type Error = ValueError<TxEnvelope>;
266
267    fn try_from(value: TxEnvelope) -> Result<Self, Self::Error> {
268        value.try_into_pooled()
269    }
270}
271
272impl From<Signed<TxEip7702>> for PooledTransaction {
273    fn from(v: Signed<TxEip7702>) -> Self {
274        Self::Eip7702(v)
275    }
276}
277
278impl Hash for PooledTransaction {
279    fn hash<H: Hasher>(&self, state: &mut H) {
280        self.trie_hash().hash(state);
281    }
282}
283
284impl Encodable for PooledTransaction {
285    /// This encodes the transaction _with_ the signature, and an rlp header.
286    ///
287    /// For legacy transactions, it encodes the transaction data:
288    /// `rlp(tx-data)`
289    ///
290    /// For EIP-2718 typed transactions, it encodes the transaction type followed by the rlp of the
291    /// transaction:
292    /// `rlp(tx-type || rlp(tx-data))`
293    fn encode(&self, out: &mut dyn bytes::BufMut) {
294        self.network_encode(out);
295    }
296
297    fn length(&self) -> usize {
298        self.network_len()
299    }
300}
301
302impl Decodable for PooledTransaction {
303    /// Decodes an enveloped post EIP-4844 [`PooledTransaction`].
304    ///
305    /// CAUTION: this expects that `buf` is `rlp(tx_type || rlp(tx-data))`
306    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
307        Ok(Self::network_decode(buf)?)
308    }
309}
310
311impl Encodable2718 for PooledTransaction {
312    fn encode_2718_len(&self) -> usize {
313        match self {
314            Self::Legacy(tx) => tx.eip2718_encoded_length(),
315            Self::Eip2930(tx) => tx.eip2718_encoded_length(),
316            Self::Eip1559(tx) => tx.eip2718_encoded_length(),
317            Self::Eip7702(tx) => tx.eip2718_encoded_length(),
318            Self::Eip4844(tx) => tx.eip2718_encoded_length(),
319        }
320    }
321
322    fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
323        match self {
324            Self::Legacy(tx) => tx.eip2718_encode(out),
325            Self::Eip2930(tx) => tx.eip2718_encode(out),
326            Self::Eip1559(tx) => tx.eip2718_encode(out),
327            Self::Eip7702(tx) => tx.eip2718_encode(out),
328            Self::Eip4844(tx) => tx.eip2718_encode(out),
329        }
330    }
331
332    fn trie_hash(&self) -> B256 {
333        *self.hash()
334    }
335}
336
337impl Decodable2718 for PooledTransaction {
338    fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
339        match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("unexpected tx type"))? {
340            TxType::Eip2930 => Ok(TxEip2930::rlp_decode_signed(buf)?.into()),
341            TxType::Eip1559 => Ok(TxEip1559::rlp_decode_signed(buf)?.into()),
342            TxType::Eip4844 => Ok(TxEip4844WithSidecar::rlp_decode_signed(buf)?.into()),
343            TxType::Eip7702 => Ok(TxEip7702::rlp_decode_signed(buf)?.into()),
344            TxType::Legacy => Err(Eip2718Error::UnexpectedType(0)),
345        }
346    }
347
348    fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
349        TxLegacy::rlp_decode_signed(buf).map(Into::into).map_err(Into::into)
350    }
351}
352
353impl Transaction for PooledTransaction {
354    fn chain_id(&self) -> Option<ChainId> {
355        match self {
356            Self::Legacy(tx) => tx.tx().chain_id(),
357            Self::Eip2930(tx) => tx.tx().chain_id(),
358            Self::Eip1559(tx) => tx.tx().chain_id(),
359            Self::Eip7702(tx) => tx.tx().chain_id(),
360            Self::Eip4844(tx) => tx.tx().chain_id(),
361        }
362    }
363
364    fn nonce(&self) -> u64 {
365        match self {
366            Self::Legacy(tx) => tx.tx().nonce(),
367            Self::Eip2930(tx) => tx.tx().nonce(),
368            Self::Eip1559(tx) => tx.tx().nonce(),
369            Self::Eip7702(tx) => tx.tx().nonce(),
370            Self::Eip4844(tx) => tx.tx().nonce(),
371        }
372    }
373
374    fn gas_limit(&self) -> u64 {
375        match self {
376            Self::Legacy(tx) => tx.tx().gas_limit(),
377            Self::Eip2930(tx) => tx.tx().gas_limit(),
378            Self::Eip1559(tx) => tx.tx().gas_limit(),
379            Self::Eip7702(tx) => tx.tx().gas_limit(),
380            Self::Eip4844(tx) => tx.tx().gas_limit(),
381        }
382    }
383
384    fn gas_price(&self) -> Option<u128> {
385        match self {
386            Self::Legacy(tx) => tx.tx().gas_price(),
387            Self::Eip2930(tx) => tx.tx().gas_price(),
388            Self::Eip1559(tx) => tx.tx().gas_price(),
389            Self::Eip7702(tx) => tx.tx().gas_price(),
390            Self::Eip4844(tx) => tx.tx().gas_price(),
391        }
392    }
393
394    fn max_fee_per_gas(&self) -> u128 {
395        match self {
396            Self::Legacy(tx) => tx.tx().max_fee_per_gas(),
397            Self::Eip2930(tx) => tx.tx().max_fee_per_gas(),
398            Self::Eip1559(tx) => tx.tx().max_fee_per_gas(),
399            Self::Eip7702(tx) => tx.tx().max_fee_per_gas(),
400            Self::Eip4844(tx) => tx.tx().max_fee_per_gas(),
401        }
402    }
403
404    fn max_priority_fee_per_gas(&self) -> Option<u128> {
405        match self {
406            Self::Legacy(tx) => tx.tx().max_priority_fee_per_gas(),
407            Self::Eip2930(tx) => tx.tx().max_priority_fee_per_gas(),
408            Self::Eip1559(tx) => tx.tx().max_priority_fee_per_gas(),
409            Self::Eip7702(tx) => tx.tx().max_priority_fee_per_gas(),
410            Self::Eip4844(tx) => tx.tx().max_priority_fee_per_gas(),
411        }
412    }
413
414    fn max_fee_per_blob_gas(&self) -> Option<u128> {
415        match self {
416            Self::Legacy(tx) => tx.tx().max_fee_per_blob_gas(),
417            Self::Eip2930(tx) => tx.tx().max_fee_per_blob_gas(),
418            Self::Eip1559(tx) => tx.tx().max_fee_per_blob_gas(),
419            Self::Eip7702(tx) => tx.tx().max_fee_per_blob_gas(),
420            Self::Eip4844(tx) => tx.tx().max_fee_per_blob_gas(),
421        }
422    }
423
424    fn priority_fee_or_price(&self) -> u128 {
425        match self {
426            Self::Legacy(tx) => tx.tx().priority_fee_or_price(),
427            Self::Eip2930(tx) => tx.tx().priority_fee_or_price(),
428            Self::Eip1559(tx) => tx.tx().priority_fee_or_price(),
429            Self::Eip7702(tx) => tx.tx().priority_fee_or_price(),
430            Self::Eip4844(tx) => tx.tx().priority_fee_or_price(),
431        }
432    }
433
434    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
435        match self {
436            Self::Legacy(tx) => tx.tx().effective_gas_price(base_fee),
437            Self::Eip2930(tx) => tx.tx().effective_gas_price(base_fee),
438            Self::Eip1559(tx) => tx.tx().effective_gas_price(base_fee),
439            Self::Eip7702(tx) => tx.tx().effective_gas_price(base_fee),
440            Self::Eip4844(tx) => tx.tx().effective_gas_price(base_fee),
441        }
442    }
443
444    fn is_dynamic_fee(&self) -> bool {
445        match self {
446            Self::Legacy(tx) => tx.tx().is_dynamic_fee(),
447            Self::Eip2930(tx) => tx.tx().is_dynamic_fee(),
448            Self::Eip1559(tx) => tx.tx().is_dynamic_fee(),
449            Self::Eip7702(tx) => tx.tx().is_dynamic_fee(),
450            Self::Eip4844(tx) => tx.tx().is_dynamic_fee(),
451        }
452    }
453
454    fn kind(&self) -> TxKind {
455        match self {
456            Self::Legacy(tx) => tx.tx().kind(),
457            Self::Eip2930(tx) => tx.tx().kind(),
458            Self::Eip1559(tx) => tx.tx().kind(),
459            Self::Eip7702(tx) => tx.tx().kind(),
460            Self::Eip4844(tx) => tx.tx().kind(),
461        }
462    }
463
464    fn is_create(&self) -> bool {
465        match self {
466            Self::Legacy(tx) => tx.tx().is_create(),
467            Self::Eip2930(tx) => tx.tx().is_create(),
468            Self::Eip1559(tx) => tx.tx().is_create(),
469            Self::Eip7702(tx) => tx.tx().is_create(),
470            Self::Eip4844(tx) => tx.tx().is_create(),
471        }
472    }
473
474    fn value(&self) -> U256 {
475        match self {
476            Self::Legacy(tx) => tx.tx().value(),
477            Self::Eip2930(tx) => tx.tx().value(),
478            Self::Eip1559(tx) => tx.tx().value(),
479            Self::Eip7702(tx) => tx.tx().value(),
480            Self::Eip4844(tx) => tx.tx().value(),
481        }
482    }
483
484    fn input(&self) -> &Bytes {
485        match self {
486            Self::Legacy(tx) => tx.tx().input(),
487            Self::Eip2930(tx) => tx.tx().input(),
488            Self::Eip1559(tx) => tx.tx().input(),
489            Self::Eip7702(tx) => tx.tx().input(),
490            Self::Eip4844(tx) => tx.tx().input(),
491        }
492    }
493
494    fn access_list(&self) -> Option<&AccessList> {
495        match self {
496            Self::Legacy(tx) => tx.tx().access_list(),
497            Self::Eip2930(tx) => tx.tx().access_list(),
498            Self::Eip1559(tx) => tx.tx().access_list(),
499            Self::Eip7702(tx) => tx.tx().access_list(),
500            Self::Eip4844(tx) => tx.tx().access_list(),
501        }
502    }
503
504    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
505        match self {
506            Self::Legacy(tx) => tx.tx().blob_versioned_hashes(),
507            Self::Eip2930(tx) => tx.tx().blob_versioned_hashes(),
508            Self::Eip1559(tx) => tx.tx().blob_versioned_hashes(),
509            Self::Eip7702(tx) => tx.tx().blob_versioned_hashes(),
510            Self::Eip4844(tx) => tx.tx().blob_versioned_hashes(),
511        }
512    }
513
514    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
515        match self {
516            Self::Legacy(tx) => tx.tx().authorization_list(),
517            Self::Eip2930(tx) => tx.tx().authorization_list(),
518            Self::Eip1559(tx) => tx.tx().authorization_list(),
519            Self::Eip7702(tx) => tx.tx().authorization_list(),
520            Self::Eip4844(tx) => tx.tx().authorization_list(),
521        }
522    }
523}
524
525impl Typed2718 for PooledTransaction {
526    fn ty(&self) -> u8 {
527        match self {
528            Self::Legacy(tx) => tx.tx().ty(),
529            Self::Eip2930(tx) => tx.tx().ty(),
530            Self::Eip1559(tx) => tx.tx().ty(),
531            Self::Eip7702(tx) => tx.tx().ty(),
532            Self::Eip4844(tx) => tx.tx().ty(),
533        }
534    }
535}
536
537impl From<PooledTransaction> for TxEnvelope {
538    fn from(tx: PooledTransaction) -> Self {
539        tx.into_envelope()
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use super::*;
546    use alloy_primitives::{address, hex};
547    use bytes::Bytes;
548    use std::path::PathBuf;
549
550    #[test]
551    fn invalid_legacy_pooled_decoding_input_too_short() {
552        let input_too_short = [
553            // this should fail because the payload length is longer than expected
554            &hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..],
555            // these should fail decoding
556            //
557            // The `c1` at the beginning is a list header, and the rest is a valid legacy
558            // transaction, BUT the payload length of the list header is 1, and the payload is
559            // obviously longer than one byte.
560            &hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"),
561            &hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"),
562            // this one is 19 bytes, and the buf is long enough, but the transaction will not
563            // consume that many bytes.
564            &hex!("d40b02808083c5cdeb8783c5acfd9e407c5656565656"),
565            &hex!("d30102808083c5cd02887dc5cdfd9e64fd9e407c56"),
566        ];
567
568        for hex_data in &input_too_short {
569            let input_rlp = &mut &hex_data[..];
570            let res = PooledTransaction::decode(input_rlp);
571
572            assert!(
573                res.is_err(),
574                "expected err after decoding rlp input: {:x?}",
575                Bytes::copy_from_slice(hex_data)
576            );
577
578            // this is a legacy tx so we can attempt the same test with decode_enveloped
579            let input_rlp = &mut &hex_data[..];
580            let res = PooledTransaction::decode_2718(input_rlp);
581
582            assert!(
583                res.is_err(),
584                "expected err after decoding enveloped rlp input: {:x?}",
585                Bytes::copy_from_slice(hex_data)
586            );
587        }
588    }
589
590    // <https://holesky.etherscan.io/tx/0x7f60faf8a410a80d95f7ffda301d5ab983545913d3d789615df3346579f6c849>
591    #[test]
592    fn decode_eip1559_enveloped() {
593        let data = hex!("02f903d382426882ba09832dc6c0848674742682ed9694714b6a4ea9b94a8a7d9fd362ed72630688c8898c80b90364492d24749189822d8512430d3f3ff7a2ede675ac08265c08e2c56ff6fdaa66dae1cdbe4a5d1d7809f3e99272d067364e597542ac0c369d69e22a6399c3e9bee5da4b07e3f3fdc34c32c3d88aa2268785f3e3f8086df0934b10ef92cfffc2e7f3d90f5e83302e31382e302d64657600000000000000000000000000000000000000000000569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000e1e210594771824dad216568b91c9cb4ceed361c00000000000000000000000000000000000000000000000000000000000546e00000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000065d6750c00000000000000000000000000000000000000000000000000000000000f288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cf600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000f1628e56fa6d8c50e5b984a58c0df14de31c7b857ce7ba499945b99252976a93d06dcda6776fc42167fbe71cb59f978f5ef5b12577a90b132d14d9c6efa528076f0161d7bf03643cfc5490ec5084f4a041db7f06c50bd97efa08907ba79ddcac8b890f24d12d8db31abbaaf18985d54f400449ee0559a4452afe53de5853ce090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000c080a01428023fc54a27544abc421d5d017b9a7c5936ad501cbdecd0d9d12d04c1a033a0753104bbf1c87634d6ff3f0ffa0982710612306003eb022363b57994bdef445a"
594);
595
596        let res = PooledTransaction::decode_2718(&mut &data[..]).unwrap();
597        assert_eq!(res.to(), Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c")));
598    }
599
600    #[test]
601    fn legacy_valid_pooled_decoding() {
602        // d3 <- payload length, d3 - c0 = 0x13 = 19
603        // 0b <- nonce
604        // 02 <- gas_price
605        // 80 <- gas_limit
606        // 80 <- to (Create)
607        // 83 c5cdeb <- value
608        // 87 83c5acfd9e407c <- input
609        // 56 <- v (eip155, so modified with a chain id)
610        // 56 <- r
611        // 56 <- s
612        let data = &hex!("d30b02808083c5cdeb8783c5acfd9e407c565656")[..];
613
614        let input_rlp = &mut &data[..];
615        let res = PooledTransaction::decode(input_rlp);
616        assert!(res.is_ok());
617        assert!(input_rlp.is_empty());
618
619        // we can also decode_enveloped
620        let res = PooledTransaction::decode_2718(&mut &data[..]);
621        assert!(res.is_ok());
622    }
623
624    #[test]
625    fn decode_encode_raw_4844_rlp() {
626        let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/4844rlp");
627        let dir = std::fs::read_dir(path).expect("Unable to read folder");
628        for entry in dir {
629            let entry = entry.unwrap();
630            let content = std::fs::read_to_string(entry.path()).unwrap();
631            let raw = hex::decode(content.trim()).unwrap();
632            let tx = PooledTransaction::decode_2718(&mut raw.as_ref())
633                .map_err(|err| {
634                    panic!("Failed to decode transaction: {:?} {:?}", err, entry.path());
635                })
636                .unwrap();
637            // We want to test only EIP-4844 transactions
638            assert!(tx.is_eip4844());
639            let encoded = tx.encoded_2718();
640            assert_eq!(encoded.as_slice(), &raw[..], "{:?}", entry.path());
641        }
642    }
643}