reth_transaction_pool/test_utils/
mock.rs

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