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}