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    transaction::PooledTransaction,
16    EthereumTxEnvelope, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702,
17    TxEnvelope, TxLegacy, TxType, Typed2718,
18};
19use alloy_eips::{
20    eip1559::MIN_PROTOCOL_BASE_FEE,
21    eip2930::AccessList,
22    eip4844::{BlobTransactionSidecar, BlobTransactionValidationError, DATA_GAS_PER_BLOB},
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::{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: BlobTransactionSidecar,
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: 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: BlobTransactionSidecar) -> 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 gas price for the transaction.
486    pub const fn set_gas_price(&mut self, val: u128) -> &mut Self {
487        match self {
488            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
489                *gas_price = val;
490            }
491            Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
492            Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
493            Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
494                *max_fee_per_gas = val;
495                *max_priority_fee_per_gas = val;
496            }
497        }
498        self
499    }
500
501    /// Sets the gas price for the transaction.
502    pub const fn with_gas_price(mut self, val: u128) -> Self {
503        match self {
504            Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
505                *gas_price = val;
506            }
507            Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
508            Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
509            Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
510                *max_fee_per_gas = val;
511                *max_priority_fee_per_gas = val;
512            }
513        }
514        self
515    }
516
517    /// Gets the gas price for the transaction.
518    pub const fn get_gas_price(&self) -> u128 {
519        match self {
520            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
521            Self::Eip1559 { max_fee_per_gas, .. } |
522            Self::Eip4844 { max_fee_per_gas, .. } |
523            Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
524        }
525    }
526
527    /// Returns a clone with a decreased nonce
528    pub fn prev(&self) -> Self {
529        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
530    }
531
532    /// Returns a clone with an increased nonce
533    pub fn next(&self) -> Self {
534        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
535    }
536
537    /// Returns a clone with an increased nonce
538    pub fn skip(&self, skip: u64) -> Self {
539        self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
540    }
541
542    /// Returns a clone with incremented nonce
543    pub fn inc_nonce(self) -> Self {
544        let nonce = self.get_nonce() + 1;
545        self.with_nonce(nonce)
546    }
547
548    /// Sets a new random hash
549    pub fn rng_hash(self) -> Self {
550        self.with_hash(B256::random())
551    }
552
553    /// Returns a new transaction with a higher gas price +1
554    pub fn inc_price(&self) -> Self {
555        self.inc_price_by(1)
556    }
557
558    /// Returns a new transaction with a higher gas price
559    pub fn inc_price_by(&self, value: u128) -> Self {
560        self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
561    }
562
563    /// Returns a new transaction with a lower gas price -1
564    pub fn decr_price(&self) -> Self {
565        self.decr_price_by(1)
566    }
567
568    /// Returns a new transaction with a lower gas price
569    pub fn decr_price_by(&self, value: u128) -> Self {
570        self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
571    }
572
573    /// Returns a new transaction with a higher value
574    pub fn inc_value(&self) -> Self {
575        self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
576    }
577
578    /// Returns a new transaction with a higher gas limit
579    pub fn inc_limit(&self) -> Self {
580        self.clone().with_gas_limit(self.get_gas_limit() + 1)
581    }
582
583    /// Returns a new transaction with a higher blob fee +1
584    ///
585    /// If it's an EIP-4844 transaction.
586    pub fn inc_blob_fee(&self) -> Self {
587        self.inc_blob_fee_by(1)
588    }
589
590    /// Returns a new transaction with a higher blob fee
591    ///
592    /// If it's an EIP-4844 transaction.
593    pub fn inc_blob_fee_by(&self, value: u128) -> Self {
594        let mut this = self.clone();
595        if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
596            *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
597        }
598        this
599    }
600
601    /// Returns a new transaction with a lower blob fee -1
602    ///
603    /// If it's an EIP-4844 transaction.
604    pub fn decr_blob_fee(&self) -> Self {
605        self.decr_price_by(1)
606    }
607
608    /// Returns a new transaction with a lower blob fee
609    ///
610    /// If it's an EIP-4844 transaction.
611    pub fn decr_blob_fee_by(&self, value: u128) -> Self {
612        let mut this = self.clone();
613        if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
614            *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
615        }
616        this
617    }
618
619    /// Returns the transaction type identifier associated with the current [`MockTransaction`].
620    pub const fn tx_type(&self) -> u8 {
621        match self {
622            Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
623            Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
624            Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
625            Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
626            Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
627        }
628    }
629
630    /// Checks if the transaction is of the legacy type.
631    pub const fn is_legacy(&self) -> bool {
632        matches!(self, Self::Legacy { .. })
633    }
634
635    /// Checks if the transaction is of the EIP-1559 type.
636    pub const fn is_eip1559(&self) -> bool {
637        matches!(self, Self::Eip1559 { .. })
638    }
639
640    /// Checks if the transaction is of the EIP-4844 type.
641    pub const fn is_eip4844(&self) -> bool {
642        matches!(self, Self::Eip4844 { .. })
643    }
644
645    /// Checks if the transaction is of the EIP-2930 type.
646    pub const fn is_eip2930(&self) -> bool {
647        matches!(self, Self::Eip2930 { .. })
648    }
649
650    /// Checks if the transaction is of the EIP-2930 type.
651    pub const fn is_eip7702(&self) -> bool {
652        matches!(self, Self::Eip7702 { .. })
653    }
654
655    fn update_cost(&mut self) {
656        match self {
657            Self::Legacy { cost, gas_limit, gas_price, value, .. } |
658            Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
659                *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
660            }
661            Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
662            Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
663            Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
664                *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
665            }
666        };
667    }
668}
669
670impl PoolTransaction for MockTransaction {
671    type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
672
673    type Consensus = TransactionSigned;
674
675    type Pooled = PooledTransaction;
676
677    fn into_consensus(self) -> Recovered<Self::Consensus> {
678        self.into()
679    }
680
681    fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
682        pooled.into()
683    }
684
685    fn hash(&self) -> &TxHash {
686        self.get_hash()
687    }
688
689    fn sender(&self) -> Address {
690        *self.get_sender()
691    }
692
693    fn sender_ref(&self) -> &Address {
694        self.get_sender()
695    }
696
697    // Having `get_cost` from `make_setters_getters` would be cleaner but we didn't
698    // want to also generate the error-prone cost setters. For now cost should be
699    // correct at construction and auto-updated per field update via `update_cost`,
700    // not to be manually set.
701    fn cost(&self) -> &U256 {
702        match self {
703            Self::Legacy { cost, .. } |
704            Self::Eip2930 { cost, .. } |
705            Self::Eip1559 { cost, .. } |
706            Self::Eip4844 { cost, .. } |
707            Self::Eip7702 { cost, .. } => cost,
708        }
709    }
710
711    /// Returns the encoded length of the transaction.
712    fn encoded_length(&self) -> usize {
713        self.size()
714    }
715}
716
717impl InMemorySize for MockTransaction {
718    fn size(&self) -> usize {
719        *self.get_size()
720    }
721}
722
723impl Typed2718 for MockTransaction {
724    fn ty(&self) -> u8 {
725        match self {
726            Self::Legacy { .. } => TxType::Legacy.into(),
727            Self::Eip1559 { .. } => TxType::Eip1559.into(),
728            Self::Eip4844 { .. } => TxType::Eip4844.into(),
729            Self::Eip2930 { .. } => TxType::Eip2930.into(),
730            Self::Eip7702 { .. } => TxType::Eip7702.into(),
731        }
732    }
733}
734
735impl alloy_consensus::Transaction for MockTransaction {
736    fn chain_id(&self) -> Option<u64> {
737        match self {
738            Self::Legacy { chain_id, .. } => *chain_id,
739            Self::Eip1559 { chain_id, .. } |
740            Self::Eip4844 { chain_id, .. } |
741            Self::Eip2930 { chain_id, .. } |
742            Self::Eip7702 { chain_id, .. } => Some(*chain_id),
743        }
744    }
745
746    fn nonce(&self) -> u64 {
747        *self.get_nonce()
748    }
749
750    fn gas_limit(&self) -> u64 {
751        *self.get_gas_limit()
752    }
753
754    fn gas_price(&self) -> Option<u128> {
755        match self {
756            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
757            _ => None,
758        }
759    }
760
761    fn max_fee_per_gas(&self) -> u128 {
762        match self {
763            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
764            Self::Eip1559 { max_fee_per_gas, .. } |
765            Self::Eip4844 { max_fee_per_gas, .. } |
766            Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
767        }
768    }
769
770    fn max_priority_fee_per_gas(&self) -> Option<u128> {
771        match self {
772            Self::Legacy { .. } | Self::Eip2930 { .. } => None,
773            Self::Eip1559 { max_priority_fee_per_gas, .. } |
774            Self::Eip4844 { max_priority_fee_per_gas, .. } |
775            Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
776        }
777    }
778
779    fn max_fee_per_blob_gas(&self) -> Option<u128> {
780        match self {
781            Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
782            _ => None,
783        }
784    }
785
786    fn priority_fee_or_price(&self) -> u128 {
787        match self {
788            Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
789            Self::Eip1559 { max_priority_fee_per_gas, .. } |
790            Self::Eip4844 { max_priority_fee_per_gas, .. } |
791            Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
792        }
793    }
794
795    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
796        base_fee.map_or(self.max_fee_per_gas(), |base_fee| {
797            // if the tip is greater than the max priority fee per gas, set it to the max
798            // priority fee per gas + base fee
799            let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128);
800            if let Some(max_tip) = self.max_priority_fee_per_gas() {
801                if tip > max_tip {
802                    max_tip + base_fee as u128
803                } else {
804                    // otherwise return the max fee per gas
805                    self.max_fee_per_gas()
806                }
807            } else {
808                self.max_fee_per_gas()
809            }
810        })
811    }
812
813    fn is_dynamic_fee(&self) -> bool {
814        !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
815    }
816
817    fn kind(&self) -> TxKind {
818        match self {
819            Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
820            Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
821        }
822    }
823
824    fn is_create(&self) -> bool {
825        match self {
826            Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
827                to.is_create()
828            }
829            Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
830        }
831    }
832
833    fn value(&self) -> U256 {
834        match self {
835            Self::Legacy { value, .. } |
836            Self::Eip1559 { value, .. } |
837            Self::Eip2930 { value, .. } |
838            Self::Eip4844 { value, .. } |
839            Self::Eip7702 { value, .. } => *value,
840        }
841    }
842
843    fn input(&self) -> &Bytes {
844        self.get_input()
845    }
846
847    fn access_list(&self) -> Option<&AccessList> {
848        match self {
849            Self::Legacy { .. } => None,
850            Self::Eip1559 { access_list: accesslist, .. } |
851            Self::Eip4844 { access_list: accesslist, .. } |
852            Self::Eip2930 { access_list: accesslist, .. } |
853            Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
854        }
855    }
856
857    fn blob_versioned_hashes(&self) -> Option<&[B256]> {
858        match self {
859            Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
860            _ => None,
861        }
862    }
863
864    fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
865        match self {
866            Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
867            _ => None,
868        }
869    }
870}
871
872impl EthPoolTransaction for MockTransaction {
873    fn take_blob(&mut self) -> EthBlobTransactionSidecar {
874        match self {
875            Self::Eip4844 { sidecar, .. } => EthBlobTransactionSidecar::Present(sidecar.clone()),
876            _ => EthBlobTransactionSidecar::None,
877        }
878    }
879
880    fn try_into_pooled_eip4844(
881        self,
882        sidecar: Arc<BlobTransactionSidecar>,
883    ) -> Option<Recovered<Self::Pooled>> {
884        let (tx, signer) = self.into_consensus().into_parts();
885        tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
886            .map(|tx| tx.with_signer(signer))
887            .ok()
888    }
889
890    fn try_from_eip4844(
891        tx: Recovered<Self::Consensus>,
892        sidecar: BlobTransactionSidecar,
893    ) -> Option<Self> {
894        let (tx, signer) = tx.into_parts();
895        tx.try_into_pooled_eip4844(sidecar)
896            .map(|tx| tx.with_signer(signer))
897            .ok()
898            .map(Self::from_pooled)
899    }
900
901    fn validate_blob(
902        &self,
903        _blob: &BlobTransactionSidecar,
904        _settings: &KzgSettings,
905    ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
906        match &self {
907            Self::Eip4844 { .. } => Ok(()),
908            _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
909        }
910    }
911}
912
913impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
914    type Error = TryFromRecoveredTransactionError;
915
916    fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
917        let sender = tx.signer();
918        let transaction = tx.into_inner();
919        let hash = *transaction.tx_hash();
920        let size = transaction.size();
921
922        match transaction.into_typed_transaction() {
923            Transaction::Legacy(TxLegacy {
924                chain_id,
925                nonce,
926                gas_price,
927                gas_limit,
928                to,
929                value,
930                input,
931            }) => Ok(Self::Legacy {
932                chain_id,
933                hash,
934                sender,
935                nonce,
936                gas_price,
937                gas_limit,
938                to,
939                value,
940                input,
941                size,
942                cost: U256::from(gas_limit) * U256::from(gas_price) + value,
943            }),
944            Transaction::Eip2930(TxEip2930 {
945                chain_id,
946                nonce,
947                gas_price,
948                gas_limit,
949                to,
950                value,
951                input,
952                access_list,
953            }) => Ok(Self::Eip2930 {
954                chain_id,
955                hash,
956                sender,
957                nonce,
958                gas_price,
959                gas_limit,
960                to,
961                value,
962                input,
963                access_list,
964                size,
965                cost: U256::from(gas_limit) * U256::from(gas_price) + value,
966            }),
967            Transaction::Eip1559(TxEip1559 {
968                chain_id,
969                nonce,
970                gas_limit,
971                max_fee_per_gas,
972                max_priority_fee_per_gas,
973                to,
974                value,
975                input,
976                access_list,
977            }) => Ok(Self::Eip1559 {
978                chain_id,
979                hash,
980                sender,
981                nonce,
982                max_fee_per_gas,
983                max_priority_fee_per_gas,
984                gas_limit,
985                to,
986                value,
987                input,
988                access_list,
989                size,
990                cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
991            }),
992            Transaction::Eip4844(TxEip4844 {
993                chain_id,
994                nonce,
995                gas_limit,
996                max_fee_per_gas,
997                max_priority_fee_per_gas,
998                to,
999                value,
1000                input,
1001                access_list,
1002                blob_versioned_hashes: _,
1003                max_fee_per_blob_gas,
1004            }) => Ok(Self::Eip4844 {
1005                chain_id,
1006                hash,
1007                sender,
1008                nonce,
1009                max_fee_per_gas,
1010                max_priority_fee_per_gas,
1011                max_fee_per_blob_gas,
1012                gas_limit,
1013                to,
1014                value,
1015                input,
1016                access_list,
1017                sidecar: BlobTransactionSidecar::default(),
1018                blob_versioned_hashes: Default::default(),
1019                size,
1020                cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1021            }),
1022            Transaction::Eip7702(TxEip7702 {
1023                chain_id,
1024                nonce,
1025                gas_limit,
1026                max_fee_per_gas,
1027                max_priority_fee_per_gas,
1028                to,
1029                value,
1030                access_list,
1031                authorization_list,
1032                input,
1033            }) => Ok(Self::Eip7702 {
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                authorization_list,
1046                size,
1047                cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1048            }),
1049        }
1050    }
1051}
1052
1053impl TryFrom<Recovered<TxEnvelope>> for MockTransaction {
1054    type Error = TryFromRecoveredTransactionError;
1055
1056    fn try_from(tx: Recovered<TxEnvelope>) -> Result<Self, Self::Error> {
1057        let sender = tx.signer();
1058        let transaction = tx.into_inner();
1059        let hash = *transaction.tx_hash();
1060        let size = transaction.size();
1061
1062        match transaction {
1063            EthereumTxEnvelope::Legacy(signed_tx) => {
1064                let tx = signed_tx.strip_signature();
1065                Ok(Self::Legacy {
1066                    chain_id: tx.chain_id,
1067                    hash,
1068                    sender,
1069                    nonce: tx.nonce,
1070                    gas_price: tx.gas_price,
1071                    gas_limit: tx.gas_limit,
1072                    to: tx.to,
1073                    value: tx.value,
1074                    input: tx.input,
1075                    size,
1076                    cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1077                })
1078            }
1079            EthereumTxEnvelope::Eip2930(signed_tx) => {
1080                let tx = signed_tx.strip_signature();
1081                Ok(Self::Eip2930 {
1082                    chain_id: tx.chain_id,
1083                    hash,
1084                    sender,
1085                    nonce: tx.nonce,
1086                    gas_price: tx.gas_price,
1087                    gas_limit: tx.gas_limit,
1088                    to: tx.to,
1089                    value: tx.value,
1090                    input: tx.input,
1091                    access_list: tx.access_list,
1092                    size,
1093                    cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1094                })
1095            }
1096            EthereumTxEnvelope::Eip1559(signed_tx) => {
1097                let tx = signed_tx.strip_signature();
1098                Ok(Self::Eip1559 {
1099                    chain_id: tx.chain_id,
1100                    hash,
1101                    sender,
1102                    nonce: tx.nonce,
1103                    max_fee_per_gas: tx.max_fee_per_gas,
1104                    max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1105                    gas_limit: tx.gas_limit,
1106                    to: tx.to,
1107                    value: tx.value,
1108                    input: tx.input,
1109                    access_list: tx.access_list,
1110                    size,
1111                    cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1112                })
1113            }
1114            EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1115                TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1116                    chain_id: tx.chain_id,
1117                    hash,
1118                    sender,
1119                    nonce: tx.nonce,
1120                    max_fee_per_gas: tx.max_fee_per_gas,
1121                    max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1122                    max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1123                    gas_limit: tx.gas_limit,
1124                    to: tx.to,
1125                    value: tx.value,
1126                    input: tx.input.clone(),
1127                    access_list: tx.access_list.clone(),
1128                    sidecar: BlobTransactionSidecar::default(),
1129                    blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1130                    size,
1131                    cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1132                }),
1133                tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1134            },
1135            EthereumTxEnvelope::Eip7702(signed_tx) => {
1136                let tx = signed_tx.strip_signature();
1137                Ok(Self::Eip7702 {
1138                    chain_id: tx.chain_id,
1139                    hash,
1140                    sender,
1141                    nonce: tx.nonce,
1142                    max_fee_per_gas: tx.max_fee_per_gas,
1143                    max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1144                    gas_limit: tx.gas_limit,
1145                    to: tx.to,
1146                    value: tx.value,
1147                    access_list: tx.access_list,
1148                    authorization_list: tx.authorization_list,
1149                    input: tx.input,
1150                    size,
1151                    cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1152                })
1153            }
1154        }
1155    }
1156}
1157
1158impl From<Recovered<PooledTransaction>> for MockTransaction {
1159    fn from(tx: Recovered<PooledTransaction>) -> Self {
1160        let (tx, signer) = tx.into_parts();
1161        Recovered::<TransactionSigned>::new_unchecked(tx.into(), signer).try_into().expect(
1162            "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1163        )
1164    }
1165}
1166
1167impl From<MockTransaction> for Recovered<TransactionSigned> {
1168    fn from(tx: MockTransaction) -> Self {
1169        let hash = *tx.hash();
1170        let sender = tx.sender();
1171        let tx = Transaction::from(tx);
1172        let tx: TransactionSigned =
1173            Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1174        Self::new_unchecked(tx, sender)
1175    }
1176}
1177
1178impl From<MockTransaction> for Transaction {
1179    fn from(mock: MockTransaction) -> Self {
1180        match mock {
1181            MockTransaction::Legacy {
1182                chain_id,
1183                nonce,
1184                gas_price,
1185                gas_limit,
1186                to,
1187                value,
1188                input,
1189                ..
1190            } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1191            MockTransaction::Eip2930 {
1192                chain_id,
1193                nonce,
1194                gas_price,
1195                gas_limit,
1196                to,
1197                value,
1198                access_list,
1199                input,
1200                ..
1201            } => Self::Eip2930(TxEip2930 {
1202                chain_id,
1203                nonce,
1204                gas_price,
1205                gas_limit,
1206                to,
1207                value,
1208                access_list,
1209                input,
1210            }),
1211            MockTransaction::Eip1559 {
1212                chain_id,
1213                nonce,
1214                gas_limit,
1215                max_fee_per_gas,
1216                max_priority_fee_per_gas,
1217                to,
1218                value,
1219                access_list,
1220                input,
1221                ..
1222            } => Self::Eip1559(TxEip1559 {
1223                chain_id,
1224                nonce,
1225                gas_limit,
1226                max_fee_per_gas,
1227                max_priority_fee_per_gas,
1228                to,
1229                value,
1230                access_list,
1231                input,
1232            }),
1233            MockTransaction::Eip4844 {
1234                chain_id,
1235                nonce,
1236                gas_limit,
1237                max_fee_per_gas,
1238                max_priority_fee_per_gas,
1239                to,
1240                value,
1241                access_list,
1242                sidecar,
1243                max_fee_per_blob_gas,
1244                input,
1245                ..
1246            } => Self::Eip4844(TxEip4844 {
1247                chain_id,
1248                nonce,
1249                gas_limit,
1250                max_fee_per_gas,
1251                max_priority_fee_per_gas,
1252                to,
1253                value,
1254                access_list,
1255                blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1256                max_fee_per_blob_gas,
1257                input,
1258            }),
1259            MockTransaction::Eip7702 {
1260                chain_id,
1261                nonce,
1262                gas_limit,
1263                max_fee_per_gas,
1264                max_priority_fee_per_gas,
1265                to,
1266                value,
1267                access_list,
1268                input,
1269                authorization_list,
1270                ..
1271            } => Self::Eip7702(TxEip7702 {
1272                chain_id,
1273                nonce,
1274                gas_limit,
1275                max_fee_per_gas,
1276                max_priority_fee_per_gas,
1277                to,
1278                value,
1279                access_list,
1280                authorization_list,
1281                input,
1282            }),
1283        }
1284    }
1285}
1286
1287#[cfg(any(test, feature = "arbitrary"))]
1288impl proptest::arbitrary::Arbitrary for MockTransaction {
1289    type Parameters = ();
1290    fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1291        use proptest::prelude::Strategy;
1292        use proptest_arbitrary_interop::arb;
1293
1294        arb::<(TransactionSigned, Address)>()
1295            .prop_map(|(signed_transaction, signer)| {
1296                Recovered::new_unchecked(signed_transaction, signer)
1297                    .try_into()
1298                    .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1299            })
1300            .boxed()
1301    }
1302
1303    type Strategy = proptest::strategy::BoxedStrategy<Self>;
1304}
1305
1306/// A factory for creating and managing various types of mock transactions.
1307#[derive(Debug, Default)]
1308pub struct MockTransactionFactory {
1309    pub(crate) ids: SenderIdentifiers,
1310}
1311
1312// === impl MockTransactionFactory ===
1313
1314impl MockTransactionFactory {
1315    /// Generates a transaction ID for the given [`MockTransaction`].
1316    pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1317        let sender = self.ids.sender_id_or_create(tx.sender());
1318        TransactionId::new(sender, *tx.get_nonce())
1319    }
1320
1321    /// Validates a [`MockTransaction`] and returns a [`MockValidTx`].
1322    pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1323        self.validated_with_origin(TransactionOrigin::External, transaction)
1324    }
1325
1326    /// Validates a [`MockTransaction`] and returns a shared [`Arc<MockValidTx>`].
1327    pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1328        Arc::new(self.validated(transaction))
1329    }
1330
1331    /// Converts the transaction into a validated transaction with a specified origin.
1332    pub fn validated_with_origin(
1333        &mut self,
1334        origin: TransactionOrigin,
1335        transaction: MockTransaction,
1336    ) -> MockValidTx {
1337        MockValidTx {
1338            propagate: false,
1339            transaction_id: self.tx_id(&transaction),
1340            transaction,
1341            timestamp: Instant::now(),
1342            origin,
1343        }
1344    }
1345
1346    /// Creates a validated legacy [`MockTransaction`].
1347    pub fn create_legacy(&mut self) -> MockValidTx {
1348        self.validated(MockTransaction::legacy())
1349    }
1350
1351    /// Creates a validated EIP-1559 [`MockTransaction`].
1352    pub fn create_eip1559(&mut self) -> MockValidTx {
1353        self.validated(MockTransaction::eip1559())
1354    }
1355
1356    /// Creates a validated EIP-4844 [`MockTransaction`].
1357    pub fn create_eip4844(&mut self) -> MockValidTx {
1358        self.validated(MockTransaction::eip4844())
1359    }
1360}
1361
1362/// `MockOrdering` is just a `CoinbaseTipOrdering` with `MockTransaction`
1363pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1364
1365/// A ratio of each of the configured transaction types. The percentages sum up to 100, this is
1366/// enforced in [`MockTransactionRatio::new`] by an assert.
1367#[derive(Debug, Clone)]
1368pub struct MockTransactionRatio {
1369    /// Percent of transactions that are legacy transactions
1370    pub legacy_pct: u32,
1371    /// Percent of transactions that are access list transactions
1372    pub access_list_pct: u32,
1373    /// Percent of transactions that are EIP-1559 transactions
1374    pub dynamic_fee_pct: u32,
1375    /// Percent of transactions that are EIP-4844 transactions
1376    pub blob_pct: u32,
1377}
1378
1379impl MockTransactionRatio {
1380    /// Creates a new [`MockTransactionRatio`] with the given percentages.
1381    ///
1382    /// Each argument is treated as a full percent, for example `30u32` is `30%`.
1383    ///
1384    /// The percentages must sum up to 100 exactly, or this method will panic.
1385    pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1386        let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1387        assert_eq!(
1388            total,
1389            100,
1390            "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}",
1391        );
1392
1393        Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1394    }
1395
1396    /// Create a [`WeightedIndex`] from this transaction ratio.
1397    ///
1398    /// This index will sample in the following order:
1399    /// * Legacy transaction => 0
1400    /// * EIP-2930 transaction => 1
1401    /// * EIP-1559 transaction => 2
1402    /// * EIP-4844 transaction => 3
1403    pub fn weighted_index(&self) -> WeightedIndex<u32> {
1404        WeightedIndex::new([
1405            self.legacy_pct,
1406            self.access_list_pct,
1407            self.dynamic_fee_pct,
1408            self.blob_pct,
1409        ])
1410        .unwrap()
1411    }
1412}
1413
1414/// The range of each type of fee, for the different transaction types
1415#[derive(Debug, Clone)]
1416pub struct MockFeeRange {
1417    /// The range of `gas_price` or legacy and access list transactions
1418    pub gas_price: Uniform<u128>,
1419    /// The range of priority fees for EIP-1559 and EIP-4844 transactions
1420    pub priority_fee: Uniform<u128>,
1421    /// The range of max fees for EIP-1559 and EIP-4844 transactions
1422    pub max_fee: Uniform<u128>,
1423    /// The range of max fees per blob gas for EIP-4844 transactions
1424    pub max_fee_blob: Uniform<u128>,
1425}
1426
1427impl MockFeeRange {
1428    /// Creates a new [`MockFeeRange`] with the given ranges.
1429    ///
1430    /// Expects the bottom of the `priority_fee_range` to be greater than the top of the
1431    /// `max_fee_range`.
1432    pub fn new(
1433        gas_price: Range<u128>,
1434        priority_fee: Range<u128>,
1435        max_fee: Range<u128>,
1436        max_fee_blob: Range<u128>,
1437    ) -> Self {
1438        assert!(
1439            max_fee.start <= priority_fee.end,
1440            "max_fee_range should be strictly below the priority fee range"
1441        );
1442        Self {
1443            gas_price: gas_price.try_into().unwrap(),
1444            priority_fee: priority_fee.try_into().unwrap(),
1445            max_fee: max_fee.try_into().unwrap(),
1446            max_fee_blob: max_fee_blob.try_into().unwrap(),
1447        }
1448    }
1449
1450    /// Returns a sample of `gas_price` for legacy and access list transactions with the given
1451    /// [Rng](rand::Rng).
1452    pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1453        self.gas_price.sample(rng)
1454    }
1455
1456    /// Returns a sample of `max_priority_fee_per_gas` for EIP-1559 and EIP-4844 transactions with
1457    /// the given [Rng](rand::Rng).
1458    pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1459        self.priority_fee.sample(rng)
1460    }
1461
1462    /// Returns a sample of `max_fee_per_gas` for EIP-1559 and EIP-4844 transactions with the given
1463    /// [Rng](rand::Rng).
1464    pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1465        self.max_fee.sample(rng)
1466    }
1467
1468    /// Returns a sample of `max_fee_per_blob_gas` for EIP-4844 transactions with the given
1469    /// [Rng](rand::Rng).
1470    pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1471        self.max_fee_blob.sample(rng)
1472    }
1473}
1474
1475/// A configured distribution that can generate transactions
1476#[derive(Debug, Clone)]
1477pub struct MockTransactionDistribution {
1478    /// ratio of each transaction type to generate
1479    transaction_ratio: MockTransactionRatio,
1480    /// generates the gas limit
1481    gas_limit_range: Uniform<u64>,
1482    /// generates the transaction's fake size
1483    size_range: Uniform<usize>,
1484    /// generates fees for the given transaction types
1485    fee_ranges: MockFeeRange,
1486}
1487
1488impl MockTransactionDistribution {
1489    /// Creates a new generator distribution.
1490    pub fn new(
1491        transaction_ratio: MockTransactionRatio,
1492        fee_ranges: MockFeeRange,
1493        gas_limit_range: Range<u64>,
1494        size_range: Range<usize>,
1495    ) -> Self {
1496        Self {
1497            transaction_ratio,
1498            gas_limit_range: gas_limit_range.try_into().unwrap(),
1499            fee_ranges,
1500            size_range: size_range.try_into().unwrap(),
1501        }
1502    }
1503
1504    /// Generates a new transaction
1505    pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1506        let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1507        let tx = match transaction_sample {
1508            0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1509            1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1510            2 => MockTransaction::eip1559()
1511                .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1512                .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1513            3 => MockTransaction::eip4844()
1514                .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1515                .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1516                .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1517            _ => unreachable!("unknown transaction type returned by the weighted index"),
1518        };
1519
1520        let size = self.size_range.sample(rng);
1521
1522        tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1523    }
1524
1525    /// Generates a new transaction set for the given sender.
1526    ///
1527    /// The nonce range defines which nonces to set, and how many transactions to generate.
1528    pub fn tx_set(
1529        &self,
1530        sender: Address,
1531        nonce_range: Range<u64>,
1532        rng: &mut impl rand::Rng,
1533    ) -> MockTransactionSet {
1534        let txs =
1535            nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1536        MockTransactionSet::new(txs)
1537    }
1538
1539    /// Generates a transaction set that ensures that blob txs are not mixed with other transaction
1540    /// types.
1541    ///
1542    /// This is done by taking the existing distribution, and using the first transaction to
1543    /// determine whether or not the sender should generate entirely blob transactions.
1544    pub fn tx_set_non_conflicting_types(
1545        &self,
1546        sender: Address,
1547        nonce_range: Range<u64>,
1548        rng: &mut impl rand::Rng,
1549    ) -> NonConflictingSetOutcome {
1550        // This will create a modified distribution that will only generate blob transactions
1551        // for the given sender, if the blob transaction is the first transaction in the set.
1552        //
1553        // Otherwise, it will modify the transaction distribution to only generate legacy, eip2930,
1554        // and eip1559 transactions.
1555        //
1556        // The new distribution should still have the same relative amount of transaction types.
1557        let mut modified_distribution = self.clone();
1558        let first_tx = self.tx(nonce_range.start, rng);
1559
1560        // now we can check and modify the distribution, preserving potentially uneven ratios
1561        // between transaction types
1562        if first_tx.is_eip4844() {
1563            modified_distribution.transaction_ratio = MockTransactionRatio {
1564                legacy_pct: 0,
1565                access_list_pct: 0,
1566                dynamic_fee_pct: 0,
1567                blob_pct: 100,
1568            };
1569
1570            // finally generate the transaction set
1571            NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1572                sender,
1573                nonce_range,
1574                rng,
1575            ))
1576        } else {
1577            let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1578                modified_distribution.transaction_ratio;
1579
1580            // Calculate the total weight of non-blob transactions
1581            let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1582
1583            // Calculate new weights, preserving the ratio between non-blob transaction types
1584            let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1585                .into_iter()
1586                .map(|weight| weight * 100 / total_non_blob_weight)
1587                .collect();
1588
1589            let new_ratio = MockTransactionRatio {
1590                legacy_pct: new_weights[0],
1591                access_list_pct: new_weights[1],
1592                dynamic_fee_pct: new_weights[2],
1593                blob_pct: 0,
1594            };
1595
1596            // Set the new transaction ratio excluding blob transactions and preserving the relative
1597            // ratios
1598            modified_distribution.transaction_ratio = new_ratio;
1599
1600            // finally generate the transaction set
1601            NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1602        }
1603    }
1604}
1605
1606/// Indicates whether or not the non-conflicting transaction set generated includes only blobs, or
1607/// a mix of transaction types.
1608#[derive(Debug, Clone)]
1609pub enum NonConflictingSetOutcome {
1610    /// The transaction set includes only blob transactions
1611    BlobsOnly(MockTransactionSet),
1612    /// The transaction set includes a mix of transaction types
1613    Mixed(MockTransactionSet),
1614}
1615
1616impl NonConflictingSetOutcome {
1617    /// Returns the inner [`MockTransactionSet`]
1618    pub fn into_inner(self) -> MockTransactionSet {
1619        match self {
1620            Self::BlobsOnly(set) | Self::Mixed(set) => set,
1621        }
1622    }
1623
1624    /// Introduces artificial nonce gaps into the transaction set, at random, with a range of gap
1625    /// sizes.
1626    ///
1627    /// If this is a [`NonConflictingSetOutcome::BlobsOnly`], then nonce gaps will not be
1628    /// introduced. Otherwise, the nonce gaps will be introduced to the mixed transaction set.
1629    ///
1630    /// See [`MockTransactionSet::with_nonce_gaps`] for more information on the generation process.
1631    pub fn with_nonce_gaps(
1632        &mut self,
1633        gap_pct: u32,
1634        gap_range: Range<u64>,
1635        rng: &mut impl rand::Rng,
1636    ) {
1637        match self {
1638            Self::BlobsOnly(_) => {}
1639            Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1640        }
1641    }
1642}
1643
1644/// A set of [`MockTransaction`]s that can be modified at once
1645#[derive(Debug, Clone)]
1646pub struct MockTransactionSet {
1647    pub(crate) transactions: Vec<MockTransaction>,
1648}
1649
1650impl MockTransactionSet {
1651    /// Create a new [`MockTransactionSet`] from a list of transactions
1652    const fn new(transactions: Vec<MockTransaction>) -> Self {
1653        Self { transactions }
1654    }
1655
1656    /// Creates a series of dependent transactions for a given sender and nonce.
1657    ///
1658    /// This method generates a sequence of transactions starting from the provided nonce
1659    /// for the given sender.
1660    ///
1661    /// The number of transactions created is determined by `tx_count`.
1662    pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1663        let mut txs = Vec::with_capacity(tx_count);
1664        let mut curr_tx =
1665            MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1666        for _ in 0..tx_count {
1667            txs.push(curr_tx.clone());
1668            curr_tx = curr_tx.next();
1669        }
1670
1671        Self::new(txs)
1672    }
1673
1674    /// Creates a chain of transactions for a given sender with a specified count.
1675    ///
1676    /// This method generates a sequence of transactions starting from the specified sender
1677    /// and creates a chain of transactions based on the `tx_count`.
1678    pub fn sequential_transactions_by_sender(
1679        sender: Address,
1680        tx_count: usize,
1681        tx_type: TxType,
1682    ) -> Self {
1683        Self::dependent(sender, 0, tx_count, tx_type)
1684    }
1685
1686    /// Introduces artificial nonce gaps into the transaction set, at random, with a range of gap
1687    /// sizes.
1688    ///
1689    /// This assumes that the `gap_pct` is between 0 and 100, and the `gap_range` has a lower bound
1690    /// of at least one. This is enforced with assertions.
1691    ///
1692    /// The `gap_pct` is the percent chance that the next transaction in the set will introduce a
1693    /// nonce gap.
1694    ///
1695    /// Let an example transaction set be `[(tx1, 1), (tx2, 2)]`, where the first element of the
1696    /// tuple is a transaction, and the second element is the nonce. If the `gap_pct` is 50, and
1697    /// the `gap_range` is `1..=1`, then the resulting transaction set could would be either
1698    /// `[(tx1, 1), (tx2, 2)]` or `[(tx1, 1), (tx2, 3)]`, with a 50% chance of either.
1699    pub fn with_nonce_gaps(
1700        &mut self,
1701        gap_pct: u32,
1702        gap_range: Range<u64>,
1703        rng: &mut impl rand::Rng,
1704    ) {
1705        assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1706        assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1707
1708        let mut prev_nonce = 0;
1709        for tx in &mut self.transactions {
1710            if rng.random_bool(gap_pct as f64 / 100.0) {
1711                prev_nonce += gap_range.start;
1712            } else {
1713                prev_nonce += 1;
1714            }
1715            tx.set_nonce(prev_nonce);
1716        }
1717    }
1718
1719    /// Add transactions to the [`MockTransactionSet`]
1720    pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1721        self.transactions.extend(txs);
1722    }
1723
1724    /// Extract the inner [Vec] of [`MockTransaction`]s
1725    pub fn into_vec(self) -> Vec<MockTransaction> {
1726        self.transactions
1727    }
1728
1729    /// Returns an iterator over the contained transactions in the set
1730    pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1731        self.transactions.iter()
1732    }
1733
1734    /// Returns a mutable iterator over the contained transactions in the set.
1735    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1736        self.transactions.iter_mut()
1737    }
1738}
1739
1740impl IntoIterator for MockTransactionSet {
1741    type Item = MockTransaction;
1742    type IntoIter = IntoIter<MockTransaction>;
1743
1744    fn into_iter(self) -> Self::IntoIter {
1745        self.transactions.into_iter()
1746    }
1747}
1748
1749#[test]
1750fn test_mock_priority() {
1751    use crate::TransactionOrdering;
1752
1753    let o = MockOrdering::default();
1754    let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1755    let hi = lo.next().inc_price();
1756    assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1757}
1758
1759#[cfg(test)]
1760mod tests {
1761    use super::*;
1762    use alloy_consensus::Transaction;
1763    use alloy_primitives::U256;
1764
1765    #[test]
1766    fn test_mock_transaction_factory() {
1767        let mut factory = MockTransactionFactory::default();
1768
1769        // Test legacy transaction creation
1770        let legacy = factory.create_legacy();
1771        assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1772
1773        // Test EIP1559 transaction creation
1774        let eip1559 = factory.create_eip1559();
1775        assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1776
1777        // Test EIP4844 transaction creation
1778        let eip4844 = factory.create_eip4844();
1779        assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1780    }
1781
1782    #[test]
1783    fn test_mock_transaction_set() {
1784        let sender = Address::random();
1785        let nonce_start = 0u64;
1786        let count = 3;
1787
1788        // Test legacy transaction set
1789        let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1790        assert_eq!(legacy_set.transactions.len(), count);
1791        for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1792            assert_eq!(tx.tx_type(), TxType::Legacy);
1793            assert_eq!(tx.nonce(), nonce_start + idx as u64);
1794            assert_eq!(tx.sender(), sender);
1795        }
1796
1797        // Test EIP1559 transaction set
1798        let eip1559_set =
1799            MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1800        assert_eq!(eip1559_set.transactions.len(), count);
1801        for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1802            assert_eq!(tx.tx_type(), TxType::Eip1559);
1803            assert_eq!(tx.nonce(), nonce_start + idx as u64);
1804            assert_eq!(tx.sender(), sender);
1805        }
1806    }
1807
1808    #[test]
1809    fn test_mock_transaction_modifications() {
1810        let tx = MockTransaction::eip1559();
1811
1812        // Test price increment
1813        let original_price = tx.get_gas_price();
1814        let tx_inc = tx.inc_price();
1815        assert!(tx_inc.get_gas_price() > original_price);
1816
1817        // Test gas limit increment
1818        let original_limit = tx.gas_limit();
1819        let tx_inc = tx.inc_limit();
1820        assert!(tx_inc.gas_limit() > original_limit);
1821
1822        // Test nonce increment
1823        let original_nonce = tx.nonce();
1824        let tx_inc = tx.inc_nonce();
1825        assert_eq!(tx_inc.nonce(), original_nonce + 1);
1826    }
1827
1828    #[test]
1829    fn test_mock_transaction_cost() {
1830        let tx = MockTransaction::eip1559()
1831            .with_gas_limit(7_000)
1832            .with_max_fee(100)
1833            .with_value(U256::ZERO);
1834
1835        // Cost is calculated as (gas_limit * max_fee_per_gas) + value
1836        let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1837        assert_eq!(*tx.cost(), expected_cost);
1838    }
1839}