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