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