Skip to main content

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