Skip to main content

reth_transaction_pool/test_utils/
mock.rs

1//! Mock types.
2
3use crate::{
4    error::RawPoolTransactionError,
5    identifier::{SenderIdentifiers, TransactionId},
6    pool::txpool::TxPool,
7    traits::TransactionOrigin,
8    CoinbaseTipOrdering, EthBlobTransactionSidecar, EthPoolTransaction, PoolTransaction,
9    ValidPoolTransaction,
10};
11use alloy_consensus::{
12    constants::{
13        EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
14        LEGACY_TX_TYPE_ID,
15    },
16    EthereumTxEnvelope, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702,
17    TxEnvelope, TxLegacy, TxType, Typed2718,
18};
19use alloy_eips::{
20    eip1559::{calc_effective_gas_price, MIN_PROTOCOL_BASE_FEE},
21    eip2718::Encodable2718,
22    eip2930::AccessList,
23    eip4844::{BlobTransactionSidecar, BlobTransactionValidationError, DATA_GAS_PER_BLOB},
24    eip7594::BlobTransactionSidecarVariant,
25    eip7702::SignedAuthorization,
26};
27use alloy_primitives::{Address, Bytes, ChainId, Signature, TxHash, TxKind, B256, U256};
28use paste::paste;
29use rand::{distr::Uniform, prelude::Distribution};
30use reth_ethereum_primitives::{PooledTransactionVariant, Transaction, TransactionSigned};
31use reth_primitives_traits::{
32    transaction::error::TryFromRecoveredTransactionError, InMemorySize, Recovered,
33    SignedTransaction,
34};
35
36use alloy_consensus::error::ValueError;
37use alloy_eips::eip4844::env_settings::KzgSettings;
38use rand::distr::weighted::WeightedIndex;
39use std::{ops::Range, sync::Arc, time::Instant, vec::IntoIter};
40
41/// A transaction pool implementation using [`MockOrdering`] for transaction ordering.
42///
43/// This type is an alias for [`TxPool<MockOrdering>`].
44pub type MockTxPool = TxPool<MockOrdering>;
45
46/// A validated transaction in the transaction pool, using [`MockTransaction`] as the transaction
47/// type.
48///
49/// This type is an alias for [`ValidPoolTransaction<MockTransaction>`].
50pub type MockValidTx = ValidPoolTransaction<MockTransaction>;
51
52/// Create an empty `TxPool`
53pub fn mock_tx_pool() -> MockTxPool {
54    MockTxPool::new(Default::default(), Default::default())
55}
56
57/// Sets the value for the field
58macro_rules! set_value {
59    // For mutable references
60    (&mut $this:expr => $field:ident) => {{
61        let new_value = $field;
62        match $this {
63            MockTransaction::Legacy { $field, .. } => {
64                *$field = new_value;
65            }
66            MockTransaction::Eip1559 { $field, .. } => {
67                *$field = new_value;
68            }
69            MockTransaction::Eip4844 { $field, .. } => {
70                *$field = new_value;
71            }
72            MockTransaction::Eip2930 { $field, .. } => {
73                *$field = new_value;
74            }
75            MockTransaction::Eip7702 { $field, .. } => {
76                *$field = new_value;
77            }
78        }
79        // Ensure the tx cost is always correct after each mutation.
80        $this.update_cost();
81    }};
82    // For owned values
83    ($this:expr => $field:ident) => {{
84        let new_value = $field;
85        match $this {
86            MockTransaction::Legacy { ref mut $field, .. } |
87            MockTransaction::Eip1559 { ref mut $field, .. } |
88            MockTransaction::Eip4844 { ref mut $field, .. } |
89            MockTransaction::Eip2930 { ref mut $field, .. } |
90            MockTransaction::Eip7702 { ref mut $field, .. } => {
91                *$field = new_value;
92            }
93        }
94        // Ensure the tx cost is always correct after each mutation.
95        $this.update_cost();
96    }};
97}
98
99/// Gets the value for the field
100macro_rules! get_value {
101    ($this:tt => $field:ident) => {
102        match $this {
103            MockTransaction::Legacy { $field, .. } |
104            MockTransaction::Eip1559 { $field, .. } |
105            MockTransaction::Eip4844 { $field, .. } |
106            MockTransaction::Eip2930 { $field, .. } |
107            MockTransaction::Eip7702 { $field, .. } => $field,
108        }
109    };
110}
111
112// Generates all setters and getters
113macro_rules! make_setters_getters {
114    ($($name:ident => $t:ty);*) => {
115        paste! {$(
116            /// Sets the value of the specified field.
117            pub fn [<set_ $name>](&mut self, $name: $t) -> &mut Self {
118                set_value!(&mut self => $name);
119                self
120            }
121
122            /// Sets the value of the specified field using a fluent interface.
123            pub fn [<with_ $name>](mut self, $name: $t) -> Self {
124                set_value!(self => $name);
125                self
126            }
127
128            /// Gets the value of the specified field.
129            pub const fn [<get_ $name>](&self) -> &$t {
130                get_value!(self => $name)
131            }
132        )*}
133    };
134}
135
136/// A Bare transaction type used for testing.
137#[derive(Debug, Clone, Eq, PartialEq)]
138pub enum MockTransaction {
139    /// Legacy transaction type.
140    Legacy {
141        /// The chain id of the transaction.
142        chain_id: Option<ChainId>,
143        /// The hash of the transaction.
144        hash: B256,
145        /// The sender's address.
146        sender: Address,
147        /// The transaction nonce.
148        nonce: u64,
149        /// The gas price for the transaction.
150        gas_price: u128,
151        /// The gas limit for the transaction.
152        gas_limit: u64,
153        /// The transaction's destination.
154        to: TxKind,
155        /// The value of the transaction.
156        value: U256,
157        /// The transaction input data.
158        input: Bytes,
159        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
160        size: usize,
161        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
162        cost: U256,
163    },
164    /// EIP-2930 transaction type.
165    Eip2930 {
166        /// The chain id of the transaction.
167        chain_id: ChainId,
168        /// The hash of the transaction.
169        hash: B256,
170        /// The sender's address.
171        sender: Address,
172        /// The transaction nonce.
173        nonce: u64,
174        /// The transaction's destination.
175        to: TxKind,
176        /// The gas limit for the transaction.
177        gas_limit: u64,
178        /// The transaction input data.
179        input: Bytes,
180        /// The value of the transaction.
181        value: U256,
182        /// The gas price for the transaction.
183        gas_price: u128,
184        /// The access list associated with the transaction.
185        access_list: AccessList,
186        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
187        size: usize,
188        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
189        cost: U256,
190    },
191    /// EIP-1559 transaction type.
192    Eip1559 {
193        /// The chain id of the transaction.
194        chain_id: ChainId,
195        /// The hash of the transaction.
196        hash: B256,
197        /// The sender's address.
198        sender: Address,
199        /// The transaction nonce.
200        nonce: u64,
201        /// The maximum fee per gas for the transaction.
202        max_fee_per_gas: u128,
203        /// The maximum priority fee per gas for the transaction.
204        max_priority_fee_per_gas: u128,
205        /// The gas limit for the transaction.
206        gas_limit: u64,
207        /// The transaction's destination.
208        to: TxKind,
209        /// The value of the transaction.
210        value: U256,
211        /// The access list associated with the transaction.
212        access_list: AccessList,
213        /// The transaction input data.
214        input: Bytes,
215        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
216        size: usize,
217        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
218        cost: U256,
219    },
220    /// EIP-4844 transaction type.
221    Eip4844 {
222        /// The chain id of the transaction.
223        chain_id: ChainId,
224        /// The hash of the transaction.
225        hash: B256,
226        /// The sender's address.
227        sender: Address,
228        /// The transaction nonce.
229        nonce: u64,
230        /// The maximum fee per gas for the transaction.
231        max_fee_per_gas: u128,
232        /// The maximum priority fee per gas for the transaction.
233        max_priority_fee_per_gas: u128,
234        /// The maximum fee per blob gas for the transaction.
235        max_fee_per_blob_gas: u128,
236        /// The gas limit for the transaction.
237        gas_limit: u64,
238        /// The transaction's destination.
239        to: Address,
240        /// The value of the transaction.
241        value: U256,
242        /// The access list associated with the transaction.
243        access_list: AccessList,
244        /// The transaction input data.
245        input: Bytes,
246        /// The sidecar information for the transaction.
247        sidecar: BlobTransactionSidecarVariant,
248        /// The blob versioned hashes for the transaction.
249        blob_versioned_hashes: Vec<B256>,
250        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
251        size: usize,
252        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
253        cost: U256,
254    },
255    /// EIP-7702 transaction type.
256    Eip7702 {
257        /// The chain id of the transaction.
258        chain_id: ChainId,
259        /// The hash of the transaction.
260        hash: B256,
261        /// The sender's address.
262        sender: Address,
263        /// The transaction nonce.
264        nonce: u64,
265        /// The maximum fee per gas for the transaction.
266        max_fee_per_gas: u128,
267        /// The maximum priority fee per gas for the transaction.
268        max_priority_fee_per_gas: u128,
269        /// The gas limit for the transaction.
270        gas_limit: u64,
271        /// The transaction's destination.
272        to: Address,
273        /// The value of the transaction.
274        value: U256,
275        /// The access list associated with the transaction.
276        access_list: AccessList,
277        /// The authorization list associated with the transaction.
278        authorization_list: Vec<SignedAuthorization>,
279        /// The transaction input data.
280        input: Bytes,
281        /// The size of the transaction, returned in the implementation of [`PoolTransaction`].
282        size: usize,
283        /// The cost of the transaction, returned in the implementation of [`PoolTransaction`].
284        cost: U256,
285    },
286}
287
288// === impl MockTransaction ===
289
290impl MockTransaction {
291    make_setters_getters! {
292        nonce => u64;
293        hash => B256;
294        sender => Address;
295        gas_limit => u64;
296        value => U256;
297        input => Bytes;
298        size => usize
299    }
300
301    /// Returns a new legacy transaction with random address and hash and empty values
302    pub fn legacy() -> Self {
303        Self::Legacy {
304            chain_id: Some(1),
305            hash: B256::random(),
306            sender: Address::random(),
307            nonce: 0,
308            gas_price: 0,
309            gas_limit: 0,
310            to: Address::random().into(),
311            value: Default::default(),
312            input: Default::default(),
313            size: Default::default(),
314            cost: U256::ZERO,
315        }
316    }
317
318    /// Returns a new EIP2930 transaction with random address and hash and empty values
319    pub fn eip2930() -> Self {
320        Self::Eip2930 {
321            chain_id: 1,
322            hash: B256::random(),
323            sender: Address::random(),
324            nonce: 0,
325            to: Address::random().into(),
326            gas_limit: 0,
327            input: Bytes::new(),
328            value: Default::default(),
329            gas_price: 0,
330            access_list: Default::default(),
331            size: Default::default(),
332            cost: U256::ZERO,
333        }
334    }
335
336    /// Returns a new EIP1559 transaction with random address and hash and empty values
337    pub fn eip1559() -> Self {
338        Self::Eip1559 {
339            chain_id: 1,
340            hash: B256::random(),
341            sender: Address::random(),
342            nonce: 0,
343            max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
344            max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
345            gas_limit: 0,
346            to: Address::random().into(),
347            value: Default::default(),
348            input: Bytes::new(),
349            access_list: Default::default(),
350            size: Default::default(),
351            cost: U256::ZERO,
352        }
353    }
354
355    /// Returns a new EIP7702 transaction with random address and hash and empty values
356    pub fn eip7702() -> Self {
357        Self::Eip7702 {
358            chain_id: 1,
359            hash: B256::random(),
360            sender: Address::random(),
361            nonce: 0,
362            max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
363            max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
364            gas_limit: 0,
365            to: Address::random(),
366            value: Default::default(),
367            input: Bytes::new(),
368            access_list: Default::default(),
369            authorization_list: vec![],
370            size: Default::default(),
371            cost: U256::ZERO,
372        }
373    }
374
375    /// Returns a new EIP4844 transaction with random address and hash and empty values
376    pub fn eip4844() -> Self {
377        Self::Eip4844 {
378            chain_id: 1,
379            hash: B256::random(),
380            sender: Address::random(),
381            nonce: 0,
382            max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
383            max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
384            max_fee_per_blob_gas: DATA_GAS_PER_BLOB as u128,
385            gas_limit: 0,
386            to: Address::random(),
387            value: Default::default(),
388            input: Bytes::new(),
389            access_list: Default::default(),
390            sidecar: BlobTransactionSidecarVariant::Eip4844(Default::default()),
391            blob_versioned_hashes: Default::default(),
392            size: Default::default(),
393            cost: U256::ZERO,
394        }
395    }
396
397    /// Returns a new EIP4844 transaction with a provided sidecar
398    pub fn eip4844_with_sidecar(sidecar: BlobTransactionSidecarVariant) -> Self {
399        let mut transaction = Self::eip4844();
400        if let Self::Eip4844 { sidecar: existing_sidecar, blob_versioned_hashes, .. } =
401            &mut transaction
402        {
403            *blob_versioned_hashes = sidecar.versioned_hashes().collect();
404            *existing_sidecar = sidecar;
405        }
406        transaction
407    }
408
409    /// Creates a new transaction with the given [`TxType`].
410    ///
411    /// See the default constructors for each of the transaction types:
412    ///
413    /// * [`MockTransaction::legacy`]
414    /// * [`MockTransaction::eip2930`]
415    /// * [`MockTransaction::eip1559`]
416    /// * [`MockTransaction::eip4844`]
417    pub fn new_from_type(tx_type: TxType) -> Self {
418        match tx_type {
419            TxType::Legacy => Self::legacy(),
420            TxType::Eip2930 => Self::eip2930(),
421            TxType::Eip1559 => Self::eip1559(),
422            TxType::Eip4844 => Self::eip4844(),
423            TxType::Eip7702 => Self::eip7702(),
424        }
425    }
426
427    /// Sets the max fee per blob gas for EIP-4844 transactions,
428    pub const fn with_blob_fee(mut self, val: u128) -> Self {
429        self.set_blob_fee(val);
430        self
431    }
432
433    /// Sets the number of blob versioned hashes for EIP-4844 transactions.
434    pub fn with_blob_hashes(mut self, count: usize) -> Self {
435        if let Self::Eip4844 { blob_versioned_hashes, .. } = &mut self {
436            *blob_versioned_hashes = (0..count).map(|_| B256::random()).collect();
437        }
438        self
439    }
440
441    /// Sets the max fee per blob gas for EIP-4844 transactions,
442    pub const fn set_blob_fee(&mut self, val: u128) -> &mut Self {
443        if let Self::Eip4844 { max_fee_per_blob_gas, .. } = self {
444            *max_fee_per_blob_gas = val;
445        }
446        self
447    }
448
449    /// Sets the priority fee for dynamic fee transactions (EIP-1559 and EIP-4844)
450    pub const fn set_priority_fee(&mut self, val: u128) -> &mut Self {
451        if let Self::Eip1559 { max_priority_fee_per_gas, .. } |
452        Self::Eip4844 { max_priority_fee_per_gas, .. } = self
453        {
454            *max_priority_fee_per_gas = val;
455        }
456        self
457    }
458
459    /// Sets the priority fee for dynamic fee transactions (EIP-1559 and EIP-4844)
460    pub const fn with_priority_fee(mut self, val: u128) -> Self {
461        self.set_priority_fee(val);
462        self
463    }
464
465    /// Gets the priority fee for dynamic fee transactions (EIP-1559 and EIP-4844)
466    pub const fn get_priority_fee(&self) -> Option<u128> {
467        match self {
468            Self::Eip1559 { max_priority_fee_per_gas, .. } |
469            Self::Eip4844 { max_priority_fee_per_gas, .. } |
470            Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
471            _ => None,
472        }
473    }
474
475    /// Sets the max fee for dynamic fee transactions (EIP-1559 and EIP-4844)
476    pub const fn set_max_fee(&mut self, val: u128) -> &mut Self {
477        if let Self::Eip1559 { max_fee_per_gas, .. } |
478        Self::Eip4844 { max_fee_per_gas, .. } |
479        Self::Eip7702 { max_fee_per_gas, .. } = self
480        {
481            *max_fee_per_gas = val;
482        }
483        self
484    }
485
486    /// Sets the max fee for dynamic fee transactions (EIP-1559 and EIP-4844)
487    pub const fn with_max_fee(mut self, val: u128) -> Self {
488        self.set_max_fee(val);
489        self
490    }
491
492    /// Gets the max fee for dynamic fee transactions (EIP-1559 and EIP-4844)
493    pub const fn get_max_fee(&self) -> Option<u128> {
494        match self {
495            Self::Eip1559 { max_fee_per_gas, .. } |
496            Self::Eip4844 { max_fee_per_gas, .. } |
497            Self::Eip7702 { max_fee_per_gas, .. } => Some(*max_fee_per_gas),
498            _ => None,
499        }
500    }
501
502    /// Sets the access list for transactions supporting EIP-1559, EIP-4844, and EIP-2930.
503    pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self {
504        match self {
505            Self::Legacy { .. } => {}
506            Self::Eip1559 { access_list: accesslist, .. } |
507            Self::Eip4844 { access_list: accesslist, .. } |
508            Self::Eip2930 { access_list: accesslist, .. } |
509            Self::Eip7702 { access_list: accesslist, .. } => {
510                *accesslist = list;
511            }
512        }
513        self
514    }
515
516    /// Sets the authorization list for EIP-7702 transactions.
517    pub fn set_authorization_list(&mut self, list: Vec<SignedAuthorization>) -> &mut Self {
518        if let Self::Eip7702 { authorization_list, .. } = self {
519            *authorization_list = list;
520        }
521
522        self
523    }
524
525    /// Sets the gas price for the transaction.
526    pub const fn set_gas_price(&mut self, val: u128) -> &mut Self {
527        match self {
528            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
529                *gas_price = val;
530            }
531            Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
532            Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
533            Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
534                *max_fee_per_gas = val;
535                *max_priority_fee_per_gas = val;
536            }
537        }
538        self
539    }
540
541    /// Sets the gas price for the transaction.
542    pub const fn with_gas_price(mut self, val: u128) -> Self {
543        match self {
544            Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
545                *gas_price = val;
546            }
547            Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
548            Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
549            Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
550                *max_fee_per_gas = val;
551                *max_priority_fee_per_gas = val;
552            }
553        }
554        self
555    }
556
557    /// Gets the gas price for the transaction.
558    pub const fn get_gas_price(&self) -> u128 {
559        match self {
560            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
561            Self::Eip1559 { max_fee_per_gas, .. } |
562            Self::Eip4844 { max_fee_per_gas, .. } |
563            Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
564        }
565    }
566
567    /// Returns a clone with a decreased nonce
568    pub fn prev(&self) -> Self {
569        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
570    }
571
572    /// Returns a clone with an increased nonce
573    pub fn next(&self) -> Self {
574        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
575    }
576
577    /// Returns a clone with an increased nonce
578    pub fn skip(&self, skip: u64) -> Self {
579        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
580    }
581
582    /// Returns a clone with incremented nonce
583    pub fn inc_nonce(self) -> Self {
584        let nonce = self.get_nonce() + 1;
585        self.with_nonce(nonce)
586    }
587
588    /// Sets a new random hash
589    pub fn rng_hash(self) -> Self {
590        self.with_hash(B256::random())
591    }
592
593    /// Returns a new transaction with a higher gas price +1
594    pub fn inc_price(&self) -> Self {
595        self.inc_price_by(1)
596    }
597
598    /// Returns a new transaction with a higher gas price
599    pub fn inc_price_by(&self, value: u128) -> Self {
600        self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
601    }
602
603    /// Returns a new transaction with a lower gas price -1
604    pub fn decr_price(&self) -> Self {
605        self.decr_price_by(1)
606    }
607
608    /// Returns a new transaction with a lower gas price
609    pub fn decr_price_by(&self, value: u128) -> Self {
610        self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
611    }
612
613    /// Returns a new transaction with a higher value
614    pub fn inc_value(&self) -> Self {
615        self.clone().with_value(self.get_value().checked_add(U256::ONE).unwrap())
616    }
617
618    /// Returns a new transaction with a higher gas limit
619    pub fn inc_limit(&self) -> Self {
620        self.clone().with_gas_limit(self.get_gas_limit() + 1)
621    }
622
623    /// Returns a new transaction with a higher blob fee +1
624    ///
625    /// If it's an EIP-4844 transaction.
626    pub fn inc_blob_fee(&self) -> Self {
627        self.inc_blob_fee_by(1)
628    }
629
630    /// Returns a new transaction with a higher blob fee
631    ///
632    /// If it's an EIP-4844 transaction.
633    pub fn inc_blob_fee_by(&self, value: u128) -> Self {
634        let mut this = self.clone();
635        if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
636            *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
637        }
638        this
639    }
640
641    /// Returns a new transaction with a lower blob fee -1
642    ///
643    /// If it's an EIP-4844 transaction.
644    pub fn decr_blob_fee(&self) -> Self {
645        self.decr_price_by(1)
646    }
647
648    /// Returns a new transaction with a lower blob fee
649    ///
650    /// If it's an EIP-4844 transaction.
651    pub fn decr_blob_fee_by(&self, value: u128) -> Self {
652        let mut this = self.clone();
653        if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
654            *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
655        }
656        this
657    }
658
659    /// Returns the transaction type identifier associated with the current [`MockTransaction`].
660    pub const fn tx_type(&self) -> u8 {
661        match self {
662            Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
663            Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
664            Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
665            Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
666            Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
667        }
668    }
669
670    /// Checks if the transaction is of the legacy type.
671    pub const fn is_legacy(&self) -> bool {
672        matches!(self, Self::Legacy { .. })
673    }
674
675    /// Checks if the transaction is of the EIP-1559 type.
676    pub const fn is_eip1559(&self) -> bool {
677        matches!(self, Self::Eip1559 { .. })
678    }
679
680    /// Checks if the transaction is of the EIP-4844 type.
681    pub const fn is_eip4844(&self) -> bool {
682        matches!(self, Self::Eip4844 { .. })
683    }
684
685    /// Checks if the transaction is of the EIP-2930 type.
686    pub const fn is_eip2930(&self) -> bool {
687        matches!(self, Self::Eip2930 { .. })
688    }
689
690    /// Checks if the transaction is of the EIP-7702 type.
691    pub const fn is_eip7702(&self) -> bool {
692        matches!(self, Self::Eip7702 { .. })
693    }
694
695    fn update_cost(&mut self) {
696        match self {
697            Self::Legacy { cost, gas_limit, gas_price, value, .. } |
698            Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
699                *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
700            }
701            Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
702            Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
703            Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
704                *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
705            }
706        };
707    }
708}
709
710impl PoolTransaction for MockTransaction {
711    type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
712
713    type Consensus = TransactionSigned;
714
715    type Pooled = PooledTransactionVariant;
716
717    fn encoded_2718_consensus(&self) -> Bytes {
718        self.clone_into_consensus().encoded_2718().into()
719    }
720
721    fn consensus_ref(&self) -> Recovered<&Self::Consensus> {
722        unimplemented!("mock transaction does not wrap a consensus transaction")
723    }
724
725    fn into_consensus(self) -> Recovered<Self::Consensus> {
726        self.into()
727    }
728
729    fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
730        pooled.into()
731    }
732
733    fn recover_raw_transaction(data: &[u8]) -> Result<Self, RawPoolTransactionError> {
734        let mut transaction = Self::try_recover(Self::decode_raw_transaction(data)?)
735            .map_err(|_| RawPoolTransactionError::InvalidTransactionSignature)?;
736        transaction.set_size(data.len());
737        Ok(transaction)
738    }
739
740    fn recover_raw_transaction_with_cache(
741        data: &[u8],
742        cache: &reth_evm::SenderRecoveryCache,
743    ) -> Result<Self, RawPoolTransactionError> {
744        let mut transaction =
745            Self::try_recover_with_cache(Self::decode_raw_transaction(data)?, cache)
746                .map_err(|_| RawPoolTransactionError::InvalidTransactionSignature)?;
747        transaction.set_size(data.len());
748        Ok(transaction)
749    }
750
751    fn hash(&self) -> &TxHash {
752        self.get_hash()
753    }
754
755    fn sender(&self) -> Address {
756        *self.get_sender()
757    }
758
759    fn sender_ref(&self) -> &Address {
760        self.get_sender()
761    }
762
763    // Having `get_cost` from `make_setters_getters` would be cleaner but we didn't
764    // want to also generate the error-prone cost setters. For now cost should be
765    // correct at construction and auto-updated per field update via `update_cost`,
766    // not to be manually set.
767    fn cost(&self) -> &U256 {
768        match self {
769            Self::Legacy { cost, .. } |
770            Self::Eip2930 { cost, .. } |
771            Self::Eip1559 { cost, .. } |
772            Self::Eip4844 { cost, .. } |
773            Self::Eip7702 { cost, .. } => cost,
774        }
775    }
776
777    /// Returns the encoded length of the transaction.
778    fn encoded_length(&self) -> usize {
779        self.size()
780    }
781}
782
783impl InMemorySize for MockTransaction {
784    fn size(&self) -> usize {
785        *self.get_size()
786    }
787}
788
789impl Typed2718 for MockTransaction {
790    fn ty(&self) -> u8 {
791        match self {
792            Self::Legacy { .. } => TxType::Legacy.into(),
793            Self::Eip1559 { .. } => TxType::Eip1559.into(),
794            Self::Eip4844 { .. } => TxType::Eip4844.into(),
795            Self::Eip2930 { .. } => TxType::Eip2930.into(),
796            Self::Eip7702 { .. } => TxType::Eip7702.into(),
797        }
798    }
799}
800
801impl alloy_consensus::Transaction for MockTransaction {
802    fn chain_id(&self) -> Option<u64> {
803        match self {
804            Self::Legacy { chain_id, .. } => *chain_id,
805            Self::Eip1559 { chain_id, .. } |
806            Self::Eip4844 { chain_id, .. } |
807            Self::Eip2930 { chain_id, .. } |
808            Self::Eip7702 { chain_id, .. } => Some(*chain_id),
809        }
810    }
811
812    fn nonce(&self) -> u64 {
813        *self.get_nonce()
814    }
815
816    fn gas_limit(&self) -> u64 {
817        *self.get_gas_limit()
818    }
819
820    fn gas_price(&self) -> Option<u128> {
821        match self {
822            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
823            _ => None,
824        }
825    }
826
827    fn max_fee_per_gas(&self) -> u128 {
828        match self {
829            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
830            Self::Eip1559 { max_fee_per_gas, .. } |
831            Self::Eip4844 { max_fee_per_gas, .. } |
832            Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
833        }
834    }
835
836    fn max_priority_fee_per_gas(&self) -> Option<u128> {
837        match self {
838            Self::Legacy { .. } | Self::Eip2930 { .. } => None,
839            Self::Eip1559 { max_priority_fee_per_gas, .. } |
840            Self::Eip4844 { max_priority_fee_per_gas, .. } |
841            Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
842        }
843    }
844
845    fn max_fee_per_blob_gas(&self) -> Option<u128> {
846        match self {
847            Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
848            _ => None,
849        }
850    }
851
852    fn priority_fee_or_price(&self) -> u128 {
853        match self {
854            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
855            Self::Eip1559 { max_priority_fee_per_gas, .. } |
856            Self::Eip4844 { max_priority_fee_per_gas, .. } |
857            Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
858        }
859    }
860
861    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
862        match self.max_priority_fee_per_gas() {
863            Some(max_tip) => calc_effective_gas_price(self.max_fee_per_gas(), max_tip, base_fee),
864            None => self.max_fee_per_gas(),
865        }
866    }
867
868    fn is_dynamic_fee(&self) -> bool {
869        !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
870    }
871
872    fn kind(&self) -> TxKind {
873        match self {
874            Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
875            Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
876        }
877    }
878
879    fn is_create(&self) -> bool {
880        match self {
881            Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
882                to.is_create()
883            }
884            Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
885        }
886    }
887
888    fn value(&self) -> U256 {
889        match self {
890            Self::Legacy { value, .. } |
891            Self::Eip1559 { value, .. } |
892            Self::Eip2930 { value, .. } |
893            Self::Eip4844 { value, .. } |
894            Self::Eip7702 { value, .. } => *value,
895        }
896    }
897
898    fn input(&self) -> &Bytes {
899        self.get_input()
900    }
901
902    fn access_list(&self) -> Option<&AccessList> {
903        match self {
904            Self::Legacy { .. } => None,
905            Self::Eip1559 { access_list: accesslist, .. } |
906            Self::Eip4844 { access_list: accesslist, .. } |
907            Self::Eip2930 { access_list: accesslist, .. } |
908            Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
909        }
910    }
911
912    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
913        match self {
914            Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
915            _ => None,
916        }
917    }
918
919    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
920        match self {
921            Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
922            _ => None,
923        }
924    }
925}
926
927impl EthPoolTransaction for MockTransaction {
928    fn take_blob(&mut self) -> EthBlobTransactionSidecar {
929        match self {
930            Self::Eip4844 { sidecar, .. } => {
931                EthBlobTransactionSidecar::Present(sidecar.clone().into())
932            }
933            _ => EthBlobTransactionSidecar::None,
934        }
935    }
936
937    fn try_into_pooled_eip4844(
938        self,
939        sidecar: Arc<BlobTransactionSidecarVariant>,
940    ) -> Option<Recovered<Self::Pooled>> {
941        let (tx, signer) = self.into_consensus().into_parts();
942        tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
943            .map(|tx| tx.with_signer(signer))
944            .ok()
945    }
946
947    fn try_from_eip4844(
948        tx: Recovered<Self::Consensus>,
949        sidecar: BlobTransactionSidecarVariant,
950    ) -> Option<Self> {
951        let (tx, signer) = tx.into_parts();
952        tx.try_into_pooled_eip4844(sidecar)
953            .map(|tx| tx.with_signer(signer))
954            .ok()
955            .map(Self::from_pooled)
956    }
957
958    fn validate_blob(
959        &self,
960        _blob: &BlobTransactionSidecarVariant,
961        _settings: &KzgSettings,
962    ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
963        match &self {
964            Self::Eip4844 { .. } => Ok(()),
965            _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
966        }
967    }
968}
969
970impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
971    type Error = TryFromRecoveredTransactionError;
972
973    fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
974        let (transaction, sender) = tx.into_parts();
975        let hash = *transaction.tx_hash();
976        let size = transaction.size();
977
978        match transaction.into_typed_transaction() {
979            Transaction::Legacy(TxLegacy {
980                chain_id,
981                nonce,
982                gas_price,
983                gas_limit,
984                to,
985                value,
986                input,
987            }) => Ok(Self::Legacy {
988                chain_id,
989                hash,
990                sender,
991                nonce,
992                gas_price,
993                gas_limit,
994                to,
995                value,
996                input,
997                size,
998                cost: U256::from(gas_limit) * U256::from(gas_price) + value,
999            }),
1000            Transaction::Eip2930(TxEip2930 {
1001                chain_id,
1002                nonce,
1003                gas_price,
1004                gas_limit,
1005                to,
1006                value,
1007                input,
1008                access_list,
1009            }) => Ok(Self::Eip2930 {
1010                chain_id,
1011                hash,
1012                sender,
1013                nonce,
1014                gas_price,
1015                gas_limit,
1016                to,
1017                value,
1018                input,
1019                access_list,
1020                size,
1021                cost: U256::from(gas_limit) * U256::from(gas_price) + value,
1022            }),
1023            Transaction::Eip1559(TxEip1559 {
1024                chain_id,
1025                nonce,
1026                gas_limit,
1027                max_fee_per_gas,
1028                max_priority_fee_per_gas,
1029                to,
1030                value,
1031                input,
1032                access_list,
1033            }) => Ok(Self::Eip1559 {
1034                chain_id,
1035                hash,
1036                sender,
1037                nonce,
1038                max_fee_per_gas,
1039                max_priority_fee_per_gas,
1040                gas_limit,
1041                to,
1042                value,
1043                input,
1044                access_list,
1045                size,
1046                cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1047            }),
1048            Transaction::Eip4844(TxEip4844 {
1049                chain_id,
1050                nonce,
1051                gas_limit,
1052                max_fee_per_gas,
1053                max_priority_fee_per_gas,
1054                to,
1055                value,
1056                input,
1057                access_list,
1058                blob_versioned_hashes: _,
1059                max_fee_per_blob_gas,
1060            }) => Ok(Self::Eip4844 {
1061                chain_id,
1062                hash,
1063                sender,
1064                nonce,
1065                max_fee_per_gas,
1066                max_priority_fee_per_gas,
1067                max_fee_per_blob_gas,
1068                gas_limit,
1069                to,
1070                value,
1071                input,
1072                access_list,
1073                sidecar: BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
1074                blob_versioned_hashes: Default::default(),
1075                size,
1076                cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1077            }),
1078            Transaction::Eip7702(TxEip7702 {
1079                chain_id,
1080                nonce,
1081                gas_limit,
1082                max_fee_per_gas,
1083                max_priority_fee_per_gas,
1084                to,
1085                value,
1086                access_list,
1087                authorization_list,
1088                input,
1089            }) => Ok(Self::Eip7702 {
1090                chain_id,
1091                hash,
1092                sender,
1093                nonce,
1094                max_fee_per_gas,
1095                max_priority_fee_per_gas,
1096                gas_limit,
1097                to,
1098                value,
1099                input,
1100                access_list,
1101                authorization_list,
1102                size,
1103                cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1104            }),
1105        }
1106    }
1107}
1108
1109impl TryFrom<Recovered<TxEnvelope>> for MockTransaction {
1110    type Error = TryFromRecoveredTransactionError;
1111
1112    fn try_from(tx: Recovered<TxEnvelope>) -> Result<Self, Self::Error> {
1113        let (transaction, sender) = tx.into_parts();
1114        let hash = *transaction.tx_hash();
1115        let size = transaction.size();
1116
1117        match transaction {
1118            EthereumTxEnvelope::Legacy(signed_tx) => {
1119                let tx = signed_tx.strip_signature();
1120                Ok(Self::Legacy {
1121                    chain_id: tx.chain_id,
1122                    hash,
1123                    sender,
1124                    nonce: tx.nonce,
1125                    gas_price: tx.gas_price,
1126                    gas_limit: tx.gas_limit,
1127                    to: tx.to,
1128                    value: tx.value,
1129                    input: tx.input,
1130                    size,
1131                    cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1132                })
1133            }
1134            EthereumTxEnvelope::Eip2930(signed_tx) => {
1135                let tx = signed_tx.strip_signature();
1136                Ok(Self::Eip2930 {
1137                    chain_id: tx.chain_id,
1138                    hash,
1139                    sender,
1140                    nonce: tx.nonce,
1141                    gas_price: tx.gas_price,
1142                    gas_limit: tx.gas_limit,
1143                    to: tx.to,
1144                    value: tx.value,
1145                    input: tx.input,
1146                    access_list: tx.access_list,
1147                    size,
1148                    cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1149                })
1150            }
1151            EthereumTxEnvelope::Eip1559(signed_tx) => {
1152                let tx = signed_tx.strip_signature();
1153                Ok(Self::Eip1559 {
1154                    chain_id: tx.chain_id,
1155                    hash,
1156                    sender,
1157                    nonce: tx.nonce,
1158                    max_fee_per_gas: tx.max_fee_per_gas,
1159                    max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1160                    gas_limit: tx.gas_limit,
1161                    to: tx.to,
1162                    value: tx.value,
1163                    input: tx.input,
1164                    access_list: tx.access_list,
1165                    size,
1166                    cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1167                })
1168            }
1169            EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1170                TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1171                    chain_id: tx.chain_id,
1172                    hash,
1173                    sender,
1174                    nonce: tx.nonce,
1175                    max_fee_per_gas: tx.max_fee_per_gas,
1176                    max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1177                    max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1178                    gas_limit: tx.gas_limit,
1179                    to: tx.to,
1180                    value: tx.value,
1181                    input: tx.input.clone(),
1182                    access_list: tx.access_list.clone(),
1183                    sidecar: BlobTransactionSidecarVariant::Eip4844(
1184                        BlobTransactionSidecar::default(),
1185                    ),
1186                    blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1187                    size,
1188                    cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1189                }),
1190                tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1191            },
1192            EthereumTxEnvelope::Eip7702(signed_tx) => {
1193                let tx = signed_tx.strip_signature();
1194                Ok(Self::Eip7702 {
1195                    chain_id: tx.chain_id,
1196                    hash,
1197                    sender,
1198                    nonce: tx.nonce,
1199                    max_fee_per_gas: tx.max_fee_per_gas,
1200                    max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1201                    gas_limit: tx.gas_limit,
1202                    to: tx.to,
1203                    value: tx.value,
1204                    access_list: tx.access_list,
1205                    authorization_list: tx.authorization_list,
1206                    input: tx.input,
1207                    size,
1208                    cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1209                })
1210            }
1211        }
1212    }
1213}
1214
1215impl From<Recovered<PooledTransactionVariant>> for MockTransaction {
1216    fn from(tx: Recovered<PooledTransactionVariant>) -> Self {
1217        tx.convert::<TransactionSigned>().try_into().expect(
1218            "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1219        )
1220    }
1221}
1222
1223impl From<MockTransaction> for Recovered<TransactionSigned> {
1224    fn from(tx: MockTransaction) -> Self {
1225        let hash = *tx.hash();
1226        let sender = tx.sender();
1227        let tx = Transaction::from(tx);
1228        let tx: TransactionSigned =
1229            Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1230        Self::new_unchecked(tx, sender)
1231    }
1232}
1233
1234impl From<MockTransaction> for Transaction {
1235    fn from(mock: MockTransaction) -> Self {
1236        match mock {
1237            MockTransaction::Legacy {
1238                chain_id,
1239                nonce,
1240                gas_price,
1241                gas_limit,
1242                to,
1243                value,
1244                input,
1245                ..
1246            } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1247            MockTransaction::Eip2930 {
1248                chain_id,
1249                nonce,
1250                gas_price,
1251                gas_limit,
1252                to,
1253                value,
1254                access_list,
1255                input,
1256                ..
1257            } => Self::Eip2930(TxEip2930 {
1258                chain_id,
1259                nonce,
1260                gas_price,
1261                gas_limit,
1262                to,
1263                value,
1264                access_list,
1265                input,
1266            }),
1267            MockTransaction::Eip1559 {
1268                chain_id,
1269                nonce,
1270                gas_limit,
1271                max_fee_per_gas,
1272                max_priority_fee_per_gas,
1273                to,
1274                value,
1275                access_list,
1276                input,
1277                ..
1278            } => Self::Eip1559(TxEip1559 {
1279                chain_id,
1280                nonce,
1281                gas_limit,
1282                max_fee_per_gas,
1283                max_priority_fee_per_gas,
1284                to,
1285                value,
1286                access_list,
1287                input,
1288            }),
1289            MockTransaction::Eip4844 {
1290                chain_id,
1291                nonce,
1292                gas_limit,
1293                max_fee_per_gas,
1294                max_priority_fee_per_gas,
1295                to,
1296                value,
1297                access_list,
1298                sidecar,
1299                max_fee_per_blob_gas,
1300                input,
1301                ..
1302            } => Self::Eip4844(TxEip4844 {
1303                chain_id,
1304                nonce,
1305                gas_limit,
1306                max_fee_per_gas,
1307                max_priority_fee_per_gas,
1308                to,
1309                value,
1310                access_list,
1311                blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1312                max_fee_per_blob_gas,
1313                input,
1314            }),
1315            MockTransaction::Eip7702 {
1316                chain_id,
1317                nonce,
1318                gas_limit,
1319                max_fee_per_gas,
1320                max_priority_fee_per_gas,
1321                to,
1322                value,
1323                access_list,
1324                input,
1325                authorization_list,
1326                ..
1327            } => Self::Eip7702(TxEip7702 {
1328                chain_id,
1329                nonce,
1330                gas_limit,
1331                max_fee_per_gas,
1332                max_priority_fee_per_gas,
1333                to,
1334                value,
1335                access_list,
1336                authorization_list,
1337                input,
1338            }),
1339        }
1340    }
1341}
1342
1343#[cfg(any(test, feature = "arbitrary"))]
1344impl proptest::arbitrary::Arbitrary for MockTransaction {
1345    type Parameters = ();
1346    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1347        use proptest::prelude::Strategy;
1348        use proptest_arbitrary_interop::arb;
1349
1350        arb::<(TransactionSigned, Address)>()
1351            .prop_map(|(signed_transaction, signer)| {
1352                Recovered::new_unchecked(signed_transaction, signer)
1353                    .try_into()
1354                    .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1355            })
1356            .boxed()
1357    }
1358
1359    type Strategy = proptest::strategy::BoxedStrategy<Self>;
1360}
1361
1362/// A factory for creating and managing various types of mock transactions.
1363#[derive(Debug, Default)]
1364pub struct MockTransactionFactory {
1365    pub(crate) ids: SenderIdentifiers,
1366}
1367
1368// === impl MockTransactionFactory ===
1369
1370impl MockTransactionFactory {
1371    /// Generates a transaction ID for the given [`MockTransaction`].
1372    pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1373        let sender = self.ids.sender_id_or_create(tx.sender());
1374        TransactionId::new(sender, *tx.get_nonce())
1375    }
1376
1377    /// Validates a [`MockTransaction`] and returns a [`MockValidTx`].
1378    pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1379        self.validated_with_origin(TransactionOrigin::External, transaction)
1380    }
1381
1382    /// Validates a [`MockTransaction`] and returns a shared [`Arc<MockValidTx>`].
1383    pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1384        Arc::new(self.validated(transaction))
1385    }
1386
1387    /// Converts the transaction into a validated transaction with a specified origin.
1388    pub fn validated_with_origin(
1389        &mut self,
1390        origin: TransactionOrigin,
1391        transaction: MockTransaction,
1392    ) -> MockValidTx {
1393        MockValidTx {
1394            propagate: false,
1395            transaction_id: self.tx_id(&transaction),
1396            transaction,
1397            timestamp: Instant::now(),
1398            origin,
1399            authority_ids: None,
1400        }
1401    }
1402
1403    /// Creates a validated legacy [`MockTransaction`].
1404    pub fn create_legacy(&mut self) -> MockValidTx {
1405        self.validated(MockTransaction::legacy())
1406    }
1407
1408    /// Creates a validated EIP-1559 [`MockTransaction`].
1409    pub fn create_eip1559(&mut self) -> MockValidTx {
1410        self.validated(MockTransaction::eip1559())
1411    }
1412
1413    /// Creates a validated EIP-4844 [`MockTransaction`].
1414    pub fn create_eip4844(&mut self) -> MockValidTx {
1415        self.validated(MockTransaction::eip4844())
1416    }
1417}
1418
1419/// `MockOrdering` is just a `CoinbaseTipOrdering` with `MockTransaction`
1420pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1421
1422/// A ratio of each of the configured transaction types. The percentages sum up to 100, this is
1423/// enforced in [`MockTransactionRatio::new`] by an assert.
1424#[derive(Debug, Clone)]
1425pub struct MockTransactionRatio {
1426    /// Percent of transactions that are legacy transactions
1427    pub legacy_pct: u32,
1428    /// Percent of transactions that are access list transactions
1429    pub access_list_pct: u32,
1430    /// Percent of transactions that are EIP-1559 transactions
1431    pub dynamic_fee_pct: u32,
1432    /// Percent of transactions that are EIP-4844 transactions
1433    pub blob_pct: u32,
1434}
1435
1436impl MockTransactionRatio {
1437    /// Creates a new [`MockTransactionRatio`] with the given percentages.
1438    ///
1439    /// Each argument is treated as a full percent, for example `30u32` is `30%`.
1440    ///
1441    /// The percentages must sum up to 100 exactly, or this method will panic.
1442    pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1443        let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1444        assert_eq!(
1445            total,
1446            100,
1447            "percentages must sum up to 100, instead got legacy: {legacy_pct}, access_list: {access_list_pct}, dynamic_fee: {dynamic_fee_pct}, blob: {blob_pct}, total: {total}",
1448        );
1449
1450        Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1451    }
1452
1453    /// Create a [`WeightedIndex`] from this transaction ratio.
1454    ///
1455    /// This index will sample in the following order:
1456    /// * Legacy transaction => 0
1457    /// * EIP-2930 transaction => 1
1458    /// * EIP-1559 transaction => 2
1459    /// * EIP-4844 transaction => 3
1460    pub fn weighted_index(&self) -> WeightedIndex<u32> {
1461        WeightedIndex::new([
1462            self.legacy_pct,
1463            self.access_list_pct,
1464            self.dynamic_fee_pct,
1465            self.blob_pct,
1466        ])
1467        .unwrap()
1468    }
1469}
1470
1471/// The range of each type of fee, for the different transaction types
1472#[derive(Debug, Clone)]
1473pub struct MockFeeRange {
1474    /// The range of `gas_price` or legacy and access list transactions
1475    pub gas_price: Uniform<u128>,
1476    /// The range of priority fees for EIP-1559 and EIP-4844 transactions
1477    pub priority_fee: Uniform<u128>,
1478    /// The range of max fees for EIP-1559 and EIP-4844 transactions
1479    pub max_fee: Uniform<u128>,
1480    /// The range of max fees per blob gas for EIP-4844 transactions
1481    pub max_fee_blob: Uniform<u128>,
1482}
1483
1484impl MockFeeRange {
1485    /// Creates a new [`MockFeeRange`] with the given ranges.
1486    ///
1487    /// Expects the bottom of the `priority_fee_range` to be greater than the top of the
1488    /// `max_fee_range`.
1489    pub fn new(
1490        gas_price: Range<u128>,
1491        priority_fee: Range<u128>,
1492        max_fee: Range<u128>,
1493        max_fee_blob: Range<u128>,
1494    ) -> Self {
1495        assert!(
1496            max_fee.start >= priority_fee.end,
1497            "max_fee_range should be strictly above the priority fee range"
1498        );
1499        Self {
1500            gas_price: gas_price.try_into().unwrap(),
1501            priority_fee: priority_fee.try_into().unwrap(),
1502            max_fee: max_fee.try_into().unwrap(),
1503            max_fee_blob: max_fee_blob.try_into().unwrap(),
1504        }
1505    }
1506
1507    /// Returns a sample of `gas_price` for legacy and access list transactions with the given
1508    /// [Rng](rand::Rng).
1509    pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1510        self.gas_price.sample(rng)
1511    }
1512
1513    /// Returns a sample of `max_priority_fee_per_gas` for EIP-1559 and EIP-4844 transactions with
1514    /// the given [Rng](rand::Rng).
1515    pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1516        self.priority_fee.sample(rng)
1517    }
1518
1519    /// Returns a sample of `max_fee_per_gas` for EIP-1559 and EIP-4844 transactions with the given
1520    /// [Rng](rand::Rng).
1521    pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1522        self.max_fee.sample(rng)
1523    }
1524
1525    /// Returns a sample of `max_fee_per_blob_gas` for EIP-4844 transactions with the given
1526    /// [Rng](rand::Rng).
1527    pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1528        self.max_fee_blob.sample(rng)
1529    }
1530}
1531
1532/// A configured distribution that can generate transactions
1533#[derive(Debug, Clone)]
1534pub struct MockTransactionDistribution {
1535    /// ratio of each transaction type to generate
1536    transaction_ratio: MockTransactionRatio,
1537    /// generates the gas limit
1538    gas_limit_range: Uniform<u64>,
1539    /// generates the transaction's fake size
1540    size_range: Uniform<usize>,
1541    /// generates fees for the given transaction types
1542    fee_ranges: MockFeeRange,
1543}
1544
1545impl MockTransactionDistribution {
1546    /// Creates a new generator distribution.
1547    pub fn new(
1548        transaction_ratio: MockTransactionRatio,
1549        fee_ranges: MockFeeRange,
1550        gas_limit_range: Range<u64>,
1551        size_range: Range<usize>,
1552    ) -> Self {
1553        Self {
1554            transaction_ratio,
1555            gas_limit_range: gas_limit_range.try_into().unwrap(),
1556            fee_ranges,
1557            size_range: size_range.try_into().unwrap(),
1558        }
1559    }
1560
1561    /// Generates a new transaction
1562    pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1563        let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1564        let tx = match transaction_sample {
1565            0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1566            1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1567            2 => MockTransaction::eip1559()
1568                .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1569                .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1570            3 => MockTransaction::eip4844()
1571                .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1572                .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1573                .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1574            _ => unreachable!("unknown transaction type returned by the weighted index"),
1575        };
1576
1577        let size = self.size_range.sample(rng);
1578
1579        tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1580    }
1581
1582    /// Generates a new transaction set for the given sender.
1583    ///
1584    /// The nonce range defines which nonces to set, and how many transactions to generate.
1585    pub fn tx_set(
1586        &self,
1587        sender: Address,
1588        nonce_range: Range<u64>,
1589        rng: &mut impl rand::Rng,
1590    ) -> MockTransactionSet {
1591        let txs =
1592            nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1593        MockTransactionSet::new(txs)
1594    }
1595
1596    /// Generates a transaction set that ensures that blob txs are not mixed with other transaction
1597    /// types.
1598    ///
1599    /// This is done by taking the existing distribution, and using the first transaction to
1600    /// determine whether or not the sender should generate entirely blob transactions.
1601    pub fn tx_set_non_conflicting_types(
1602        &self,
1603        sender: Address,
1604        nonce_range: Range<u64>,
1605        rng: &mut impl rand::Rng,
1606    ) -> NonConflictingSetOutcome {
1607        // This will create a modified distribution that will only generate blob transactions
1608        // for the given sender, if the blob transaction is the first transaction in the set.
1609        //
1610        // Otherwise, it will modify the transaction distribution to only generate legacy, eip2930,
1611        // and eip1559 transactions.
1612        //
1613        // The new distribution should still have the same relative amount of transaction types.
1614        let mut modified_distribution = self.clone();
1615        let first_tx = self.tx(nonce_range.start, rng);
1616
1617        // now we can check and modify the distribution, preserving potentially uneven ratios
1618        // between transaction types
1619        if first_tx.is_eip4844() {
1620            modified_distribution.transaction_ratio = MockTransactionRatio {
1621                legacy_pct: 0,
1622                access_list_pct: 0,
1623                dynamic_fee_pct: 0,
1624                blob_pct: 100,
1625            };
1626
1627            // finally generate the transaction set
1628            NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1629                sender,
1630                nonce_range,
1631                rng,
1632            ))
1633        } else {
1634            let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1635                modified_distribution.transaction_ratio;
1636
1637            // Calculate the total weight of non-blob transactions
1638            let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1639
1640            // Calculate new weights, preserving the ratio between non-blob transaction types
1641            let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1642                .into_iter()
1643                .map(|weight| weight * 100 / total_non_blob_weight)
1644                .collect();
1645
1646            let new_ratio = MockTransactionRatio {
1647                legacy_pct: new_weights[0],
1648                access_list_pct: new_weights[1],
1649                dynamic_fee_pct: new_weights[2],
1650                blob_pct: 0,
1651            };
1652
1653            // Set the new transaction ratio excluding blob transactions and preserving the relative
1654            // ratios
1655            modified_distribution.transaction_ratio = new_ratio;
1656
1657            // finally generate the transaction set
1658            NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1659        }
1660    }
1661}
1662
1663/// Indicates whether or not the non-conflicting transaction set generated includes only blobs, or
1664/// a mix of transaction types.
1665#[derive(Debug, Clone)]
1666pub enum NonConflictingSetOutcome {
1667    /// The transaction set includes only blob transactions
1668    BlobsOnly(MockTransactionSet),
1669    /// The transaction set includes a mix of transaction types
1670    Mixed(MockTransactionSet),
1671}
1672
1673impl NonConflictingSetOutcome {
1674    /// Returns the inner [`MockTransactionSet`]
1675    pub fn into_inner(self) -> MockTransactionSet {
1676        match self {
1677            Self::BlobsOnly(set) | Self::Mixed(set) => set,
1678        }
1679    }
1680
1681    /// Introduces artificial nonce gaps into the transaction set, at random, with a range of gap
1682    /// sizes.
1683    ///
1684    /// If this is a [`NonConflictingSetOutcome::BlobsOnly`], then nonce gaps will not be
1685    /// introduced. Otherwise, the nonce gaps will be introduced to the mixed transaction set.
1686    ///
1687    /// See [`MockTransactionSet::with_nonce_gaps`] for more information on the generation process.
1688    pub fn with_nonce_gaps(
1689        &mut self,
1690        gap_pct: u32,
1691        gap_range: Range<u64>,
1692        rng: &mut impl rand::Rng,
1693    ) {
1694        match self {
1695            Self::BlobsOnly(_) => {}
1696            Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1697        }
1698    }
1699}
1700
1701/// A set of [`MockTransaction`]s that can be modified at once
1702#[derive(Debug, Clone)]
1703pub struct MockTransactionSet {
1704    pub(crate) transactions: Vec<MockTransaction>,
1705}
1706
1707impl MockTransactionSet {
1708    /// Create a new [`MockTransactionSet`] from a list of transactions
1709    const fn new(transactions: Vec<MockTransaction>) -> Self {
1710        Self { transactions }
1711    }
1712
1713    /// Creates a series of dependent transactions for a given sender and nonce.
1714    ///
1715    /// This method generates a sequence of transactions starting from the provided nonce
1716    /// for the given sender.
1717    ///
1718    /// The number of transactions created is determined by `tx_count`.
1719    pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1720        let mut txs = Vec::with_capacity(tx_count);
1721        let mut curr_tx =
1722            MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1723        for _ in 0..tx_count {
1724            txs.push(curr_tx.clone());
1725            curr_tx = curr_tx.next();
1726        }
1727
1728        Self::new(txs)
1729    }
1730
1731    /// Creates a chain of transactions for a given sender with a specified count.
1732    ///
1733    /// This method generates a sequence of transactions starting from the specified sender
1734    /// and creates a chain of transactions based on the `tx_count`.
1735    pub fn sequential_transactions_by_sender(
1736        sender: Address,
1737        tx_count: usize,
1738        tx_type: TxType,
1739    ) -> Self {
1740        Self::dependent(sender, 0, tx_count, tx_type)
1741    }
1742
1743    /// Introduces artificial nonce gaps into the transaction set, at random, with a range of gap
1744    /// sizes.
1745    ///
1746    /// This assumes that the `gap_pct` is between 0 and 100, and the `gap_range` has a lower bound
1747    /// of at least one. This is enforced with assertions.
1748    ///
1749    /// The `gap_pct` is the percent chance that the next transaction in the set will introduce a
1750    /// nonce gap.
1751    ///
1752    /// Let an example transaction set be `[(tx1, 1), (tx2, 2)]`, where the first element of the
1753    /// tuple is a transaction, and the second element is the nonce. If the `gap_pct` is 50, and
1754    /// the `gap_range` is `1..=1`, then the resulting transaction set could be either
1755    /// `[(tx1, 1), (tx2, 2)]` or `[(tx1, 1), (tx2, 3)]`, with a 50% chance of either.
1756    pub fn with_nonce_gaps(
1757        &mut self,
1758        gap_pct: u32,
1759        gap_range: Range<u64>,
1760        rng: &mut impl rand::Rng,
1761    ) {
1762        assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1763        assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1764
1765        let mut prev_nonce = 0;
1766        for tx in &mut self.transactions {
1767            if rng.random_bool(gap_pct as f64 / 100.0) {
1768                prev_nonce += gap_range.start;
1769            } else {
1770                prev_nonce += 1;
1771            }
1772            tx.set_nonce(prev_nonce);
1773        }
1774    }
1775
1776    /// Add transactions to the [`MockTransactionSet`]
1777    pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1778        self.transactions.extend(txs);
1779    }
1780
1781    /// Extract the inner [Vec] of [`MockTransaction`]s
1782    pub fn into_vec(self) -> Vec<MockTransaction> {
1783        self.transactions
1784    }
1785
1786    /// Returns an iterator over the contained transactions in the set
1787    pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1788        self.transactions.iter()
1789    }
1790
1791    /// Returns a mutable iterator over the contained transactions in the set.
1792    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1793        self.transactions.iter_mut()
1794    }
1795}
1796
1797impl IntoIterator for MockTransactionSet {
1798    type Item = MockTransaction;
1799    type IntoIter = IntoIter<MockTransaction>;
1800
1801    fn into_iter(self) -> Self::IntoIter {
1802        self.transactions.into_iter()
1803    }
1804}
1805
1806#[test]
1807fn test_mock_priority() {
1808    use crate::TransactionOrdering;
1809
1810    let o = MockOrdering::default();
1811    let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1812    let hi = lo.next().inc_price();
1813    assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1814}
1815
1816#[cfg(test)]
1817mod tests {
1818    use super::*;
1819    use alloy_consensus::Transaction;
1820    use alloy_primitives::U256;
1821
1822    #[test]
1823    fn test_mock_transaction_factory() {
1824        let mut factory = MockTransactionFactory::default();
1825
1826        // Test legacy transaction creation
1827        let legacy = factory.create_legacy();
1828        assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1829
1830        // Test EIP1559 transaction creation
1831        let eip1559 = factory.create_eip1559();
1832        assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1833
1834        // Test EIP4844 transaction creation
1835        let eip4844 = factory.create_eip4844();
1836        assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1837    }
1838
1839    #[test]
1840    fn test_mock_transaction_set() {
1841        let sender = Address::random();
1842        let nonce_start = 0u64;
1843        let count = 3;
1844
1845        // Test legacy transaction set
1846        let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1847        assert_eq!(legacy_set.transactions.len(), count);
1848        for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1849            assert_eq!(tx.tx_type(), TxType::Legacy);
1850            assert_eq!(tx.nonce(), nonce_start + idx as u64);
1851            assert_eq!(tx.sender(), sender);
1852        }
1853
1854        // Test EIP1559 transaction set
1855        let eip1559_set =
1856            MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1857        assert_eq!(eip1559_set.transactions.len(), count);
1858        for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1859            assert_eq!(tx.tx_type(), TxType::Eip1559);
1860            assert_eq!(tx.nonce(), nonce_start + idx as u64);
1861            assert_eq!(tx.sender(), sender);
1862        }
1863    }
1864
1865    #[test]
1866    fn test_mock_transaction_modifications() {
1867        let tx = MockTransaction::eip1559();
1868
1869        // Test price increment
1870        let original_price = tx.get_gas_price();
1871        let tx_inc = tx.inc_price();
1872        assert!(tx_inc.get_gas_price() > original_price);
1873
1874        // Test gas limit increment
1875        let original_limit = tx.gas_limit();
1876        let tx_inc = tx.inc_limit();
1877        assert!(tx_inc.gas_limit() > original_limit);
1878
1879        // Test nonce increment
1880        let original_nonce = tx.nonce();
1881        let tx_inc = tx.inc_nonce();
1882        assert_eq!(tx_inc.nonce(), original_nonce + 1);
1883    }
1884
1885    #[test]
1886    fn test_mock_transaction_cost() {
1887        let tx = MockTransaction::eip1559()
1888            .with_gas_limit(7_000)
1889            .with_max_fee(100)
1890            .with_value(U256::ZERO);
1891
1892        // Cost is calculated as (gas_limit * max_fee_per_gas) + value
1893        let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1894        assert_eq!(*tx.cost(), expected_cost);
1895    }
1896}