1use 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
39pub type MockTxPool = TxPool<MockOrdering>;
43
44pub type MockValidTx = ValidPoolTransaction<MockTransaction>;
49
50pub fn mock_tx_pool() -> MockTxPool {
52 MockTxPool::new(Default::default(), Default::default())
53}
54
55macro_rules! set_value {
57 ($this:ident => $field:ident) => {
58 let new_value = $field;
59 match $this {
60 MockTransaction::Legacy { ref mut $field, .. } |
61 MockTransaction::Eip1559 { ref mut $field, .. } |
62 MockTransaction::Eip4844 { ref mut $field, .. } |
63 MockTransaction::Eip2930 { ref mut $field, .. } |
64 MockTransaction::Eip7702 { ref mut $field, .. } => {
65 *$field = new_value;
66 }
67 }
68 $this.update_cost();
70 };
71}
72
73macro_rules! get_value {
75 ($this:tt => $field:ident) => {
76 match $this {
77 MockTransaction::Legacy { $field, .. } |
78 MockTransaction::Eip1559 { $field, .. } |
79 MockTransaction::Eip4844 { $field, .. } |
80 MockTransaction::Eip2930 { $field, .. } |
81 MockTransaction::Eip7702 { $field, .. } => $field,
82 }
83 };
84}
85
86macro_rules! make_setters_getters {
88 ($($name:ident => $t:ty);*) => {
89 paste! {$(
90 pub fn [<set_ $name>](&mut self, $name: $t) -> &mut Self {
92 set_value!(self => $name);
93 self
94 }
95
96 pub fn [<with_ $name>](mut self, $name: $t) -> Self {
98 set_value!(self => $name);
99 self
100 }
101
102 pub const fn [<get_ $name>](&self) -> &$t {
104 get_value!(self => $name)
105 }
106 )*}
107 };
108}
109
110#[derive(Debug, Clone, Eq, PartialEq)]
112pub enum MockTransaction {
113 Legacy {
115 chain_id: Option<ChainId>,
117 hash: B256,
119 sender: Address,
121 nonce: u64,
123 gas_price: u128,
125 gas_limit: u64,
127 to: TxKind,
129 value: U256,
131 input: Bytes,
133 size: usize,
135 cost: U256,
137 },
138 Eip2930 {
140 chain_id: ChainId,
142 hash: B256,
144 sender: Address,
146 nonce: u64,
148 to: TxKind,
150 gas_limit: u64,
152 input: Bytes,
154 value: U256,
156 gas_price: u128,
158 access_list: AccessList,
160 size: usize,
162 cost: U256,
164 },
165 Eip1559 {
167 chain_id: ChainId,
169 hash: B256,
171 sender: Address,
173 nonce: u64,
175 max_fee_per_gas: u128,
177 max_priority_fee_per_gas: u128,
179 gas_limit: u64,
181 to: TxKind,
183 value: U256,
185 access_list: AccessList,
187 input: Bytes,
189 size: usize,
191 cost: U256,
193 },
194 Eip4844 {
196 chain_id: ChainId,
198 hash: B256,
200 sender: Address,
202 nonce: u64,
204 max_fee_per_gas: u128,
206 max_priority_fee_per_gas: u128,
208 max_fee_per_blob_gas: u128,
210 gas_limit: u64,
212 to: Address,
214 value: U256,
216 access_list: AccessList,
218 input: Bytes,
220 sidecar: BlobTransactionSidecarVariant,
222 blob_versioned_hashes: Vec<B256>,
224 size: usize,
226 cost: U256,
228 },
229 Eip7702 {
231 chain_id: ChainId,
233 hash: B256,
235 sender: Address,
237 nonce: u64,
239 max_fee_per_gas: u128,
241 max_priority_fee_per_gas: u128,
243 gas_limit: u64,
245 to: Address,
247 value: U256,
249 access_list: AccessList,
251 authorization_list: Vec<SignedAuthorization>,
253 input: Bytes,
255 size: usize,
257 cost: U256,
259 },
260}
261
262impl MockTransaction {
265 make_setters_getters! {
266 nonce => u64;
267 hash => B256;
268 sender => Address;
269 gas_limit => u64;
270 value => U256;
271 input => Bytes;
272 size => usize
273 }
274
275 pub fn legacy() -> Self {
277 Self::Legacy {
278 chain_id: Some(1),
279 hash: B256::random(),
280 sender: Address::random(),
281 nonce: 0,
282 gas_price: 0,
283 gas_limit: 0,
284 to: Address::random().into(),
285 value: Default::default(),
286 input: Default::default(),
287 size: Default::default(),
288 cost: U256::ZERO,
289 }
290 }
291
292 pub fn eip2930() -> Self {
294 Self::Eip2930 {
295 chain_id: 1,
296 hash: B256::random(),
297 sender: Address::random(),
298 nonce: 0,
299 to: Address::random().into(),
300 gas_limit: 0,
301 input: Bytes::new(),
302 value: Default::default(),
303 gas_price: 0,
304 access_list: Default::default(),
305 size: Default::default(),
306 cost: U256::ZERO,
307 }
308 }
309
310 pub fn eip1559() -> Self {
312 Self::Eip1559 {
313 chain_id: 1,
314 hash: B256::random(),
315 sender: Address::random(),
316 nonce: 0,
317 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
318 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
319 gas_limit: 0,
320 to: Address::random().into(),
321 value: Default::default(),
322 input: Bytes::new(),
323 access_list: Default::default(),
324 size: Default::default(),
325 cost: U256::ZERO,
326 }
327 }
328
329 pub fn eip7702() -> Self {
331 Self::Eip7702 {
332 chain_id: 1,
333 hash: B256::random(),
334 sender: Address::random(),
335 nonce: 0,
336 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
337 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
338 gas_limit: 0,
339 to: Address::random(),
340 value: Default::default(),
341 input: Bytes::new(),
342 access_list: Default::default(),
343 authorization_list: vec![],
344 size: Default::default(),
345 cost: U256::ZERO,
346 }
347 }
348
349 pub fn eip4844() -> Self {
351 Self::Eip4844 {
352 chain_id: 1,
353 hash: B256::random(),
354 sender: Address::random(),
355 nonce: 0,
356 max_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
357 max_priority_fee_per_gas: MIN_PROTOCOL_BASE_FEE as u128,
358 max_fee_per_blob_gas: DATA_GAS_PER_BLOB as u128,
359 gas_limit: 0,
360 to: Address::random(),
361 value: Default::default(),
362 input: Bytes::new(),
363 access_list: Default::default(),
364 sidecar: BlobTransactionSidecarVariant::Eip4844(Default::default()),
365 blob_versioned_hashes: Default::default(),
366 size: Default::default(),
367 cost: U256::ZERO,
368 }
369 }
370
371 pub fn eip4844_with_sidecar(sidecar: BlobTransactionSidecarVariant) -> Self {
373 let mut transaction = Self::eip4844();
374 if let Self::Eip4844 { sidecar: existing_sidecar, blob_versioned_hashes, .. } =
375 &mut transaction
376 {
377 *blob_versioned_hashes = sidecar.versioned_hashes().collect();
378 *existing_sidecar = sidecar;
379 }
380 transaction
381 }
382
383 pub fn new_from_type(tx_type: TxType) -> Self {
392 match tx_type {
393 TxType::Legacy => Self::legacy(),
394 TxType::Eip2930 => Self::eip2930(),
395 TxType::Eip1559 => Self::eip1559(),
396 TxType::Eip4844 => Self::eip4844(),
397 TxType::Eip7702 => Self::eip7702(),
398 }
399 }
400
401 pub const fn with_blob_fee(mut self, val: u128) -> Self {
403 self.set_blob_fee(val);
404 self
405 }
406
407 pub const fn set_blob_fee(&mut self, val: u128) -> &mut Self {
409 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = self {
410 *max_fee_per_blob_gas = val;
411 }
412 self
413 }
414
415 pub const fn set_priority_fee(&mut self, val: u128) -> &mut Self {
417 if let Self::Eip1559 { max_priority_fee_per_gas, .. } |
418 Self::Eip4844 { max_priority_fee_per_gas, .. } = self
419 {
420 *max_priority_fee_per_gas = val;
421 }
422 self
423 }
424
425 pub const fn with_priority_fee(mut self, val: u128) -> Self {
427 self.set_priority_fee(val);
428 self
429 }
430
431 pub const fn get_priority_fee(&self) -> Option<u128> {
433 match self {
434 Self::Eip1559 { max_priority_fee_per_gas, .. } |
435 Self::Eip4844 { max_priority_fee_per_gas, .. } |
436 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
437 _ => None,
438 }
439 }
440
441 pub const fn set_max_fee(&mut self, val: u128) -> &mut Self {
443 if let Self::Eip1559 { max_fee_per_gas, .. } |
444 Self::Eip4844 { max_fee_per_gas, .. } |
445 Self::Eip7702 { max_fee_per_gas, .. } = self
446 {
447 *max_fee_per_gas = val;
448 }
449 self
450 }
451
452 pub const fn with_max_fee(mut self, val: u128) -> Self {
454 self.set_max_fee(val);
455 self
456 }
457
458 pub const fn get_max_fee(&self) -> Option<u128> {
460 match self {
461 Self::Eip1559 { max_fee_per_gas, .. } |
462 Self::Eip4844 { max_fee_per_gas, .. } |
463 Self::Eip7702 { max_fee_per_gas, .. } => Some(*max_fee_per_gas),
464 _ => None,
465 }
466 }
467
468 pub fn set_accesslist(&mut self, list: AccessList) -> &mut Self {
470 match self {
471 Self::Legacy { .. } => {}
472 Self::Eip1559 { access_list: accesslist, .. } |
473 Self::Eip4844 { access_list: accesslist, .. } |
474 Self::Eip2930 { access_list: accesslist, .. } |
475 Self::Eip7702 { access_list: accesslist, .. } => {
476 *accesslist = list;
477 }
478 }
479 self
480 }
481
482 pub fn set_authorization_list(&mut self, list: Vec<SignedAuthorization>) -> &mut Self {
484 if let Self::Eip7702 { authorization_list, .. } = self {
485 *authorization_list = list;
486 }
487
488 self
489 }
490
491 pub const fn set_gas_price(&mut self, val: u128) -> &mut Self {
493 match self {
494 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => {
495 *gas_price = val;
496 }
497 Self::Eip1559 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
498 Self::Eip4844 { max_fee_per_gas, max_priority_fee_per_gas, .. } |
499 Self::Eip7702 { max_fee_per_gas, max_priority_fee_per_gas, .. } => {
500 *max_fee_per_gas = val;
501 *max_priority_fee_per_gas = val;
502 }
503 }
504 self
505 }
506
507 pub const fn with_gas_price(mut self, val: u128) -> Self {
509 match self {
510 Self::Legacy { ref mut gas_price, .. } | Self::Eip2930 { ref mut gas_price, .. } => {
511 *gas_price = val;
512 }
513 Self::Eip1559 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
514 Self::Eip4844 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } |
515 Self::Eip7702 { ref mut max_fee_per_gas, ref mut max_priority_fee_per_gas, .. } => {
516 *max_fee_per_gas = val;
517 *max_priority_fee_per_gas = val;
518 }
519 }
520 self
521 }
522
523 pub const fn get_gas_price(&self) -> u128 {
525 match self {
526 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
527 Self::Eip1559 { max_fee_per_gas, .. } |
528 Self::Eip4844 { max_fee_per_gas, .. } |
529 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
530 }
531 }
532
533 pub fn prev(&self) -> Self {
535 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() - 1)
536 }
537
538 pub fn next(&self) -> Self {
540 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + 1)
541 }
542
543 pub fn skip(&self, skip: u64) -> Self {
545 self.clone().with_hash(B256::random()).with_nonce(self.get_nonce() + skip + 1)
546 }
547
548 pub fn inc_nonce(self) -> Self {
550 let nonce = self.get_nonce() + 1;
551 self.with_nonce(nonce)
552 }
553
554 pub fn rng_hash(self) -> Self {
556 self.with_hash(B256::random())
557 }
558
559 pub fn inc_price(&self) -> Self {
561 self.inc_price_by(1)
562 }
563
564 pub fn inc_price_by(&self, value: u128) -> Self {
566 self.clone().with_gas_price(self.get_gas_price().checked_add(value).unwrap())
567 }
568
569 pub fn decr_price(&self) -> Self {
571 self.decr_price_by(1)
572 }
573
574 pub fn decr_price_by(&self, value: u128) -> Self {
576 self.clone().with_gas_price(self.get_gas_price().checked_sub(value).unwrap())
577 }
578
579 pub fn inc_value(&self) -> Self {
581 self.clone().with_value(self.get_value().checked_add(U256::from(1)).unwrap())
582 }
583
584 pub fn inc_limit(&self) -> Self {
586 self.clone().with_gas_limit(self.get_gas_limit() + 1)
587 }
588
589 pub fn inc_blob_fee(&self) -> Self {
593 self.inc_blob_fee_by(1)
594 }
595
596 pub fn inc_blob_fee_by(&self, value: u128) -> Self {
600 let mut this = self.clone();
601 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
602 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_add(value).unwrap();
603 }
604 this
605 }
606
607 pub fn decr_blob_fee(&self) -> Self {
611 self.decr_price_by(1)
612 }
613
614 pub fn decr_blob_fee_by(&self, value: u128) -> Self {
618 let mut this = self.clone();
619 if let Self::Eip4844 { max_fee_per_blob_gas, .. } = &mut this {
620 *max_fee_per_blob_gas = max_fee_per_blob_gas.checked_sub(value).unwrap();
621 }
622 this
623 }
624
625 pub const fn tx_type(&self) -> u8 {
627 match self {
628 Self::Legacy { .. } => LEGACY_TX_TYPE_ID,
629 Self::Eip1559 { .. } => EIP1559_TX_TYPE_ID,
630 Self::Eip4844 { .. } => EIP4844_TX_TYPE_ID,
631 Self::Eip2930 { .. } => EIP2930_TX_TYPE_ID,
632 Self::Eip7702 { .. } => EIP7702_TX_TYPE_ID,
633 }
634 }
635
636 pub const fn is_legacy(&self) -> bool {
638 matches!(self, Self::Legacy { .. })
639 }
640
641 pub const fn is_eip1559(&self) -> bool {
643 matches!(self, Self::Eip1559 { .. })
644 }
645
646 pub const fn is_eip4844(&self) -> bool {
648 matches!(self, Self::Eip4844 { .. })
649 }
650
651 pub const fn is_eip2930(&self) -> bool {
653 matches!(self, Self::Eip2930 { .. })
654 }
655
656 pub const fn is_eip7702(&self) -> bool {
658 matches!(self, Self::Eip7702 { .. })
659 }
660
661 fn update_cost(&mut self) {
662 match self {
663 Self::Legacy { cost, gas_limit, gas_price, value, .. } |
664 Self::Eip2930 { cost, gas_limit, gas_price, value, .. } => {
665 *cost = U256::from(*gas_limit) * U256::from(*gas_price) + *value
666 }
667 Self::Eip1559 { cost, gas_limit, max_fee_per_gas, value, .. } |
668 Self::Eip4844 { cost, gas_limit, max_fee_per_gas, value, .. } |
669 Self::Eip7702 { cost, gas_limit, max_fee_per_gas, value, .. } => {
670 *cost = U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + *value
671 }
672 };
673 }
674}
675
676impl PoolTransaction for MockTransaction {
677 type TryFromConsensusError = ValueError<EthereumTxEnvelope<TxEip4844>>;
678
679 type Consensus = TransactionSigned;
680
681 type Pooled = PooledTransactionVariant;
682
683 fn into_consensus(self) -> Recovered<Self::Consensus> {
684 self.into()
685 }
686
687 fn from_pooled(pooled: Recovered<Self::Pooled>) -> Self {
688 pooled.into()
689 }
690
691 fn hash(&self) -> &TxHash {
692 self.get_hash()
693 }
694
695 fn sender(&self) -> Address {
696 *self.get_sender()
697 }
698
699 fn sender_ref(&self) -> &Address {
700 self.get_sender()
701 }
702
703 fn cost(&self) -> &U256 {
708 match self {
709 Self::Legacy { cost, .. } |
710 Self::Eip2930 { cost, .. } |
711 Self::Eip1559 { cost, .. } |
712 Self::Eip4844 { cost, .. } |
713 Self::Eip7702 { cost, .. } => cost,
714 }
715 }
716
717 fn encoded_length(&self) -> usize {
719 self.size()
720 }
721}
722
723impl InMemorySize for MockTransaction {
724 fn size(&self) -> usize {
725 *self.get_size()
726 }
727}
728
729impl Typed2718 for MockTransaction {
730 fn ty(&self) -> u8 {
731 match self {
732 Self::Legacy { .. } => TxType::Legacy.into(),
733 Self::Eip1559 { .. } => TxType::Eip1559.into(),
734 Self::Eip4844 { .. } => TxType::Eip4844.into(),
735 Self::Eip2930 { .. } => TxType::Eip2930.into(),
736 Self::Eip7702 { .. } => TxType::Eip7702.into(),
737 }
738 }
739}
740
741impl alloy_consensus::Transaction for MockTransaction {
742 fn chain_id(&self) -> Option<u64> {
743 match self {
744 Self::Legacy { chain_id, .. } => *chain_id,
745 Self::Eip1559 { chain_id, .. } |
746 Self::Eip4844 { chain_id, .. } |
747 Self::Eip2930 { chain_id, .. } |
748 Self::Eip7702 { chain_id, .. } => Some(*chain_id),
749 }
750 }
751
752 fn nonce(&self) -> u64 {
753 *self.get_nonce()
754 }
755
756 fn gas_limit(&self) -> u64 {
757 *self.get_gas_limit()
758 }
759
760 fn gas_price(&self) -> Option<u128> {
761 match self {
762 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => Some(*gas_price),
763 _ => None,
764 }
765 }
766
767 fn max_fee_per_gas(&self) -> u128 {
768 match self {
769 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
770 Self::Eip1559 { max_fee_per_gas, .. } |
771 Self::Eip4844 { max_fee_per_gas, .. } |
772 Self::Eip7702 { max_fee_per_gas, .. } => *max_fee_per_gas,
773 }
774 }
775
776 fn max_priority_fee_per_gas(&self) -> Option<u128> {
777 match self {
778 Self::Legacy { .. } | Self::Eip2930 { .. } => None,
779 Self::Eip1559 { max_priority_fee_per_gas, .. } |
780 Self::Eip4844 { max_priority_fee_per_gas, .. } |
781 Self::Eip7702 { max_priority_fee_per_gas, .. } => Some(*max_priority_fee_per_gas),
782 }
783 }
784
785 fn max_fee_per_blob_gas(&self) -> Option<u128> {
786 match self {
787 Self::Eip4844 { max_fee_per_blob_gas, .. } => Some(*max_fee_per_blob_gas),
788 _ => None,
789 }
790 }
791
792 fn priority_fee_or_price(&self) -> u128 {
793 match self {
794 Self::Legacy { gas_price, .. } | Self::Eip2930 { gas_price, .. } => *gas_price,
795 Self::Eip1559 { max_priority_fee_per_gas, .. } |
796 Self::Eip4844 { max_priority_fee_per_gas, .. } |
797 Self::Eip7702 { max_priority_fee_per_gas, .. } => *max_priority_fee_per_gas,
798 }
799 }
800
801 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
802 base_fee.map_or_else(
803 || self.max_fee_per_gas(),
804 |base_fee| {
805 let tip = self.max_fee_per_gas().saturating_sub(base_fee as u128);
808 if let Some(max_tip) = self.max_priority_fee_per_gas() {
809 if tip > max_tip {
810 max_tip + base_fee as u128
811 } else {
812 self.max_fee_per_gas()
814 }
815 } else {
816 self.max_fee_per_gas()
817 }
818 },
819 )
820 }
821
822 fn is_dynamic_fee(&self) -> bool {
823 !matches!(self, Self::Legacy { .. } | Self::Eip2930 { .. })
824 }
825
826 fn kind(&self) -> TxKind {
827 match self {
828 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => *to,
829 Self::Eip4844 { to, .. } | Self::Eip7702 { to, .. } => TxKind::Call(*to),
830 }
831 }
832
833 fn is_create(&self) -> bool {
834 match self {
835 Self::Legacy { to, .. } | Self::Eip1559 { to, .. } | Self::Eip2930 { to, .. } => {
836 to.is_create()
837 }
838 Self::Eip4844 { .. } | Self::Eip7702 { .. } => false,
839 }
840 }
841
842 fn value(&self) -> U256 {
843 match self {
844 Self::Legacy { value, .. } |
845 Self::Eip1559 { value, .. } |
846 Self::Eip2930 { value, .. } |
847 Self::Eip4844 { value, .. } |
848 Self::Eip7702 { value, .. } => *value,
849 }
850 }
851
852 fn input(&self) -> &Bytes {
853 self.get_input()
854 }
855
856 fn access_list(&self) -> Option<&AccessList> {
857 match self {
858 Self::Legacy { .. } => None,
859 Self::Eip1559 { access_list: accesslist, .. } |
860 Self::Eip4844 { access_list: accesslist, .. } |
861 Self::Eip2930 { access_list: accesslist, .. } |
862 Self::Eip7702 { access_list: accesslist, .. } => Some(accesslist),
863 }
864 }
865
866 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
867 match self {
868 Self::Eip4844 { blob_versioned_hashes, .. } => Some(blob_versioned_hashes),
869 _ => None,
870 }
871 }
872
873 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
874 match self {
875 Self::Eip7702 { authorization_list, .. } => Some(authorization_list),
876 _ => None,
877 }
878 }
879}
880
881impl EthPoolTransaction for MockTransaction {
882 fn take_blob(&mut self) -> EthBlobTransactionSidecar {
883 match self {
884 Self::Eip4844 { sidecar, .. } => EthBlobTransactionSidecar::Present(sidecar.clone()),
885 _ => EthBlobTransactionSidecar::None,
886 }
887 }
888
889 fn try_into_pooled_eip4844(
890 self,
891 sidecar: Arc<BlobTransactionSidecarVariant>,
892 ) -> Option<Recovered<Self::Pooled>> {
893 let (tx, signer) = self.into_consensus().into_parts();
894 tx.try_into_pooled_eip4844(Arc::unwrap_or_clone(sidecar))
895 .map(|tx| tx.with_signer(signer))
896 .ok()
897 }
898
899 fn try_from_eip4844(
900 tx: Recovered<Self::Consensus>,
901 sidecar: BlobTransactionSidecarVariant,
902 ) -> Option<Self> {
903 let (tx, signer) = tx.into_parts();
904 tx.try_into_pooled_eip4844(sidecar)
905 .map(|tx| tx.with_signer(signer))
906 .ok()
907 .map(Self::from_pooled)
908 }
909
910 fn validate_blob(
911 &self,
912 _blob: &BlobTransactionSidecarVariant,
913 _settings: &KzgSettings,
914 ) -> Result<(), alloy_eips::eip4844::BlobTransactionValidationError> {
915 match &self {
916 Self::Eip4844 { .. } => Ok(()),
917 _ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
918 }
919 }
920}
921
922impl TryFrom<Recovered<TransactionSigned>> for MockTransaction {
923 type Error = TryFromRecoveredTransactionError;
924
925 fn try_from(tx: Recovered<TransactionSigned>) -> Result<Self, Self::Error> {
926 let sender = tx.signer();
927 let transaction = tx.into_inner();
928 let hash = *transaction.tx_hash();
929 let size = transaction.size();
930
931 match transaction.into_typed_transaction() {
932 Transaction::Legacy(TxLegacy {
933 chain_id,
934 nonce,
935 gas_price,
936 gas_limit,
937 to,
938 value,
939 input,
940 }) => Ok(Self::Legacy {
941 chain_id,
942 hash,
943 sender,
944 nonce,
945 gas_price,
946 gas_limit,
947 to,
948 value,
949 input,
950 size,
951 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
952 }),
953 Transaction::Eip2930(TxEip2930 {
954 chain_id,
955 nonce,
956 gas_price,
957 gas_limit,
958 to,
959 value,
960 input,
961 access_list,
962 }) => Ok(Self::Eip2930 {
963 chain_id,
964 hash,
965 sender,
966 nonce,
967 gas_price,
968 gas_limit,
969 to,
970 value,
971 input,
972 access_list,
973 size,
974 cost: U256::from(gas_limit) * U256::from(gas_price) + value,
975 }),
976 Transaction::Eip1559(TxEip1559 {
977 chain_id,
978 nonce,
979 gas_limit,
980 max_fee_per_gas,
981 max_priority_fee_per_gas,
982 to,
983 value,
984 input,
985 access_list,
986 }) => Ok(Self::Eip1559 {
987 chain_id,
988 hash,
989 sender,
990 nonce,
991 max_fee_per_gas,
992 max_priority_fee_per_gas,
993 gas_limit,
994 to,
995 value,
996 input,
997 access_list,
998 size,
999 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1000 }),
1001 Transaction::Eip4844(TxEip4844 {
1002 chain_id,
1003 nonce,
1004 gas_limit,
1005 max_fee_per_gas,
1006 max_priority_fee_per_gas,
1007 to,
1008 value,
1009 input,
1010 access_list,
1011 blob_versioned_hashes: _,
1012 max_fee_per_blob_gas,
1013 }) => Ok(Self::Eip4844 {
1014 chain_id,
1015 hash,
1016 sender,
1017 nonce,
1018 max_fee_per_gas,
1019 max_priority_fee_per_gas,
1020 max_fee_per_blob_gas,
1021 gas_limit,
1022 to,
1023 value,
1024 input,
1025 access_list,
1026 sidecar: BlobTransactionSidecarVariant::Eip4844(BlobTransactionSidecar::default()),
1027 blob_versioned_hashes: Default::default(),
1028 size,
1029 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1030 }),
1031 Transaction::Eip7702(TxEip7702 {
1032 chain_id,
1033 nonce,
1034 gas_limit,
1035 max_fee_per_gas,
1036 max_priority_fee_per_gas,
1037 to,
1038 value,
1039 access_list,
1040 authorization_list,
1041 input,
1042 }) => Ok(Self::Eip7702 {
1043 chain_id,
1044 hash,
1045 sender,
1046 nonce,
1047 max_fee_per_gas,
1048 max_priority_fee_per_gas,
1049 gas_limit,
1050 to,
1051 value,
1052 input,
1053 access_list,
1054 authorization_list,
1055 size,
1056 cost: U256::from(gas_limit) * U256::from(max_fee_per_gas) + value,
1057 }),
1058 }
1059 }
1060}
1061
1062impl TryFrom<Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>>
1063 for MockTransaction
1064{
1065 type Error = TryFromRecoveredTransactionError;
1066
1067 fn try_from(
1068 tx: Recovered<EthereumTxEnvelope<TxEip4844Variant<BlobTransactionSidecarVariant>>>,
1069 ) -> Result<Self, Self::Error> {
1070 let sender = tx.signer();
1071 let transaction = tx.into_inner();
1072 let hash = *transaction.tx_hash();
1073 let size = transaction.size();
1074
1075 match transaction {
1076 EthereumTxEnvelope::Legacy(signed_tx) => {
1077 let tx = signed_tx.strip_signature();
1078 Ok(Self::Legacy {
1079 chain_id: tx.chain_id,
1080 hash,
1081 sender,
1082 nonce: tx.nonce,
1083 gas_price: tx.gas_price,
1084 gas_limit: tx.gas_limit,
1085 to: tx.to,
1086 value: tx.value,
1087 input: tx.input,
1088 size,
1089 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1090 })
1091 }
1092 EthereumTxEnvelope::Eip2930(signed_tx) => {
1093 let tx = signed_tx.strip_signature();
1094 Ok(Self::Eip2930 {
1095 chain_id: tx.chain_id,
1096 hash,
1097 sender,
1098 nonce: tx.nonce,
1099 gas_price: tx.gas_price,
1100 gas_limit: tx.gas_limit,
1101 to: tx.to,
1102 value: tx.value,
1103 input: tx.input,
1104 access_list: tx.access_list,
1105 size,
1106 cost: U256::from(tx.gas_limit) * U256::from(tx.gas_price) + tx.value,
1107 })
1108 }
1109 EthereumTxEnvelope::Eip1559(signed_tx) => {
1110 let tx = signed_tx.strip_signature();
1111 Ok(Self::Eip1559 {
1112 chain_id: tx.chain_id,
1113 hash,
1114 sender,
1115 nonce: tx.nonce,
1116 max_fee_per_gas: tx.max_fee_per_gas,
1117 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1118 gas_limit: tx.gas_limit,
1119 to: tx.to,
1120 value: tx.value,
1121 input: tx.input,
1122 access_list: tx.access_list,
1123 size,
1124 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1125 })
1126 }
1127 EthereumTxEnvelope::Eip4844(signed_tx) => match signed_tx.tx() {
1128 TxEip4844Variant::TxEip4844(tx) => Ok(Self::Eip4844 {
1129 chain_id: tx.chain_id,
1130 hash,
1131 sender,
1132 nonce: tx.nonce,
1133 max_fee_per_gas: tx.max_fee_per_gas,
1134 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1135 max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
1136 gas_limit: tx.gas_limit,
1137 to: tx.to,
1138 value: tx.value,
1139 input: tx.input.clone(),
1140 access_list: tx.access_list.clone(),
1141 sidecar: BlobTransactionSidecarVariant::Eip4844(
1142 BlobTransactionSidecar::default(),
1143 ),
1144 blob_versioned_hashes: tx.blob_versioned_hashes.clone(),
1145 size,
1146 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1147 }),
1148 tx => Err(TryFromRecoveredTransactionError::UnsupportedTransactionType(tx.ty())),
1149 },
1150 EthereumTxEnvelope::Eip7702(signed_tx) => {
1151 let tx = signed_tx.strip_signature();
1152 Ok(Self::Eip7702 {
1153 chain_id: tx.chain_id,
1154 hash,
1155 sender,
1156 nonce: tx.nonce,
1157 max_fee_per_gas: tx.max_fee_per_gas,
1158 max_priority_fee_per_gas: tx.max_priority_fee_per_gas,
1159 gas_limit: tx.gas_limit,
1160 to: tx.to,
1161 value: tx.value,
1162 access_list: tx.access_list,
1163 authorization_list: tx.authorization_list,
1164 input: tx.input,
1165 size,
1166 cost: U256::from(tx.gas_limit) * U256::from(tx.max_fee_per_gas) + tx.value,
1167 })
1168 }
1169 }
1170 }
1171}
1172
1173impl From<Recovered<PooledTransactionVariant>> for MockTransaction {
1174 fn from(tx: Recovered<PooledTransactionVariant>) -> Self {
1175 let (tx, signer) = tx.into_parts();
1176 Recovered::<TransactionSigned>::new_unchecked(tx.into(), signer).try_into().expect(
1177 "Failed to convert from PooledTransactionsElementEcRecovered to MockTransaction",
1178 )
1179 }
1180}
1181
1182impl From<MockTransaction> for Recovered<TransactionSigned> {
1183 fn from(tx: MockTransaction) -> Self {
1184 let hash = *tx.hash();
1185 let sender = tx.sender();
1186 let tx = Transaction::from(tx);
1187 let tx: TransactionSigned =
1188 Signed::new_unchecked(tx, Signature::test_signature(), hash).into();
1189 Self::new_unchecked(tx, sender)
1190 }
1191}
1192
1193impl From<MockTransaction> for Transaction {
1194 fn from(mock: MockTransaction) -> Self {
1195 match mock {
1196 MockTransaction::Legacy {
1197 chain_id,
1198 nonce,
1199 gas_price,
1200 gas_limit,
1201 to,
1202 value,
1203 input,
1204 ..
1205 } => Self::Legacy(TxLegacy { chain_id, nonce, gas_price, gas_limit, to, value, input }),
1206 MockTransaction::Eip2930 {
1207 chain_id,
1208 nonce,
1209 gas_price,
1210 gas_limit,
1211 to,
1212 value,
1213 access_list,
1214 input,
1215 ..
1216 } => Self::Eip2930(TxEip2930 {
1217 chain_id,
1218 nonce,
1219 gas_price,
1220 gas_limit,
1221 to,
1222 value,
1223 access_list,
1224 input,
1225 }),
1226 MockTransaction::Eip1559 {
1227 chain_id,
1228 nonce,
1229 gas_limit,
1230 max_fee_per_gas,
1231 max_priority_fee_per_gas,
1232 to,
1233 value,
1234 access_list,
1235 input,
1236 ..
1237 } => Self::Eip1559(TxEip1559 {
1238 chain_id,
1239 nonce,
1240 gas_limit,
1241 max_fee_per_gas,
1242 max_priority_fee_per_gas,
1243 to,
1244 value,
1245 access_list,
1246 input,
1247 }),
1248 MockTransaction::Eip4844 {
1249 chain_id,
1250 nonce,
1251 gas_limit,
1252 max_fee_per_gas,
1253 max_priority_fee_per_gas,
1254 to,
1255 value,
1256 access_list,
1257 sidecar,
1258 max_fee_per_blob_gas,
1259 input,
1260 ..
1261 } => Self::Eip4844(TxEip4844 {
1262 chain_id,
1263 nonce,
1264 gas_limit,
1265 max_fee_per_gas,
1266 max_priority_fee_per_gas,
1267 to,
1268 value,
1269 access_list,
1270 blob_versioned_hashes: sidecar.versioned_hashes().collect(),
1271 max_fee_per_blob_gas,
1272 input,
1273 }),
1274 MockTransaction::Eip7702 {
1275 chain_id,
1276 nonce,
1277 gas_limit,
1278 max_fee_per_gas,
1279 max_priority_fee_per_gas,
1280 to,
1281 value,
1282 access_list,
1283 input,
1284 authorization_list,
1285 ..
1286 } => Self::Eip7702(TxEip7702 {
1287 chain_id,
1288 nonce,
1289 gas_limit,
1290 max_fee_per_gas,
1291 max_priority_fee_per_gas,
1292 to,
1293 value,
1294 access_list,
1295 authorization_list,
1296 input,
1297 }),
1298 }
1299 }
1300}
1301
1302#[cfg(any(test, feature = "arbitrary"))]
1303impl proptest::arbitrary::Arbitrary for MockTransaction {
1304 type Parameters = ();
1305 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
1306 use proptest::prelude::Strategy;
1307 use proptest_arbitrary_interop::arb;
1308
1309 arb::<(TransactionSigned, Address)>()
1310 .prop_map(|(signed_transaction, signer)| {
1311 Recovered::new_unchecked(signed_transaction, signer)
1312 .try_into()
1313 .expect("Failed to create an Arbitrary MockTransaction from a Recovered tx")
1314 })
1315 .boxed()
1316 }
1317
1318 type Strategy = proptest::strategy::BoxedStrategy<Self>;
1319}
1320
1321#[derive(Debug, Default)]
1323pub struct MockTransactionFactory {
1324 pub(crate) ids: SenderIdentifiers,
1325}
1326
1327impl MockTransactionFactory {
1330 pub fn tx_id(&mut self, tx: &MockTransaction) -> TransactionId {
1332 let sender = self.ids.sender_id_or_create(tx.sender());
1333 TransactionId::new(sender, *tx.get_nonce())
1334 }
1335
1336 pub fn validated(&mut self, transaction: MockTransaction) -> MockValidTx {
1338 self.validated_with_origin(TransactionOrigin::External, transaction)
1339 }
1340
1341 pub fn validated_arc(&mut self, transaction: MockTransaction) -> Arc<MockValidTx> {
1343 Arc::new(self.validated(transaction))
1344 }
1345
1346 pub fn validated_with_origin(
1348 &mut self,
1349 origin: TransactionOrigin,
1350 transaction: MockTransaction,
1351 ) -> MockValidTx {
1352 MockValidTx {
1353 propagate: false,
1354 transaction_id: self.tx_id(&transaction),
1355 transaction,
1356 timestamp: Instant::now(),
1357 origin,
1358 authority_ids: None,
1359 }
1360 }
1361
1362 pub fn create_legacy(&mut self) -> MockValidTx {
1364 self.validated(MockTransaction::legacy())
1365 }
1366
1367 pub fn create_eip1559(&mut self) -> MockValidTx {
1369 self.validated(MockTransaction::eip1559())
1370 }
1371
1372 pub fn create_eip4844(&mut self) -> MockValidTx {
1374 self.validated(MockTransaction::eip4844())
1375 }
1376}
1377
1378pub type MockOrdering = CoinbaseTipOrdering<MockTransaction>;
1380
1381#[derive(Debug, Clone)]
1384pub struct MockTransactionRatio {
1385 pub legacy_pct: u32,
1387 pub access_list_pct: u32,
1389 pub dynamic_fee_pct: u32,
1391 pub blob_pct: u32,
1393}
1394
1395impl MockTransactionRatio {
1396 pub fn new(legacy_pct: u32, access_list_pct: u32, dynamic_fee_pct: u32, blob_pct: u32) -> Self {
1402 let total = legacy_pct + access_list_pct + dynamic_fee_pct + blob_pct;
1403 assert_eq!(
1404 total,
1405 100,
1406 "percentages must sum up to 100, instead got legacy: {legacy_pct}, access_list: {access_list_pct}, dynamic_fee: {dynamic_fee_pct}, blob: {blob_pct}, total: {total}",
1407 );
1408
1409 Self { legacy_pct, access_list_pct, dynamic_fee_pct, blob_pct }
1410 }
1411
1412 pub fn weighted_index(&self) -> WeightedIndex<u32> {
1420 WeightedIndex::new([
1421 self.legacy_pct,
1422 self.access_list_pct,
1423 self.dynamic_fee_pct,
1424 self.blob_pct,
1425 ])
1426 .unwrap()
1427 }
1428}
1429
1430#[derive(Debug, Clone)]
1432pub struct MockFeeRange {
1433 pub gas_price: Uniform<u128>,
1435 pub priority_fee: Uniform<u128>,
1437 pub max_fee: Uniform<u128>,
1439 pub max_fee_blob: Uniform<u128>,
1441}
1442
1443impl MockFeeRange {
1444 pub fn new(
1449 gas_price: Range<u128>,
1450 priority_fee: Range<u128>,
1451 max_fee: Range<u128>,
1452 max_fee_blob: Range<u128>,
1453 ) -> Self {
1454 assert!(
1455 max_fee.start <= priority_fee.end,
1456 "max_fee_range should be strictly below the priority fee range"
1457 );
1458 Self {
1459 gas_price: gas_price.try_into().unwrap(),
1460 priority_fee: priority_fee.try_into().unwrap(),
1461 max_fee: max_fee.try_into().unwrap(),
1462 max_fee_blob: max_fee_blob.try_into().unwrap(),
1463 }
1464 }
1465
1466 pub fn sample_gas_price(&self, rng: &mut impl rand::Rng) -> u128 {
1469 self.gas_price.sample(rng)
1470 }
1471
1472 pub fn sample_priority_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1475 self.priority_fee.sample(rng)
1476 }
1477
1478 pub fn sample_max_fee(&self, rng: &mut impl rand::Rng) -> u128 {
1481 self.max_fee.sample(rng)
1482 }
1483
1484 pub fn sample_max_fee_blob(&self, rng: &mut impl rand::Rng) -> u128 {
1487 self.max_fee_blob.sample(rng)
1488 }
1489}
1490
1491#[derive(Debug, Clone)]
1493pub struct MockTransactionDistribution {
1494 transaction_ratio: MockTransactionRatio,
1496 gas_limit_range: Uniform<u64>,
1498 size_range: Uniform<usize>,
1500 fee_ranges: MockFeeRange,
1502}
1503
1504impl MockTransactionDistribution {
1505 pub fn new(
1507 transaction_ratio: MockTransactionRatio,
1508 fee_ranges: MockFeeRange,
1509 gas_limit_range: Range<u64>,
1510 size_range: Range<usize>,
1511 ) -> Self {
1512 Self {
1513 transaction_ratio,
1514 gas_limit_range: gas_limit_range.try_into().unwrap(),
1515 fee_ranges,
1516 size_range: size_range.try_into().unwrap(),
1517 }
1518 }
1519
1520 pub fn tx(&self, nonce: u64, rng: &mut impl rand::Rng) -> MockTransaction {
1522 let transaction_sample = self.transaction_ratio.weighted_index().sample(rng);
1523 let tx = match transaction_sample {
1524 0 => MockTransaction::legacy().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1525 1 => MockTransaction::eip2930().with_gas_price(self.fee_ranges.sample_gas_price(rng)),
1526 2 => MockTransaction::eip1559()
1527 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1528 .with_max_fee(self.fee_ranges.sample_max_fee(rng)),
1529 3 => MockTransaction::eip4844()
1530 .with_priority_fee(self.fee_ranges.sample_priority_fee(rng))
1531 .with_max_fee(self.fee_ranges.sample_max_fee(rng))
1532 .with_blob_fee(self.fee_ranges.sample_max_fee_blob(rng)),
1533 _ => unreachable!("unknown transaction type returned by the weighted index"),
1534 };
1535
1536 let size = self.size_range.sample(rng);
1537
1538 tx.with_nonce(nonce).with_gas_limit(self.gas_limit_range.sample(rng)).with_size(size)
1539 }
1540
1541 pub fn tx_set(
1545 &self,
1546 sender: Address,
1547 nonce_range: Range<u64>,
1548 rng: &mut impl rand::Rng,
1549 ) -> MockTransactionSet {
1550 let txs =
1551 nonce_range.map(|nonce| self.tx(nonce, rng).with_sender(sender)).collect::<Vec<_>>();
1552 MockTransactionSet::new(txs)
1553 }
1554
1555 pub fn tx_set_non_conflicting_types(
1561 &self,
1562 sender: Address,
1563 nonce_range: Range<u64>,
1564 rng: &mut impl rand::Rng,
1565 ) -> NonConflictingSetOutcome {
1566 let mut modified_distribution = self.clone();
1574 let first_tx = self.tx(nonce_range.start, rng);
1575
1576 if first_tx.is_eip4844() {
1579 modified_distribution.transaction_ratio = MockTransactionRatio {
1580 legacy_pct: 0,
1581 access_list_pct: 0,
1582 dynamic_fee_pct: 0,
1583 blob_pct: 100,
1584 };
1585
1586 NonConflictingSetOutcome::BlobsOnly(modified_distribution.tx_set(
1588 sender,
1589 nonce_range,
1590 rng,
1591 ))
1592 } else {
1593 let MockTransactionRatio { legacy_pct, access_list_pct, dynamic_fee_pct, .. } =
1594 modified_distribution.transaction_ratio;
1595
1596 let total_non_blob_weight: u32 = legacy_pct + access_list_pct + dynamic_fee_pct;
1598
1599 let new_weights: Vec<u32> = [legacy_pct, access_list_pct, dynamic_fee_pct]
1601 .into_iter()
1602 .map(|weight| weight * 100 / total_non_blob_weight)
1603 .collect();
1604
1605 let new_ratio = MockTransactionRatio {
1606 legacy_pct: new_weights[0],
1607 access_list_pct: new_weights[1],
1608 dynamic_fee_pct: new_weights[2],
1609 blob_pct: 0,
1610 };
1611
1612 modified_distribution.transaction_ratio = new_ratio;
1615
1616 NonConflictingSetOutcome::Mixed(modified_distribution.tx_set(sender, nonce_range, rng))
1618 }
1619 }
1620}
1621
1622#[derive(Debug, Clone)]
1625pub enum NonConflictingSetOutcome {
1626 BlobsOnly(MockTransactionSet),
1628 Mixed(MockTransactionSet),
1630}
1631
1632impl NonConflictingSetOutcome {
1633 pub fn into_inner(self) -> MockTransactionSet {
1635 match self {
1636 Self::BlobsOnly(set) | Self::Mixed(set) => set,
1637 }
1638 }
1639
1640 pub fn with_nonce_gaps(
1648 &mut self,
1649 gap_pct: u32,
1650 gap_range: Range<u64>,
1651 rng: &mut impl rand::Rng,
1652 ) {
1653 match self {
1654 Self::BlobsOnly(_) => {}
1655 Self::Mixed(set) => set.with_nonce_gaps(gap_pct, gap_range, rng),
1656 }
1657 }
1658}
1659
1660#[derive(Debug, Clone)]
1662pub struct MockTransactionSet {
1663 pub(crate) transactions: Vec<MockTransaction>,
1664}
1665
1666impl MockTransactionSet {
1667 const fn new(transactions: Vec<MockTransaction>) -> Self {
1669 Self { transactions }
1670 }
1671
1672 pub fn dependent(sender: Address, from_nonce: u64, tx_count: usize, tx_type: TxType) -> Self {
1679 let mut txs = Vec::with_capacity(tx_count);
1680 let mut curr_tx =
1681 MockTransaction::new_from_type(tx_type).with_nonce(from_nonce).with_sender(sender);
1682 for _ in 0..tx_count {
1683 txs.push(curr_tx.clone());
1684 curr_tx = curr_tx.next();
1685 }
1686
1687 Self::new(txs)
1688 }
1689
1690 pub fn sequential_transactions_by_sender(
1695 sender: Address,
1696 tx_count: usize,
1697 tx_type: TxType,
1698 ) -> Self {
1699 Self::dependent(sender, 0, tx_count, tx_type)
1700 }
1701
1702 pub fn with_nonce_gaps(
1716 &mut self,
1717 gap_pct: u32,
1718 gap_range: Range<u64>,
1719 rng: &mut impl rand::Rng,
1720 ) {
1721 assert!(gap_pct <= 100, "gap_pct must be between 0 and 100");
1722 assert!(gap_range.start >= 1, "gap_range must have a lower bound of at least one");
1723
1724 let mut prev_nonce = 0;
1725 for tx in &mut self.transactions {
1726 if rng.random_bool(gap_pct as f64 / 100.0) {
1727 prev_nonce += gap_range.start;
1728 } else {
1729 prev_nonce += 1;
1730 }
1731 tx.set_nonce(prev_nonce);
1732 }
1733 }
1734
1735 pub fn extend<T: IntoIterator<Item = MockTransaction>>(&mut self, txs: T) {
1737 self.transactions.extend(txs);
1738 }
1739
1740 pub fn into_vec(self) -> Vec<MockTransaction> {
1742 self.transactions
1743 }
1744
1745 pub fn iter(&self) -> impl Iterator<Item = &MockTransaction> {
1747 self.transactions.iter()
1748 }
1749
1750 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut MockTransaction> {
1752 self.transactions.iter_mut()
1753 }
1754}
1755
1756impl IntoIterator for MockTransactionSet {
1757 type Item = MockTransaction;
1758 type IntoIter = IntoIter<MockTransaction>;
1759
1760 fn into_iter(self) -> Self::IntoIter {
1761 self.transactions.into_iter()
1762 }
1763}
1764
1765#[test]
1766fn test_mock_priority() {
1767 use crate::TransactionOrdering;
1768
1769 let o = MockOrdering::default();
1770 let lo = MockTransaction::eip1559().with_gas_limit(100_000);
1771 let hi = lo.next().inc_price();
1772 assert!(o.priority(&hi, 0) > o.priority(&lo, 0));
1773}
1774
1775#[cfg(test)]
1776mod tests {
1777 use super::*;
1778 use alloy_consensus::Transaction;
1779 use alloy_primitives::U256;
1780
1781 #[test]
1782 fn test_mock_transaction_factory() {
1783 let mut factory = MockTransactionFactory::default();
1784
1785 let legacy = factory.create_legacy();
1787 assert_eq!(legacy.transaction.tx_type(), TxType::Legacy);
1788
1789 let eip1559 = factory.create_eip1559();
1791 assert_eq!(eip1559.transaction.tx_type(), TxType::Eip1559);
1792
1793 let eip4844 = factory.create_eip4844();
1795 assert_eq!(eip4844.transaction.tx_type(), TxType::Eip4844);
1796 }
1797
1798 #[test]
1799 fn test_mock_transaction_set() {
1800 let sender = Address::random();
1801 let nonce_start = 0u64;
1802 let count = 3;
1803
1804 let legacy_set = MockTransactionSet::dependent(sender, nonce_start, count, TxType::Legacy);
1806 assert_eq!(legacy_set.transactions.len(), count);
1807 for (idx, tx) in legacy_set.transactions.iter().enumerate() {
1808 assert_eq!(tx.tx_type(), TxType::Legacy);
1809 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1810 assert_eq!(tx.sender(), sender);
1811 }
1812
1813 let eip1559_set =
1815 MockTransactionSet::dependent(sender, nonce_start, count, TxType::Eip1559);
1816 assert_eq!(eip1559_set.transactions.len(), count);
1817 for (idx, tx) in eip1559_set.transactions.iter().enumerate() {
1818 assert_eq!(tx.tx_type(), TxType::Eip1559);
1819 assert_eq!(tx.nonce(), nonce_start + idx as u64);
1820 assert_eq!(tx.sender(), sender);
1821 }
1822 }
1823
1824 #[test]
1825 fn test_mock_transaction_modifications() {
1826 let tx = MockTransaction::eip1559();
1827
1828 let original_price = tx.get_gas_price();
1830 let tx_inc = tx.inc_price();
1831 assert!(tx_inc.get_gas_price() > original_price);
1832
1833 let original_limit = tx.gas_limit();
1835 let tx_inc = tx.inc_limit();
1836 assert!(tx_inc.gas_limit() > original_limit);
1837
1838 let original_nonce = tx.nonce();
1840 let tx_inc = tx.inc_nonce();
1841 assert_eq!(tx_inc.nonce(), original_nonce + 1);
1842 }
1843
1844 #[test]
1845 fn test_mock_transaction_cost() {
1846 let tx = MockTransaction::eip1559()
1847 .with_gas_limit(7_000)
1848 .with_max_fee(100)
1849 .with_value(U256::ZERO);
1850
1851 let expected_cost = U256::from(7_000u64) * U256::from(100u128) + U256::ZERO;
1853 assert_eq!(*tx.cost(), expected_cost);
1854 }
1855}