revm_context/
tx.rs

1//! This module contains [`TxEnv`] struct and implements [`Transaction`] trait for it.
2use crate::TransactionType;
3use context_interface::{
4    either::Either,
5    transaction::{
6        AccessList, AccessListItem, Authorization, RecoveredAuthority, RecoveredAuthorization,
7        SignedAuthorization, Transaction,
8    },
9};
10use core::fmt::Debug;
11use database_interface::{BENCH_CALLER, BENCH_TARGET};
12use primitives::{eip7825, Address, Bytes, TxKind, B256, U256};
13use std::{vec, vec::Vec};
14
15/// The Transaction Environment is a struct that contains all fields that can be found in all Ethereum transaction,
16/// including EIP-4844, EIP-7702, EIP-7873, etc.  It implements the [`Transaction`] trait, which is used inside the EVM to execute a transaction.
17///
18/// [`TxEnvBuilder`] builder is recommended way to create a new [`TxEnv`] as it will automatically
19/// set the transaction type based on the fields set.
20#[derive(Clone, Debug, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct TxEnv {
23    /// Transaction type
24    pub tx_type: u8,
25    /// Caller aka Author aka transaction signer
26    pub caller: Address,
27    /// The gas limit of the transaction.
28    pub gas_limit: u64,
29    /// The gas price of the transaction.
30    ///
31    /// For EIP-1559 transaction this represent max_gas_fee.
32    pub gas_price: u128,
33    /// The destination of the transaction
34    pub kind: TxKind,
35    /// The value sent to `transact_to`
36    pub value: U256,
37    /// The data of the transaction
38    pub data: Bytes,
39
40    /// The nonce of the transaction
41    pub nonce: u64,
42
43    /// The chain ID of the transaction
44    ///
45    /// Incorporated as part of the Spurious Dragon upgrade via [EIP-155].
46    ///
47    /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
48    pub chain_id: Option<u64>,
49
50    /// A list of addresses and storage keys that the transaction plans to access
51    ///
52    /// Added in [EIP-2930].
53    ///
54    /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930
55    pub access_list: AccessList,
56
57    /// The priority fee per gas
58    ///
59    /// Incorporated as part of the London upgrade via [EIP-1559].
60    ///
61    /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
62    pub gas_priority_fee: Option<u128>,
63
64    /// The list of blob versioned hashes
65    ///
66    /// Per EIP there should be at least one blob present if [`max_fee_per_blob_gas`][Self::max_fee_per_blob_gas] is [`Some`].
67    ///
68    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
69    ///
70    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
71    pub blob_hashes: Vec<B256>,
72
73    /// The max fee per blob gas
74    ///
75    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
76    ///
77    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
78    pub max_fee_per_blob_gas: u128,
79
80    /// List of authorizations
81    ///
82    /// `authorization_list` contains the signature that authorizes this
83    /// caller to place the code to signer account.
84    ///
85    /// Set EOA account code for one transaction via [EIP-7702].
86    ///
87    /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702
88    pub authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
89}
90
91impl Default for TxEnv {
92    fn default() -> Self {
93        Self::builder().build().unwrap()
94    }
95}
96
97/// Error type for deriving transaction type used as error in [`TxEnv::derive_tx_type`] function.
98#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub enum DeriveTxTypeError {
101    /// Missing target for EIP-4844
102    MissingTargetForEip4844,
103    /// Missing target for EIP-7702
104    MissingTargetForEip7702,
105    /// Missing target for EIP-7873
106    MissingTargetForEip7873,
107}
108
109impl TxEnv {
110    /// Creates a new TxEnv with benchmark-specific values.
111    pub fn new_bench() -> Self {
112        Self {
113            caller: BENCH_CALLER,
114            kind: TxKind::Call(BENCH_TARGET),
115            gas_limit: 1_000_000_000,
116            ..Default::default()
117        }
118    }
119
120    /// Derives tx type from transaction fields and sets it to `tx_type`.
121    /// Returns error in case some fields were not set correctly.
122    pub fn derive_tx_type(&mut self) -> Result<(), DeriveTxTypeError> {
123        if !self.access_list.0.is_empty() {
124            self.tx_type = TransactionType::Eip2930 as u8;
125        }
126
127        if self.gas_priority_fee.is_some() {
128            self.tx_type = TransactionType::Eip1559 as u8;
129        }
130
131        if !self.blob_hashes.is_empty() || self.max_fee_per_blob_gas > 0 {
132            if let TxKind::Call(_) = self.kind {
133                self.tx_type = TransactionType::Eip4844 as u8;
134                return Ok(());
135            } else {
136                return Err(DeriveTxTypeError::MissingTargetForEip4844);
137            }
138        }
139
140        if !self.authorization_list.is_empty() {
141            if let TxKind::Call(_) = self.kind {
142                self.tx_type = TransactionType::Eip7702 as u8;
143                return Ok(());
144            } else {
145                return Err(DeriveTxTypeError::MissingTargetForEip7702);
146            }
147        }
148        Ok(())
149    }
150
151    /// Insert a list of signed authorizations into the authorization list.
152    pub fn set_signed_authorization(&mut self, auth: Vec<SignedAuthorization>) {
153        self.authorization_list = auth.into_iter().map(Either::Left).collect();
154    }
155
156    /// Insert a list of recovered authorizations into the authorization list.
157    pub fn set_recovered_authorization(&mut self, auth: Vec<RecoveredAuthorization>) {
158        self.authorization_list = auth.into_iter().map(Either::Right).collect();
159    }
160}
161
162impl Transaction for TxEnv {
163    type AccessListItem<'a> = &'a AccessListItem;
164    type Authorization<'a> = &'a Either<SignedAuthorization, RecoveredAuthorization>;
165
166    fn tx_type(&self) -> u8 {
167        self.tx_type
168    }
169
170    fn kind(&self) -> TxKind {
171        self.kind
172    }
173
174    fn caller(&self) -> Address {
175        self.caller
176    }
177
178    fn gas_limit(&self) -> u64 {
179        self.gas_limit
180    }
181
182    fn gas_price(&self) -> u128 {
183        self.gas_price
184    }
185
186    fn value(&self) -> U256 {
187        self.value
188    }
189
190    fn nonce(&self) -> u64 {
191        self.nonce
192    }
193
194    fn chain_id(&self) -> Option<u64> {
195        self.chain_id
196    }
197
198    fn access_list(&self) -> Option<impl Iterator<Item = Self::AccessListItem<'_>>> {
199        Some(self.access_list.0.iter())
200    }
201
202    fn max_fee_per_gas(&self) -> u128 {
203        self.gas_price
204    }
205
206    fn max_fee_per_blob_gas(&self) -> u128 {
207        self.max_fee_per_blob_gas
208    }
209
210    fn authorization_list_len(&self) -> usize {
211        self.authorization_list.len()
212    }
213
214    fn authorization_list(&self) -> impl Iterator<Item = Self::Authorization<'_>> {
215        self.authorization_list.iter()
216    }
217
218    fn input(&self) -> &Bytes {
219        &self.data
220    }
221
222    fn blob_versioned_hashes(&self) -> &[B256] {
223        &self.blob_hashes
224    }
225
226    fn max_priority_fee_per_gas(&self) -> Option<u128> {
227        self.gas_priority_fee
228    }
229}
230
231/// Builder for constructing [`TxEnv`] instances
232#[derive(Default, Debug)]
233pub struct TxEnvBuilder {
234    tx_type: Option<u8>,
235    caller: Address,
236    gas_limit: u64,
237    gas_price: u128,
238    kind: TxKind,
239    value: U256,
240    data: Bytes,
241    nonce: u64,
242    chain_id: Option<u64>,
243    access_list: AccessList,
244    gas_priority_fee: Option<u128>,
245    blob_hashes: Vec<B256>,
246    max_fee_per_blob_gas: u128,
247    authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
248}
249
250impl TxEnvBuilder {
251    /// Create a new builder with default values
252    pub fn new() -> Self {
253        Self {
254            tx_type: None,
255            caller: Address::default(),
256            gas_limit: eip7825::TX_GAS_LIMIT_CAP,
257            gas_price: 0,
258            kind: TxKind::Call(Address::default()),
259            value: U256::ZERO,
260            data: Bytes::default(),
261            nonce: 0,
262            chain_id: Some(1), // Mainnet chain ID is 1
263            access_list: Default::default(),
264            gas_priority_fee: None,
265            blob_hashes: Vec::new(),
266            max_fee_per_blob_gas: 0,
267            authorization_list: Vec::new(),
268        }
269    }
270
271    /// Set the transaction type
272    pub fn tx_type(mut self, tx_type: Option<u8>) -> Self {
273        self.tx_type = tx_type;
274        self
275    }
276
277    /// Get the transaction type
278    pub fn get_tx_type(&self) -> Option<u8> {
279        self.tx_type
280    }
281
282    /// Set the caller address
283    pub fn caller(mut self, caller: Address) -> Self {
284        self.caller = caller;
285        self
286    }
287
288    /// Set the gas limit
289    pub fn gas_limit(mut self, gas_limit: u64) -> Self {
290        self.gas_limit = gas_limit;
291        self
292    }
293
294    /// Set the max fee per gas.
295    pub fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
296        self.gas_price = max_fee_per_gas;
297        self
298    }
299
300    /// Set the gas price
301    pub fn gas_price(mut self, gas_price: u128) -> Self {
302        self.gas_price = gas_price;
303        self
304    }
305
306    /// Set the transaction kind
307    pub fn kind(mut self, kind: TxKind) -> Self {
308        self.kind = kind;
309        self
310    }
311
312    /// Set the transaction kind to call
313    pub fn call(mut self, target: Address) -> Self {
314        self.kind = TxKind::Call(target);
315        self
316    }
317
318    /// Set the transaction kind to create
319    pub fn create(mut self) -> Self {
320        self.kind = TxKind::Create;
321        self
322    }
323
324    /// Set the transaction kind to create
325    pub fn to(self, target: Address) -> Self {
326        self.call(target)
327    }
328
329    /// Set the transaction value
330    pub fn value(mut self, value: U256) -> Self {
331        self.value = value;
332        self
333    }
334
335    /// Set the transaction data
336    pub fn data(mut self, data: Bytes) -> Self {
337        self.data = data;
338        self
339    }
340
341    /// Set the transaction nonce
342    pub fn nonce(mut self, nonce: u64) -> Self {
343        self.nonce = nonce;
344        self
345    }
346
347    /// Set the chain ID
348    pub fn chain_id(mut self, chain_id: Option<u64>) -> Self {
349        self.chain_id = chain_id;
350        self
351    }
352
353    /// Set the access list
354    pub fn access_list(mut self, access_list: AccessList) -> Self {
355        self.access_list = access_list;
356        self
357    }
358
359    /// Set the gas priority fee
360    pub fn gas_priority_fee(mut self, gas_priority_fee: Option<u128>) -> Self {
361        self.gas_priority_fee = gas_priority_fee;
362        self
363    }
364
365    /// Set the blob hashes
366    pub fn blob_hashes(mut self, blob_hashes: Vec<B256>) -> Self {
367        self.blob_hashes = blob_hashes;
368        self
369    }
370
371    /// Set the max fee per blob gas
372    pub fn max_fee_per_blob_gas(mut self, max_fee_per_blob_gas: u128) -> Self {
373        self.max_fee_per_blob_gas = max_fee_per_blob_gas;
374        self
375    }
376
377    /// Set the authorization list
378    pub fn authorization_list(
379        mut self,
380        authorization_list: Vec<Either<SignedAuthorization, RecoveredAuthorization>>,
381    ) -> Self {
382        self.authorization_list = authorization_list;
383        self
384    }
385
386    /// Insert a list of signed authorizations into the authorization list.
387    pub fn authorization_list_signed(mut self, auth: Vec<SignedAuthorization>) -> Self {
388        self.authorization_list = auth.into_iter().map(Either::Left).collect();
389        self
390    }
391
392    /// Insert a list of recovered authorizations into the authorization list.
393    pub fn authorization_list_recovered(mut self, auth: Vec<RecoveredAuthorization>) -> Self {
394        self.authorization_list = auth.into_iter().map(Either::Right).collect();
395        self
396    }
397
398    /// Build the final [`TxEnv`] with default values for missing fields.
399    pub fn build_fill(mut self) -> TxEnv {
400        if let Some(tx_type) = self.tx_type {
401            match TransactionType::from(tx_type) {
402                TransactionType::Legacy => {
403                    // do nothing
404                }
405                TransactionType::Eip2930 => {
406                    // do nothing, all fields are set. Access list can be empty.
407                }
408                TransactionType::Eip1559 => {
409                    // gas priority fee is required
410                    if self.gas_priority_fee.is_none() {
411                        self.gas_priority_fee = Some(0);
412                    }
413                }
414                TransactionType::Eip4844 => {
415                    // gas priority fee is required
416                    if self.gas_priority_fee.is_none() {
417                        self.gas_priority_fee = Some(0);
418                    }
419
420                    // blob hashes can be empty
421                    if self.blob_hashes.is_empty() {
422                        self.blob_hashes = vec![B256::default()];
423                    }
424
425                    // target is required
426                    if !self.kind.is_call() {
427                        self.kind = TxKind::Call(Address::default());
428                    }
429                }
430                TransactionType::Eip7702 => {
431                    // gas priority fee is required
432                    if self.gas_priority_fee.is_none() {
433                        self.gas_priority_fee = Some(0);
434                    }
435
436                    // authorization list can be empty
437                    if self.authorization_list.is_empty() {
438                        // add dummy authorization
439                        self.authorization_list =
440                            vec![Either::Right(RecoveredAuthorization::new_unchecked(
441                                Authorization {
442                                    chain_id: U256::from(self.chain_id.unwrap_or(1)),
443                                    address: self.caller,
444                                    nonce: self.nonce,
445                                },
446                                RecoveredAuthority::Invalid,
447                            ))];
448                    }
449
450                    // target is required
451                    if !self.kind.is_call() {
452                        self.kind = TxKind::Call(Address::default());
453                    }
454                }
455                TransactionType::Custom => {
456                    // do nothing
457                }
458            }
459        }
460
461        let mut tx = TxEnv {
462            tx_type: self.tx_type.unwrap_or(0),
463            caller: self.caller,
464            gas_limit: self.gas_limit,
465            gas_price: self.gas_price,
466            kind: self.kind,
467            value: self.value,
468            data: self.data,
469            nonce: self.nonce,
470            chain_id: self.chain_id,
471            access_list: self.access_list,
472            gas_priority_fee: self.gas_priority_fee,
473            blob_hashes: self.blob_hashes,
474            max_fee_per_blob_gas: self.max_fee_per_blob_gas,
475            authorization_list: self.authorization_list,
476        };
477
478        // if tx_type is not set, derive it from fields and fix errors.
479        if self.tx_type.is_none() {
480            match tx.derive_tx_type() {
481                Ok(_) => {}
482                Err(DeriveTxTypeError::MissingTargetForEip4844) => {
483                    tx.kind = TxKind::Call(Address::default());
484                }
485                Err(DeriveTxTypeError::MissingTargetForEip7702) => {
486                    tx.kind = TxKind::Call(Address::default());
487                }
488                Err(DeriveTxTypeError::MissingTargetForEip7873) => {
489                    tx.kind = TxKind::Call(Address::default());
490                }
491            }
492        }
493
494        tx
495    }
496
497    /// Build the final [`TxEnv`], returns error if some fields are wrongly set.
498    /// If it is fine to fill missing fields with default values, use [`TxEnvBuilder::build_fill`] instead.
499    pub fn build(self) -> Result<TxEnv, TxEnvBuildError> {
500        // if tx_type is set, check if all needed fields are set correctly.
501        if let Some(tx_type) = self.tx_type {
502            match TransactionType::from(tx_type) {
503                TransactionType::Legacy => {
504                    // do nothing
505                }
506                TransactionType::Eip2930 => {
507                    // do nothing, all fields are set. Access list can be empty.
508                }
509                TransactionType::Eip1559 => {
510                    // gas priority fee is required
511                    if self.gas_priority_fee.is_none() {
512                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
513                    }
514                }
515                TransactionType::Eip4844 => {
516                    // gas priority fee is required
517                    if self.gas_priority_fee.is_none() {
518                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
519                    }
520
521                    // blob hashes can be empty
522                    if self.blob_hashes.is_empty() {
523                        return Err(TxEnvBuildError::MissingBlobHashesForEip4844);
524                    }
525
526                    // target is required
527                    if !self.kind.is_call() {
528                        return Err(TxEnvBuildError::MissingTargetForEip4844);
529                    }
530                }
531                TransactionType::Eip7702 => {
532                    // gas priority fee is required
533                    if self.gas_priority_fee.is_none() {
534                        return Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559);
535                    }
536
537                    // authorization list can be empty
538                    if self.authorization_list.is_empty() {
539                        return Err(TxEnvBuildError::MissingAuthorizationListForEip7702);
540                    }
541
542                    // target is required
543                    if !self.kind.is_call() {
544                        return Err(DeriveTxTypeError::MissingTargetForEip4844.into());
545                    }
546                }
547                TransactionType::Custom => {
548                    // do nothing, custom transaction type is handled by the caller.
549                }
550            }
551        }
552
553        let mut tx = TxEnv {
554            tx_type: self.tx_type.unwrap_or(0),
555            caller: self.caller,
556            gas_limit: self.gas_limit,
557            gas_price: self.gas_price,
558            kind: self.kind,
559            value: self.value,
560            data: self.data,
561            nonce: self.nonce,
562            chain_id: self.chain_id,
563            access_list: self.access_list,
564            gas_priority_fee: self.gas_priority_fee,
565            blob_hashes: self.blob_hashes,
566            max_fee_per_blob_gas: self.max_fee_per_blob_gas,
567            authorization_list: self.authorization_list,
568        };
569
570        // Derive tx type from fields, if some fields are wrongly set it will return an error.
571        if self.tx_type.is_none() {
572            tx.derive_tx_type()?;
573        }
574
575        Ok(tx)
576    }
577}
578
579/// Error type for building [`TxEnv`]
580#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
581#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
582pub enum TxEnvBuildError {
583    /// Derive tx type error
584    DeriveErr(DeriveTxTypeError),
585    /// Missing priority fee for EIP-1559
586    MissingGasPriorityFeeForEip1559,
587    /// Missing blob hashes for EIP-4844
588    MissingBlobHashesForEip4844,
589    /// Missing authorization list for EIP-7702
590    MissingAuthorizationListForEip7702,
591    /// Missing target for EIP-4844
592    MissingTargetForEip4844,
593}
594
595impl From<DeriveTxTypeError> for TxEnvBuildError {
596    fn from(error: DeriveTxTypeError) -> Self {
597        TxEnvBuildError::DeriveErr(error)
598    }
599}
600
601impl TxEnv {
602    /// Create a new builder for constructing a [`TxEnv`]
603    pub fn builder() -> TxEnvBuilder {
604        TxEnvBuilder::new()
605    }
606
607    /// Create a new builder for constructing a [`TxEnv`] with benchmark-specific values.
608    pub fn builder_for_bench() -> TxEnvBuilder {
609        TxEnv::new_bench().modify()
610    }
611
612    /// Modify the [`TxEnv`] by using builder pattern.
613    pub fn modify(self) -> TxEnvBuilder {
614        let TxEnv {
615            tx_type,
616            caller,
617            gas_limit,
618            gas_price,
619            kind,
620            value,
621            data,
622            nonce,
623            chain_id,
624            access_list,
625            gas_priority_fee,
626            blob_hashes,
627            max_fee_per_blob_gas,
628            authorization_list,
629        } = self;
630
631        TxEnvBuilder::new()
632            .tx_type(Some(tx_type))
633            .caller(caller)
634            .gas_limit(gas_limit)
635            .gas_price(gas_price)
636            .kind(kind)
637            .value(value)
638            .data(data)
639            .nonce(nonce)
640            .chain_id(chain_id)
641            .access_list(access_list)
642            .gas_priority_fee(gas_priority_fee)
643            .blob_hashes(blob_hashes)
644            .max_fee_per_blob_gas(max_fee_per_blob_gas)
645            .authorization_list(authorization_list)
646    }
647}
648
649#[cfg(test)]
650mod tests {
651    use super::*;
652
653    fn effective_gas_setup(
654        tx_type: TransactionType,
655        gas_price: u128,
656        gas_priority_fee: Option<u128>,
657    ) -> u128 {
658        let tx = TxEnv {
659            tx_type: tx_type as u8,
660            gas_price,
661            gas_priority_fee,
662            ..Default::default()
663        };
664        let base_fee = 100;
665        tx.effective_gas_price(base_fee)
666    }
667
668    #[test]
669    fn test_tx_env_builder_build_valid_legacy() {
670        // Legacy transaction
671        let tx = TxEnvBuilder::new()
672            .tx_type(Some(0))
673            .caller(Address::from([1u8; 20]))
674            .gas_limit(21000)
675            .gas_price(20)
676            .kind(TxKind::Call(Address::from([2u8; 20])))
677            .value(U256::from(100))
678            .data(Bytes::from(vec![0x01, 0x02]))
679            .nonce(5)
680            .chain_id(Some(1))
681            .build()
682            .unwrap();
683
684        assert_eq!(tx.kind, TxKind::Call(Address::from([2u8; 20])));
685        assert_eq!(tx.caller, Address::from([1u8; 20]));
686        assert_eq!(tx.gas_limit, 21000);
687        assert_eq!(tx.gas_price, 20);
688        assert_eq!(tx.value, U256::from(100));
689        assert_eq!(tx.data, Bytes::from(vec![0x01, 0x02]));
690        assert_eq!(tx.nonce, 5);
691        assert_eq!(tx.chain_id, Some(1));
692        assert_eq!(tx.tx_type, TransactionType::Legacy);
693    }
694
695    #[test]
696    fn test_tx_env_builder_build_valid_eip2930() {
697        // EIP-2930 transaction with access list
698        let access_list = AccessList(vec![AccessListItem {
699            address: Address::from([3u8; 20]),
700            storage_keys: vec![B256::from([4u8; 32])],
701        }]);
702        let tx = TxEnvBuilder::new()
703            .tx_type(Some(1))
704            .caller(Address::from([1u8; 20]))
705            .gas_limit(50000)
706            .gas_price(25)
707            .kind(TxKind::Call(Address::from([2u8; 20])))
708            .access_list(access_list.clone())
709            .build()
710            .unwrap();
711
712        assert_eq!(tx.tx_type, TransactionType::Eip2930);
713        assert_eq!(tx.access_list, access_list);
714    }
715
716    #[test]
717    fn test_tx_env_builder_build_valid_eip1559() {
718        // EIP-1559 transaction
719        let tx = TxEnvBuilder::new()
720            .tx_type(Some(2))
721            .caller(Address::from([1u8; 20]))
722            .gas_limit(50000)
723            .gas_price(30)
724            .gas_priority_fee(Some(10))
725            .kind(TxKind::Call(Address::from([2u8; 20])))
726            .build()
727            .unwrap();
728
729        assert_eq!(tx.tx_type, TransactionType::Eip1559);
730        assert_eq!(tx.gas_priority_fee, Some(10));
731    }
732
733    #[test]
734    fn test_tx_env_builder_build_valid_eip4844() {
735        // EIP-4844 blob transaction
736        let blob_hashes = vec![B256::from([5u8; 32]), B256::from([6u8; 32])];
737        let tx = TxEnvBuilder::new()
738            .tx_type(Some(3))
739            .caller(Address::from([1u8; 20]))
740            .gas_limit(50000)
741            .gas_price(30)
742            .gas_priority_fee(Some(10))
743            .kind(TxKind::Call(Address::from([2u8; 20])))
744            .blob_hashes(blob_hashes.clone())
745            .max_fee_per_blob_gas(100)
746            .build()
747            .unwrap();
748
749        assert_eq!(tx.tx_type, TransactionType::Eip4844);
750        assert_eq!(tx.blob_hashes, blob_hashes);
751        assert_eq!(tx.max_fee_per_blob_gas, 100);
752    }
753
754    #[test]
755    fn test_tx_env_builder_build_valid_eip7702() {
756        // EIP-7702 EOA code transaction
757        let auth = RecoveredAuthorization::new_unchecked(
758            Authorization {
759                chain_id: U256::from(1),
760                nonce: 0,
761                address: Address::default(),
762            },
763            RecoveredAuthority::Valid(Address::default()),
764        );
765        let auth_list = vec![Either::Right(auth)];
766
767        let tx = TxEnvBuilder::new()
768            .tx_type(Some(4))
769            .caller(Address::from([1u8; 20]))
770            .gas_limit(50000)
771            .gas_price(30)
772            .gas_priority_fee(Some(10))
773            .kind(TxKind::Call(Address::from([2u8; 20])))
774            .authorization_list(auth_list.clone())
775            .build()
776            .unwrap();
777
778        assert_eq!(tx.tx_type, TransactionType::Eip7702);
779        assert_eq!(tx.authorization_list.len(), 1);
780    }
781
782    #[test]
783    fn test_tx_env_builder_build_create_transaction() {
784        // Contract creation transaction
785        let bytecode = Bytes::from(vec![0x60, 0x80, 0x60, 0x40]);
786        let tx = TxEnvBuilder::new()
787            .kind(TxKind::Create)
788            .data(bytecode.clone())
789            .gas_limit(100000)
790            .gas_price(20)
791            .build()
792            .unwrap();
793
794        assert_eq!(tx.kind, TxKind::Create);
795        assert_eq!(tx.data, bytecode);
796    }
797
798    #[test]
799    fn test_tx_env_builder_build_errors_eip1559_missing_priority_fee() {
800        // EIP-1559 without gas_priority_fee should fail
801        let result = TxEnvBuilder::new()
802            .tx_type(Some(2))
803            .caller(Address::from([1u8; 20]))
804            .gas_limit(50000)
805            .gas_price(30)
806            .kind(TxKind::Call(Address::from([2u8; 20])))
807            .build();
808
809        assert!(matches!(
810            result,
811            Err(TxEnvBuildError::MissingGasPriorityFeeForEip1559)
812        ));
813    }
814
815    #[test]
816    fn test_tx_env_builder_build_errors_eip4844_missing_blob_hashes() {
817        // EIP-4844 without blob hashes should fail
818        let result = TxEnvBuilder::new()
819            .tx_type(Some(3))
820            .gas_priority_fee(Some(10))
821            .kind(TxKind::Call(Address::from([2u8; 20])))
822            .build();
823
824        assert!(matches!(
825            result,
826            Err(TxEnvBuildError::MissingBlobHashesForEip4844)
827        ));
828    }
829
830    #[test]
831    fn test_tx_env_builder_build_errors_eip4844_not_call() {
832        // EIP-4844 with Create should fail
833        let result = TxEnvBuilder::new()
834            .tx_type(Some(3))
835            .gas_priority_fee(Some(10))
836            .blob_hashes(vec![B256::from([5u8; 32])])
837            .kind(TxKind::Create)
838            .build();
839
840        assert!(matches!(
841            result,
842            Err(TxEnvBuildError::MissingTargetForEip4844)
843        ));
844    }
845
846    #[test]
847    fn test_tx_env_builder_build_errors_eip7702_missing_auth_list() {
848        // EIP-7702 without authorization list should fail
849        let result = TxEnvBuilder::new()
850            .tx_type(Some(4))
851            .gas_priority_fee(Some(10))
852            .kind(TxKind::Call(Address::from([2u8; 20])))
853            .build();
854
855        assert!(matches!(
856            result,
857            Err(TxEnvBuildError::MissingAuthorizationListForEip7702)
858        ));
859    }
860
861    #[test]
862    fn test_tx_env_builder_build_errors_eip7702_not_call() {
863        // EIP-7702 with Create should fail
864        let auth = RecoveredAuthorization::new_unchecked(
865            Authorization {
866                chain_id: U256::from(1),
867                nonce: 0,
868                address: Address::default(),
869            },
870            RecoveredAuthority::Valid(Address::default()),
871        );
872        let result = TxEnvBuilder::new()
873            .tx_type(Some(4))
874            .gas_priority_fee(Some(10))
875            .authorization_list(vec![Either::Right(auth)])
876            .kind(TxKind::Create)
877            .build();
878
879        assert!(matches!(result, Err(TxEnvBuildError::DeriveErr(_))));
880    }
881
882    #[test]
883    fn test_tx_env_builder_build_fill_legacy() {
884        // Legacy transaction with build_fill
885        let tx = TxEnvBuilder::new()
886            .caller(Address::from([1u8; 20]))
887            .gas_limit(21000)
888            .gas_price(20)
889            .kind(TxKind::Call(Address::from([2u8; 20])))
890            .build_fill();
891
892        assert_eq!(tx.tx_type, TransactionType::Legacy);
893        assert_eq!(tx.gas_priority_fee, None);
894    }
895
896    #[test]
897    fn test_tx_env_builder_build_fill_eip1559_missing_priority_fee() {
898        // EIP-1559 without gas_priority_fee should be filled with 0
899        let tx = TxEnvBuilder::new()
900            .tx_type(Some(2))
901            .caller(Address::from([1u8; 20]))
902            .gas_limit(50000)
903            .gas_price(30)
904            .kind(TxKind::Call(Address::from([2u8; 20])))
905            .build_fill();
906
907        assert_eq!(tx.tx_type, TransactionType::Eip1559);
908        assert_eq!(tx.gas_priority_fee, Some(0));
909    }
910
911    #[test]
912    fn test_tx_env_builder_build_fill_eip4844_missing_blob_hashes() {
913        // EIP-4844 without blob hashes should add default blob hash
914        let tx = TxEnvBuilder::new()
915            .tx_type(Some(3))
916            .gas_priority_fee(Some(10))
917            .kind(TxKind::Call(Address::from([2u8; 20])))
918            .build_fill();
919
920        assert_eq!(tx.tx_type, TransactionType::Eip4844);
921        assert_eq!(tx.blob_hashes.len(), 1);
922        assert_eq!(tx.blob_hashes[0], B256::default());
923    }
924
925    #[test]
926    fn test_tx_env_builder_build_fill_eip4844_create_to_call() {
927        // EIP-4844 with Create should be converted to Call
928        let tx = TxEnvBuilder::new()
929            .tx_type(Some(3))
930            .gas_priority_fee(Some(10))
931            .blob_hashes(vec![B256::from([5u8; 32])])
932            .kind(TxKind::Create)
933            .build_fill();
934
935        assert_eq!(tx.tx_type, TransactionType::Eip4844);
936        assert_eq!(tx.kind, TxKind::Call(Address::default()));
937    }
938
939    #[test]
940    fn test_tx_env_builder_build_fill_eip7702_missing_auth_list() {
941        // EIP-7702 without authorization list should add dummy auth
942        let tx = TxEnvBuilder::new()
943            .tx_type(Some(4))
944            .gas_priority_fee(Some(10))
945            .kind(TxKind::Call(Address::from([2u8; 20])))
946            .build_fill();
947
948        assert_eq!(tx.tx_type, TransactionType::Eip7702);
949        assert_eq!(tx.authorization_list.len(), 1);
950    }
951
952    #[test]
953    fn test_tx_env_builder_build_fill_eip7702_create_to_call() {
954        // EIP-7702 with Create should be converted to Call
955        let auth = RecoveredAuthorization::new_unchecked(
956            Authorization {
957                chain_id: U256::from(1),
958                nonce: 0,
959                address: Address::default(),
960            },
961            RecoveredAuthority::Valid(Address::default()),
962        );
963        let tx = TxEnvBuilder::new()
964            .tx_type(Some(4))
965            .gas_priority_fee(Some(10))
966            .authorization_list(vec![Either::Right(auth)])
967            .kind(TxKind::Create)
968            .build_fill();
969
970        assert_eq!(tx.tx_type, TransactionType::Eip7702);
971        assert_eq!(tx.kind, TxKind::Call(Address::default()));
972    }
973
974    #[test]
975    fn test_tx_env_builder_derive_tx_type_legacy() {
976        // No special fields, should derive Legacy
977        let tx = TxEnvBuilder::new()
978            .caller(Address::from([1u8; 20]))
979            .gas_limit(21000)
980            .gas_price(20)
981            .build()
982            .unwrap();
983
984        assert_eq!(tx.tx_type, TransactionType::Legacy);
985    }
986
987    #[test]
988    fn test_tx_env_builder_derive_tx_type_eip2930() {
989        // Access list present, should derive EIP-2930
990        let access_list = AccessList(vec![AccessListItem {
991            address: Address::from([3u8; 20]),
992            storage_keys: vec![B256::from([4u8; 32])],
993        }]);
994        let tx = TxEnvBuilder::new()
995            .caller(Address::from([1u8; 20]))
996            .access_list(access_list)
997            .build()
998            .unwrap();
999
1000        assert_eq!(tx.tx_type, TransactionType::Eip2930);
1001    }
1002
1003    #[test]
1004    fn test_tx_env_builder_derive_tx_type_eip1559() {
1005        // Gas priority fee present, should derive EIP-1559
1006        let tx = TxEnvBuilder::new()
1007            .caller(Address::from([1u8; 20]))
1008            .gas_priority_fee(Some(10))
1009            .build()
1010            .unwrap();
1011
1012        assert_eq!(tx.tx_type, TransactionType::Eip1559);
1013    }
1014
1015    #[test]
1016    fn test_tx_env_builder_derive_tx_type_eip4844() {
1017        // Blob hashes present, should derive EIP-4844
1018        let tx = TxEnvBuilder::new()
1019            .caller(Address::from([1u8; 20]))
1020            .gas_priority_fee(Some(10))
1021            .blob_hashes(vec![B256::from([5u8; 32])])
1022            .kind(TxKind::Call(Address::from([2u8; 20])))
1023            .build()
1024            .unwrap();
1025
1026        assert_eq!(tx.tx_type, TransactionType::Eip4844);
1027    }
1028
1029    #[test]
1030    fn test_tx_env_builder_derive_tx_type_eip7702() {
1031        // Authorization list present, should derive EIP-7702
1032        let auth = RecoveredAuthorization::new_unchecked(
1033            Authorization {
1034                chain_id: U256::from(1),
1035                nonce: 0,
1036                address: Address::default(),
1037            },
1038            RecoveredAuthority::Valid(Address::default()),
1039        );
1040        let tx = TxEnvBuilder::new()
1041            .caller(Address::from([1u8; 20]))
1042            .gas_priority_fee(Some(10))
1043            .authorization_list(vec![Either::Right(auth)])
1044            .kind(TxKind::Call(Address::from([2u8; 20])))
1045            .build()
1046            .unwrap();
1047
1048        assert_eq!(tx.tx_type, TransactionType::Eip7702);
1049    }
1050
1051    #[test]
1052    fn test_tx_env_builder_custom_tx_type() {
1053        // Custom transaction type (0xFF)
1054        let tx = TxEnvBuilder::new()
1055            .tx_type(Some(0xFF))
1056            .caller(Address::from([1u8; 20]))
1057            .build()
1058            .unwrap();
1059
1060        assert_eq!(tx.tx_type, TransactionType::Custom);
1061    }
1062
1063    #[test]
1064    fn test_tx_env_builder_chain_methods() {
1065        // Test method chaining
1066        let tx = TxEnvBuilder::new()
1067            .caller(Address::from([1u8; 20]))
1068            .gas_limit(50000)
1069            .gas_price(25)
1070            .kind(TxKind::Call(Address::from([2u8; 20])))
1071            .value(U256::from(1000))
1072            .data(Bytes::from(vec![0x12, 0x34]))
1073            .nonce(10)
1074            .chain_id(Some(5))
1075            .access_list(AccessList(vec![AccessListItem {
1076                address: Address::from([3u8; 20]),
1077                storage_keys: vec![],
1078            }]))
1079            .gas_priority_fee(Some(5))
1080            .blob_hashes(vec![B256::from([7u8; 32])])
1081            .max_fee_per_blob_gas(200)
1082            .build_fill();
1083
1084        assert_eq!(tx.caller, Address::from([1u8; 20]));
1085        assert_eq!(tx.gas_limit, 50000);
1086        assert_eq!(tx.gas_price, 25);
1087        assert_eq!(tx.kind, TxKind::Call(Address::from([2u8; 20])));
1088        assert_eq!(tx.value, U256::from(1000));
1089        assert_eq!(tx.data, Bytes::from(vec![0x12, 0x34]));
1090        assert_eq!(tx.nonce, 10);
1091        assert_eq!(tx.chain_id, Some(5));
1092        assert_eq!(tx.access_list.len(), 1);
1093        assert_eq!(tx.gas_priority_fee, Some(5));
1094        assert_eq!(tx.blob_hashes.len(), 1);
1095        assert_eq!(tx.max_fee_per_blob_gas, 200);
1096    }
1097
1098    #[test]
1099    fn test_effective_gas_price() {
1100        assert_eq!(90, effective_gas_setup(TransactionType::Legacy, 90, None));
1101        assert_eq!(
1102            90,
1103            effective_gas_setup(TransactionType::Legacy, 90, Some(0))
1104        );
1105        assert_eq!(
1106            90,
1107            effective_gas_setup(TransactionType::Legacy, 90, Some(10))
1108        );
1109        assert_eq!(
1110            120,
1111            effective_gas_setup(TransactionType::Legacy, 120, Some(10))
1112        );
1113        assert_eq!(90, effective_gas_setup(TransactionType::Eip2930, 90, None));
1114        assert_eq!(
1115            90,
1116            effective_gas_setup(TransactionType::Eip2930, 90, Some(0))
1117        );
1118        assert_eq!(
1119            90,
1120            effective_gas_setup(TransactionType::Eip2930, 90, Some(10))
1121        );
1122        assert_eq!(
1123            120,
1124            effective_gas_setup(TransactionType::Eip2930, 120, Some(10))
1125        );
1126        assert_eq!(90, effective_gas_setup(TransactionType::Eip1559, 90, None));
1127        assert_eq!(
1128            90,
1129            effective_gas_setup(TransactionType::Eip1559, 90, Some(0))
1130        );
1131        assert_eq!(
1132            90,
1133            effective_gas_setup(TransactionType::Eip1559, 90, Some(10))
1134        );
1135        assert_eq!(
1136            110,
1137            effective_gas_setup(TransactionType::Eip1559, 120, Some(10))
1138        );
1139        assert_eq!(90, effective_gas_setup(TransactionType::Eip4844, 90, None));
1140        assert_eq!(
1141            90,
1142            effective_gas_setup(TransactionType::Eip4844, 90, Some(0))
1143        );
1144        assert_eq!(
1145            90,
1146            effective_gas_setup(TransactionType::Eip4844, 90, Some(10))
1147        );
1148        assert_eq!(
1149            110,
1150            effective_gas_setup(TransactionType::Eip4844, 120, Some(10))
1151        );
1152        assert_eq!(90, effective_gas_setup(TransactionType::Eip7702, 90, None));
1153        assert_eq!(
1154            90,
1155            effective_gas_setup(TransactionType::Eip7702, 90, Some(0))
1156        );
1157        assert_eq!(
1158            90,
1159            effective_gas_setup(TransactionType::Eip7702, 90, Some(10))
1160        );
1161        assert_eq!(
1162            110,
1163            effective_gas_setup(TransactionType::Eip7702, 120, Some(10))
1164        );
1165    }
1166}