rtvm_primitives/
env.rs

1pub mod handler_cfg;
2
3pub use handler_cfg::{CfgEnvWithHandlerCfg, EnvWithHandlerCfg, HandlerCfg};
4
5use crate::{
6    calc_blob_gasprice, Account, Address, Bytes, HashMap, InvalidHeader, InvalidTransaction, Spec,
7    SpecId, B256, GAS_PER_BLOB, KECCAK_EMPTY, MAX_BLOB_NUMBER_PER_BLOCK, MAX_INITCODE_SIZE, U256,
8    VERSIONED_HASH_VERSION_KZG,
9};
10use core::cmp::{min, Ordering};
11use core::hash::Hash;
12use std::boxed::Box;
13use std::vec::Vec;
14
15/// EVM environment configuration.
16#[derive(Clone, Debug, Default, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub struct Env {
19    /// Configuration of the EVM itself.
20    pub cfg: CfgEnv,
21    /// Configuration of the block the transaction is in.
22    pub block: BlockEnv,
23    /// Configuration of the transaction that is being executed.
24    pub tx: TxEnv,
25}
26
27impl Env {
28    /// Resets environment to default values.
29    #[inline]
30    pub fn clear(&mut self) {
31        *self = Self::default();
32    }
33
34    /// Create boxed [Env].
35    #[inline]
36    pub fn boxed(cfg: CfgEnv, block: BlockEnv, tx: TxEnv) -> Box<Self> {
37        Box::new(Self { cfg, block, tx })
38    }
39
40    /// Calculates the effective gas price of the transaction.
41    #[inline]
42    pub fn effective_gas_price(&self) -> U256 {
43        if let Some(priority_fee) = self.tx.gas_priority_fee {
44            min(self.tx.gas_price, self.block.basefee + priority_fee)
45        } else {
46            self.tx.gas_price
47        }
48    }
49
50    /// Calculates the [EIP-4844] `data_fee` of the transaction.
51    ///
52    /// Returns `None` if `Cancun` is not enabled. This is enforced in [`Env::validate_block_env`].
53    ///
54    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
55    #[inline]
56    pub fn calc_data_fee(&self) -> Option<U256> {
57        self.block.get_blob_gasprice().map(|blob_gas_price| {
58            U256::from(blob_gas_price).saturating_mul(U256::from(self.tx.get_total_blob_gas()))
59        })
60    }
61
62    /// Calculates the maximum [EIP-4844] `data_fee` of the transaction.
63    ///
64    /// This is used for ensuring that the user has at least enough funds to pay the
65    /// `max_fee_per_blob_gas * total_blob_gas`, on top of regular gas costs.
66    ///
67    /// See EIP-4844:
68    /// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md#execution-layer-validation>
69    pub fn calc_max_data_fee(&self) -> Option<U256> {
70        self.tx.max_fee_per_blob_gas.map(|max_fee_per_blob_gas| {
71            max_fee_per_blob_gas.saturating_mul(U256::from(self.tx.get_total_blob_gas()))
72        })
73    }
74
75    /// Validate the block environment.
76    #[inline]
77    pub fn validate_block_env<SPEC: Spec>(&self) -> Result<(), InvalidHeader> {
78        // `prevrandao` is required for the merge
79        if SPEC::enabled(SpecId::MERGE) && self.block.prevrandao.is_none() {
80            return Err(InvalidHeader::PrevrandaoNotSet);
81        }
82        // `excess_blob_gas` is required for Cancun
83        if SPEC::enabled(SpecId::CANCUN) && self.block.blob_excess_gas_and_price.is_none() {
84            return Err(InvalidHeader::ExcessBlobGasNotSet);
85        }
86        Ok(())
87    }
88
89    /// Validate transaction data that is set inside ENV and return error if something is wrong.
90    ///
91    /// Return initial spend gas (Gas needed to execute transaction).
92    #[inline]
93    pub fn validate_tx<SPEC: Spec>(&self) -> Result<(), InvalidTransaction> {
94        // BASEFEE tx check
95        if SPEC::enabled(SpecId::LONDON) {
96            if let Some(priority_fee) = self.tx.gas_priority_fee {
97                if priority_fee > self.tx.gas_price {
98                    // or gas_max_fee for eip1559
99                    return Err(InvalidTransaction::PriorityFeeGreaterThanMaxFee);
100                }
101            }
102
103            // check minimal cost against basefee
104            if !self.cfg.is_base_fee_check_disabled()
105                && self.effective_gas_price() < self.block.basefee
106            {
107                return Err(InvalidTransaction::GasPriceLessThanBasefee);
108            }
109        }
110
111        // Check if gas_limit is more than block_gas_limit
112        if !self.cfg.is_block_gas_limit_disabled()
113            && U256::from(self.tx.gas_limit) > self.block.gas_limit
114        {
115            return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
116        }
117
118        // EIP-3860: Limit and meter initcode
119        if SPEC::enabled(SpecId::SHANGHAI) && self.tx.transact_to.is_create() {
120            let max_initcode_size = self
121                .cfg
122                .limit_contract_code_size
123                .map(|limit| limit.saturating_mul(2))
124                .unwrap_or(MAX_INITCODE_SIZE);
125            if self.tx.data.len() > max_initcode_size {
126                return Err(InvalidTransaction::CreateInitCodeSizeLimit);
127            }
128        }
129
130        // Check if the transaction's chain id is correct
131        if let Some(tx_chain_id) = self.tx.chain_id {
132            if tx_chain_id != self.cfg.chain_id {
133                return Err(InvalidTransaction::InvalidChainId);
134            }
135        }
136
137        // Check that access list is empty for transactions before BERLIN
138        if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() {
139            return Err(InvalidTransaction::AccessListNotSupported);
140        }
141
142        // - For CANCUN and later, check that the gas price is not more than the tx max
143        // - For before CANCUN, check that `blob_hashes` and `max_fee_per_blob_gas` are empty / not set
144        if SPEC::enabled(SpecId::CANCUN) {
145            // Presence of max_fee_per_blob_gas means that this is blob transaction.
146            if let Some(max) = self.tx.max_fee_per_blob_gas {
147                // ensure that the user was willing to at least pay the current blob gasprice
148                let price = self.block.get_blob_gasprice().expect("already checked");
149                if U256::from(price) > max {
150                    return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
151                }
152
153                // there must be at least one blob
154                if self.tx.blob_hashes.is_empty() {
155                    return Err(InvalidTransaction::EmptyBlobs);
156                }
157
158                // The field `to` deviates slightly from the semantics with the exception
159                // that it MUST NOT be nil and therefore must always represent
160                // a 20-byte address. This means that blob transactions cannot
161                // have the form of a create transaction.
162                if self.tx.transact_to.is_create() {
163                    return Err(InvalidTransaction::BlobCreateTransaction);
164                }
165
166                // all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
167                for blob in self.tx.blob_hashes.iter() {
168                    if blob[0] != VERSIONED_HASH_VERSION_KZG {
169                        return Err(InvalidTransaction::BlobVersionNotSupported);
170                    }
171                }
172
173                // ensure the total blob gas spent is at most equal to the limit
174                // assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
175                if self.tx.blob_hashes.len() > MAX_BLOB_NUMBER_PER_BLOCK as usize {
176                    return Err(InvalidTransaction::TooManyBlobs);
177                }
178            }
179        } else {
180            if !self.tx.blob_hashes.is_empty() {
181                return Err(InvalidTransaction::BlobVersionedHashesNotSupported);
182            }
183            if self.tx.max_fee_per_blob_gas.is_some() {
184                return Err(InvalidTransaction::MaxFeePerBlobGasNotSupported);
185            }
186        }
187
188        if SPEC::enabled(SpecId::PRAGUE) {
189            if !self.tx.eof_initcodes.is_empty() {
190                // If initcode is set other fields must be empty
191                if !self.tx.blob_hashes.is_empty() {
192                    return Err(InvalidTransaction::BlobVersionedHashesNotSupported);
193                }
194                // EOF Create tx extends EIP-1559 tx. It must have max_fee_per_blob_gas
195                if self.tx.max_fee_per_blob_gas.is_some() {
196                    return Err(InvalidTransaction::MaxFeePerBlobGasNotSupported);
197                }
198                // EOF Create must have a to address
199                if matches!(self.tx.transact_to, TransactTo::Call(_)) {
200                    return Err(InvalidTransaction::EofCrateShouldHaveToAddress);
201                }
202            } else {
203                // If initcode is set check its bounds.
204                if self.tx.eof_initcodes.len() > 256 {
205                    return Err(InvalidTransaction::EofInitcodesNumberLimit);
206                }
207                if self
208                    .tx
209                    .eof_initcodes_hashed
210                    .iter()
211                    .any(|(_, i)| i.len() >= MAX_INITCODE_SIZE)
212                {
213                    return Err(InvalidTransaction::EofInitcodesSizeLimit);
214                }
215            }
216        } else {
217            // Initcode set when not supported.
218            if !self.tx.eof_initcodes.is_empty() {
219                return Err(InvalidTransaction::EofInitcodesNotSupported);
220            }
221        }
222
223        Ok(())
224    }
225
226    /// Validate transaction against state.
227    #[inline]
228    pub fn validate_tx_against_state<SPEC: Spec>(
229        &self,
230        account: &mut Account,
231    ) -> Result<(), InvalidTransaction> {
232        // EIP-3607: Reject transactions from senders with deployed code
233        // This EIP is introduced after london but there was no collision in past
234        // so we can leave it enabled always
235        if !self.cfg.is_eip3607_disabled() && account.info.code_hash != KECCAK_EMPTY {
236            return Err(InvalidTransaction::RejectCallerWithCode);
237        }
238
239        // Check that the transaction's nonce is correct
240        if let Some(tx) = self.tx.nonce {
241            let state = account.info.nonce;
242            match tx.cmp(&state) {
243                Ordering::Greater => {
244                    return Err(InvalidTransaction::NonceTooHigh { tx, state });
245                }
246                Ordering::Less => {
247                    return Err(InvalidTransaction::NonceTooLow { tx, state });
248                }
249                _ => {}
250            }
251        }
252
253        let mut balance_check = U256::from(self.tx.gas_limit)
254            .checked_mul(self.tx.gas_price)
255            .and_then(|gas_cost| gas_cost.checked_add(self.tx.value))
256            .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
257
258        if SPEC::enabled(SpecId::CANCUN) {
259            // if the tx is not a blob tx, this will be None, so we add zero
260            let data_fee = self.calc_max_data_fee().unwrap_or_default();
261            balance_check = balance_check
262                .checked_add(U256::from(data_fee))
263                .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
264        }
265
266        // Check if account has enough balance for gas_limit*gas_price and value transfer.
267        // Transfer will be done inside `*_inner` functions.
268        if balance_check > account.info.balance {
269            if self.cfg.is_balance_check_disabled() {
270                // Add transaction cost to balance to ensure execution doesn't fail.
271                account.info.balance = balance_check;
272            } else {
273                return Err(InvalidTransaction::LackOfFundForMaxFee {
274                    fee: Box::new(balance_check),
275                    balance: Box::new(account.info.balance),
276                });
277            }
278        }
279
280        Ok(())
281    }
282}
283
284/// EVM configuration.
285#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
286#[derive(Clone, Debug, Eq, PartialEq)]
287#[non_exhaustive]
288pub struct CfgEnv {
289    /// Chain ID of the EVM, it will be compared to the transaction's Chain ID.
290    /// Chain ID is introduced EIP-155
291    pub chain_id: u64,
292    /// KZG Settings for point evaluation precompile. By default, this is loaded from the ethereum mainnet trusted setup.
293    #[cfg(feature = "c-kzg")]
294    #[cfg_attr(feature = "serde", serde(skip))]
295    pub kzg_settings: crate::kzg::EnvKzgSettings,
296    /// Bytecode that is created with CREATE/CREATE2 is by default analysed and jumptable is created.
297    /// This is very beneficial for testing and speeds up execution of that bytecode if called multiple times.
298    ///
299    /// Default: Analyse
300    pub perf_analyse_created_bytecodes: AnalysisKind,
301    /// If some it will effects EIP-170: Contract code size limit. Useful to increase this because of tests.
302    /// By default it is 0x6000 (~25kb).
303    pub limit_contract_code_size: Option<usize>,
304    /// A hard memory limit in bytes beyond which [crate::result::OutOfGasError::Memory] cannot be resized.
305    ///
306    /// In cases where the gas limit may be extraordinarily high, it is recommended to set this to
307    /// a sane value to prevent memory allocation panics. Defaults to `2^32 - 1` bytes per
308    /// EIP-1985.
309    #[cfg(feature = "memory_limit")]
310    pub memory_limit: u64,
311    /// Skip balance checks if true. Adds transaction cost to balance to ensure execution doesn't fail.
312    #[cfg(feature = "optional_balance_check")]
313    pub disable_balance_check: bool,
314    /// There are use cases where it's allowed to provide a gas limit that's higher than a block's gas limit. To that
315    /// end, you can disable the block gas limit validation.
316    /// By default, it is set to `false`.
317    #[cfg(feature = "optional_block_gas_limit")]
318    pub disable_block_gas_limit: bool,
319    /// EIP-3607 rejects transactions from senders with deployed code. In development, it can be desirable to simulate
320    /// calls from contracts, which this setting allows.
321    /// By default, it is set to `false`.
322    #[cfg(feature = "optional_eip3607")]
323    pub disable_eip3607: bool,
324    /// Disables all gas refunds. This is useful when using chains that have gas refunds disabled e.g. Avalanche.
325    /// Reasoning behind removing gas refunds can be found in EIP-3298.
326    /// By default, it is set to `false`.
327    #[cfg(feature = "optional_gas_refund")]
328    pub disable_gas_refund: bool,
329    /// Disables base fee checks for EIP-1559 transactions.
330    /// This is useful for testing method calls with zero gas price.
331    /// By default, it is set to `false`.
332    #[cfg(feature = "optional_no_base_fee")]
333    pub disable_base_fee: bool,
334    /// Disables the payout of the reward to the beneficiary.
335    /// By default, it is set to `false`.
336    #[cfg(feature = "optional_beneficiary_reward")]
337    pub disable_beneficiary_reward: bool,
338}
339
340impl CfgEnv {
341    pub fn with_chain_id(mut self, chain_id: u64) -> Self {
342        self.chain_id = chain_id;
343        self
344    }
345
346    #[cfg(feature = "optional_eip3607")]
347    pub fn is_eip3607_disabled(&self) -> bool {
348        self.disable_eip3607
349    }
350
351    #[cfg(not(feature = "optional_eip3607"))]
352    pub fn is_eip3607_disabled(&self) -> bool {
353        false
354    }
355
356    #[cfg(feature = "optional_balance_check")]
357    pub fn is_balance_check_disabled(&self) -> bool {
358        self.disable_balance_check
359    }
360
361    #[cfg(not(feature = "optional_balance_check"))]
362    pub fn is_balance_check_disabled(&self) -> bool {
363        false
364    }
365
366    #[cfg(feature = "optional_gas_refund")]
367    pub fn is_gas_refund_disabled(&self) -> bool {
368        self.disable_gas_refund
369    }
370
371    #[cfg(not(feature = "optional_gas_refund"))]
372    pub fn is_gas_refund_disabled(&self) -> bool {
373        false
374    }
375
376    #[cfg(feature = "optional_no_base_fee")]
377    pub fn is_base_fee_check_disabled(&self) -> bool {
378        self.disable_base_fee
379    }
380
381    #[cfg(not(feature = "optional_no_base_fee"))]
382    pub fn is_base_fee_check_disabled(&self) -> bool {
383        false
384    }
385
386    #[cfg(feature = "optional_block_gas_limit")]
387    pub fn is_block_gas_limit_disabled(&self) -> bool {
388        self.disable_block_gas_limit
389    }
390
391    #[cfg(not(feature = "optional_block_gas_limit"))]
392    pub fn is_block_gas_limit_disabled(&self) -> bool {
393        false
394    }
395
396    #[cfg(feature = "optional_beneficiary_reward")]
397    pub fn is_beneficiary_reward_disabled(&self) -> bool {
398        self.disable_beneficiary_reward
399    }
400
401    #[cfg(not(feature = "optional_beneficiary_reward"))]
402    pub fn is_beneficiary_reward_disabled(&self) -> bool {
403        false
404    }
405}
406
407impl Default for CfgEnv {
408    fn default() -> Self {
409        Self {
410            chain_id: 1,
411            perf_analyse_created_bytecodes: AnalysisKind::default(),
412            limit_contract_code_size: None,
413            #[cfg(feature = "c-kzg")]
414            kzg_settings: crate::kzg::EnvKzgSettings::Default,
415            #[cfg(feature = "memory_limit")]
416            memory_limit: (1 << 32) - 1,
417            #[cfg(feature = "optional_balance_check")]
418            disable_balance_check: false,
419            #[cfg(feature = "optional_block_gas_limit")]
420            disable_block_gas_limit: false,
421            #[cfg(feature = "optional_eip3607")]
422            disable_eip3607: false,
423            #[cfg(feature = "optional_gas_refund")]
424            disable_gas_refund: false,
425            #[cfg(feature = "optional_no_base_fee")]
426            disable_base_fee: false,
427            #[cfg(feature = "optional_beneficiary_reward")]
428            disable_beneficiary_reward: false,
429        }
430    }
431}
432
433/// The block environment.
434#[derive(Clone, Debug, PartialEq, Eq, Hash)]
435#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
436pub struct BlockEnv {
437    /// The number of ancestor blocks of this block (block height).
438    pub number: U256,
439    /// Coinbase or miner or address that created and signed the block.
440    ///
441    /// This is the receiver address of all the gas spent in the block.
442    pub coinbase: Address,
443
444    /// The timestamp of the block in seconds since the UNIX epoch.
445    pub timestamp: U256,
446    /// The gas limit of the block.
447    pub gas_limit: U256,
448    /// The base fee per gas, added in the London upgrade with [EIP-1559].
449    ///
450    /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
451    pub basefee: U256,
452    /// The difficulty of the block.
453    ///
454    /// Unused after the Paris (AKA the merge) upgrade, and replaced by `prevrandao`.
455    pub difficulty: U256,
456    /// The output of the randomness beacon provided by the beacon chain.
457    ///
458    /// Replaces `difficulty` after the Paris (AKA the merge) upgrade with [EIP-4399].
459    ///
460    /// NOTE: `prevrandao` can be found in a block in place of `mix_hash`.
461    ///
462    /// [EIP-4399]: https://eips.ethereum.org/EIPS/eip-4399
463    pub prevrandao: Option<B256>,
464    /// Excess blob gas and blob gasprice.
465    /// See also [`crate::calc_excess_blob_gas`]
466    /// and [`calc_blob_gasprice`].
467    ///
468    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
469    ///
470    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
471    pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>,
472}
473
474impl BlockEnv {
475    /// Takes `blob_excess_gas` saves it inside env
476    /// and calculates `blob_fee` with [`BlobExcessGasAndPrice`].
477    pub fn set_blob_excess_gas_and_price(&mut self, excess_blob_gas: u64) {
478        self.blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(excess_blob_gas));
479    }
480    /// See [EIP-4844] and [`crate::calc_blob_gasprice`].
481    ///
482    /// Returns `None` if `Cancun` is not enabled. This is enforced in [`Env::validate_block_env`].
483    ///
484    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
485    #[inline]
486    pub fn get_blob_gasprice(&self) -> Option<u128> {
487        self.blob_excess_gas_and_price
488            .as_ref()
489            .map(|a| a.blob_gasprice)
490    }
491
492    /// Return `blob_excess_gas` header field. See [EIP-4844].
493    ///
494    /// Returns `None` if `Cancun` is not enabled. This is enforced in [`Env::validate_block_env`].
495    ///
496    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
497    #[inline]
498    pub fn get_blob_excess_gas(&self) -> Option<u64> {
499        self.blob_excess_gas_and_price
500            .as_ref()
501            .map(|a| a.excess_blob_gas)
502    }
503
504    /// Clears environment and resets fields to default values.
505    #[inline]
506    pub fn clear(&mut self) {
507        *self = Self::default();
508    }
509}
510
511impl Default for BlockEnv {
512    fn default() -> Self {
513        Self {
514            number: U256::ZERO,
515            coinbase: Address::ZERO,
516            timestamp: U256::from(1),
517            gas_limit: U256::MAX,
518            basefee: U256::ZERO,
519            difficulty: U256::ZERO,
520            prevrandao: Some(B256::ZERO),
521            blob_excess_gas_and_price: Some(BlobExcessGasAndPrice::new(0)),
522        }
523    }
524}
525
526/// The transaction environment.
527#[derive(Clone, Debug, PartialEq, Eq)]
528#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
529pub struct TxEnv {
530    /// Caller aka Author aka transaction signer.
531    pub caller: Address,
532    /// The gas limit of the transaction.
533    pub gas_limit: u64,
534    /// The gas price of the transaction.
535    pub gas_price: U256,
536    /// The destination of the transaction.
537    pub transact_to: TransactTo,
538    /// The value sent to `transact_to`.
539    pub value: U256,
540    /// The data of the transaction.
541    pub data: Bytes,
542    /// The nonce of the transaction.
543    ///
544    /// Caution: If set to `None`, then nonce validation against the account's nonce is skipped: [InvalidTransaction::NonceTooHigh] and [InvalidTransaction::NonceTooLow]
545    pub nonce: Option<u64>,
546
547    /// The chain ID of the transaction. If set to `None`, no checks are performed.
548    ///
549    /// Incorporated as part of the Spurious Dragon upgrade via [EIP-155].
550    ///
551    /// [EIP-155]: https://eips.ethereum.org/EIPS/eip-155
552    pub chain_id: Option<u64>,
553
554    /// A list of addresses and storage keys that the transaction plans to access.
555    ///
556    /// Added in [EIP-2930].
557    ///
558    /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930
559    pub access_list: Vec<(Address, Vec<U256>)>,
560
561    /// The priority fee per gas.
562    ///
563    /// Incorporated as part of the London upgrade via [EIP-1559].
564    ///
565    /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
566    pub gas_priority_fee: Option<U256>,
567
568    /// The list of blob versioned hashes. Per EIP there should be at least
569    /// one blob present if [`Self::max_fee_per_blob_gas`] is `Some`.
570    ///
571    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
572    ///
573    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
574    pub blob_hashes: Vec<B256>,
575
576    /// The max fee per blob gas.
577    ///
578    /// Incorporated as part of the Cancun upgrade via [EIP-4844].
579    ///
580    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
581    pub max_fee_per_blob_gas: Option<U256>,
582
583    /// EOF Initcodes for EOF CREATE transaction
584    ///
585    /// Incorporated as part of the Prague upgrade via [EOF]
586    ///
587    /// [EOF]: https://eips.ethereum.org/EIPS/eip-4844
588    pub eof_initcodes: Vec<Bytes>,
589
590    /// Internal Temporary field that stores the hashes of the EOF initcodes.
591    ///
592    /// Those are always cleared after the transaction is executed.
593    /// And calculated/overwritten every time transaction starts.
594    /// They are calculated from the [`Self::eof_initcodes`] field.
595    pub eof_initcodes_hashed: HashMap<B256, Bytes>,
596
597    #[cfg_attr(feature = "serde", serde(flatten))]
598    #[cfg(feature = "optimism")]
599    /// Optimism fields.
600    pub optimism: OptimismFields,
601}
602
603pub enum TxType {
604    Legacy,
605    Eip1559,
606    BlobTx,
607    EofCreate,
608}
609
610impl TxEnv {
611    /// See [EIP-4844], [`Env::calc_data_fee`], and [`Env::calc_max_data_fee`].
612    ///
613    /// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
614    #[inline]
615    pub fn get_total_blob_gas(&self) -> u64 {
616        GAS_PER_BLOB * self.blob_hashes.len() as u64
617    }
618
619    /// Clears environment and resets fields to default values.
620    #[inline]
621    pub fn clear(&mut self) {
622        *self = Self::default();
623    }
624}
625
626impl Default for TxEnv {
627    fn default() -> Self {
628        Self {
629            caller: Address::ZERO,
630            gas_limit: u64::MAX,
631            gas_price: U256::ZERO,
632            gas_priority_fee: None,
633            transact_to: TransactTo::Call(Address::ZERO), // will do nothing
634            value: U256::ZERO,
635            data: Bytes::new(),
636            chain_id: None,
637            nonce: None,
638            access_list: Vec::new(),
639            blob_hashes: Vec::new(),
640            max_fee_per_blob_gas: None,
641            eof_initcodes: Vec::new(),
642            eof_initcodes_hashed: HashMap::new(),
643            #[cfg(feature = "optimism")]
644            optimism: OptimismFields::default(),
645        }
646    }
647}
648
649/// Structure holding block blob excess gas and it calculates blob fee.
650///
651/// Incorporated as part of the Cancun upgrade via [EIP-4844].
652///
653/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
654#[derive(Clone, Debug, PartialEq, Eq, Hash)]
655#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
656pub struct BlobExcessGasAndPrice {
657    /// The excess blob gas of the block.
658    pub excess_blob_gas: u64,
659    /// The calculated blob gas price based on the `excess_blob_gas`, See [calc_blob_gasprice]
660    pub blob_gasprice: u128,
661}
662
663impl BlobExcessGasAndPrice {
664    /// Creates a new instance by calculating the blob gas price with [`calc_blob_gasprice`].
665    pub fn new(excess_blob_gas: u64) -> Self {
666        let blob_gasprice = calc_blob_gasprice(excess_blob_gas);
667        Self {
668            excess_blob_gas,
669            blob_gasprice,
670        }
671    }
672}
673
674/// Additional [TxEnv] fields for optimism.
675#[cfg(feature = "optimism")]
676#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
677#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
678pub struct OptimismFields {
679    /// The source hash is used to make sure that deposit transactions do
680    /// not have identical hashes.
681    ///
682    /// L1 originated deposit transaction source hashes are computed using
683    /// the hash of the l1 block hash and the l1 log index.
684    /// L1 attributes deposit source hashes are computed with the l1 block
685    /// hash and the sequence number = l2 block number - l2 epoch start
686    /// block number.
687    ///
688    /// These two deposit transaction sources specify a domain in the outer
689    /// hash so there are no collisions.
690    pub source_hash: Option<B256>,
691    /// The amount to increase the balance of the `from` account as part of
692    /// a deposit transaction. This is unconditional and is applied to the
693    /// `from` account even if the deposit transaction fails since
694    /// the deposit is pre-paid on L1.
695    pub mint: Option<u128>,
696    /// Whether or not the transaction is a system transaction.
697    pub is_system_transaction: Option<bool>,
698    /// An enveloped EIP-2718 typed transaction. This is used
699    /// to compute the L1 tx cost using the L1 block info, as
700    /// opposed to requiring downstream apps to compute the cost
701    /// externally.
702    /// This field is optional to allow the [TxEnv] to be constructed
703    /// for non-optimism chains when the `optimism` feature is enabled,
704    /// but the [CfgEnv] `optimism` field is set to false.
705    pub enveloped_tx: Option<Bytes>,
706}
707
708/// Transaction destination.
709#[derive(Clone, Debug, PartialEq, Eq, Hash)]
710#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
711pub enum TransactTo {
712    /// Simple call to an address.
713    Call(Address),
714    /// Contract creation.
715    Create,
716}
717
718impl TransactTo {
719    /// Calls the given address.
720    #[inline]
721    pub fn call(address: Address) -> Self {
722        Self::Call(address)
723    }
724
725    /// Creates a contract.
726    #[inline]
727    pub fn create() -> Self {
728        Self::Create
729    }
730    /// Returns `true` if the transaction is `Call`.
731    #[inline]
732    pub fn is_call(&self) -> bool {
733        matches!(self, Self::Call(_))
734    }
735
736    /// Returns `true` if the transaction is `Create` or `Create2`.
737    #[inline]
738    pub fn is_create(&self) -> bool {
739        matches!(self, Self::Create)
740    }
741}
742
743/// Create scheme.
744#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
745#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
746pub enum CreateScheme {
747    /// Legacy create scheme of `CREATE`.
748    Create,
749    /// Create scheme of `CREATE2`.
750    Create2 {
751        /// Salt.
752        salt: U256,
753    },
754}
755
756/// What bytecode analysis to perform.
757#[derive(Clone, Default, Debug, Eq, PartialEq, Hash)]
758#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
759pub enum AnalysisKind {
760    /// Do not perform bytecode analysis.
761    Raw,
762    /// Perform bytecode analysis.
763    #[default]
764    Analyse,
765}
766
767#[cfg(test)]
768mod tests {
769    use super::*;
770
771    #[test]
772    fn test_validate_tx_chain_id() {
773        let mut env = Env::default();
774        env.tx.chain_id = Some(1);
775        env.cfg.chain_id = 2;
776        assert_eq!(
777            env.validate_tx::<crate::LatestSpec>(),
778            Err(InvalidTransaction::InvalidChainId)
779        );
780    }
781
782    #[test]
783    fn test_validate_tx_access_list() {
784        let mut env = Env::default();
785        env.tx.access_list = vec![(Address::ZERO, vec![])];
786        assert_eq!(
787            env.validate_tx::<crate::FrontierSpec>(),
788            Err(InvalidTransaction::AccessListNotSupported)
789        );
790    }
791}