Skip to main content

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