├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── BUG-FORM.yml │ └── FEATURE-FORM.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── crates ├── op-evm │ ├── README.md │ ├── Cargo.toml │ └── src │ │ └── block │ │ ├── receipt_builder.rs │ │ └── canyon.rs └── evm │ ├── src │ ├── rpc │ │ ├── mod.rs │ │ ├── transaction.rs │ │ └── fees.rs │ ├── op │ │ ├── mod.rs │ │ ├── rpc.rs │ │ ├── env.rs │ │ ├── spec_id.rs │ │ └── tx.rs │ ├── lib.rs │ ├── block │ │ ├── state_hook.rs │ │ ├── state.rs │ │ ├── system_calls │ │ │ ├── eip2935.rs │ │ │ ├── eip4788.rs │ │ │ ├── eip7002.rs │ │ │ ├── eip7251.rs │ │ │ └── mod.rs │ │ ├── state_changes.rs │ │ ├── calc.rs │ │ └── error.rs │ ├── eth │ │ ├── spec.rs │ │ ├── receipt_builder.rs │ │ ├── dao_fork.rs │ │ ├── spec_id.rs │ │ ├── env.rs │ │ ├── block.rs │ │ └── mod.rs │ ├── call.rs │ ├── either.rs │ ├── error.rs │ ├── env.rs │ ├── tracing.rs │ ├── overrides.rs │ └── evm.rs │ ├── README.md │ └── Cargo.toml ├── .gitignore ├── .config ├── nextest.toml └── zepter.yaml ├── scripts ├── changelog.sh └── check_no_std.sh ├── rustfmt.toml ├── release.toml ├── README.md ├── LICENSE-MIT ├── deny.toml ├── cliff.toml ├── Cargo.toml └── LICENSE-APACHE /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @mattsse @klkvr 2 | -------------------------------------------------------------------------------- /crates/op-evm/README.md: -------------------------------------------------------------------------------- 1 | # alloy-evm 2 | 3 | OP EVM implementation. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .vscode 4 | .idea 5 | .env 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.config/nextest.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | retries = { backoff = "exponential", count = 2, delay = "2s", jitter = true } 3 | slow-timeout = { period = "30s", terminate-after = 4 } 4 | -------------------------------------------------------------------------------- /crates/evm/src/rpc/mod.rs: -------------------------------------------------------------------------------- 1 | //! RPC-related traits and implementations. 2 | 3 | mod fees; 4 | mod transaction; 5 | 6 | pub use fees::{CallFees, CallFeesError}; 7 | pub use transaction::{EthTxEnvError, TryIntoTxEnv}; 8 | -------------------------------------------------------------------------------- /crates/evm/src/op/mod.rs: -------------------------------------------------------------------------------- 1 | //! Optimism EVM implementation. 2 | 3 | mod env; 4 | #[cfg(feature = "rpc")] 5 | mod rpc; 6 | mod spec_id; 7 | mod tx; 8 | 9 | pub use spec_id::{spec, spec_by_timestamp_after_bedrock}; 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Support 4 | url: https://t.me/ethers_rs 5 | about: This issue tracker is only for bugs and feature requests. Support is available on Telegram! 6 | -------------------------------------------------------------------------------- /scripts/changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e -o pipefail 3 | 4 | root=$(dirname "$(dirname "$0")") 5 | cmd=(git cliff --workdir "$root" --output "$root/CHANGELOG.md" "$@") 6 | 7 | if [ "$DRY_RUN" = "true" ]; then 8 | echo "skipping due to dry run: ${cmd[*]}" >&2 9 | exit 0 10 | else 11 | "${cmd[@]}" 12 | fi -------------------------------------------------------------------------------- /crates/evm/README.md: -------------------------------------------------------------------------------- 1 | # alloy-evm 2 | 3 | EVM interface. 4 | 5 | This crate contains constants, types, and functions for interacting with the Ethereum Virtual Machine (EVM). 6 | It is compatible with the types from the [alloy](https://crates.io/crates/alloy) ecosystem and comes with batteries included for [revm](https://crates.io/crates/revm) 7 | 8 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | use_field_init_shorthand = true 3 | use_small_heuristics = "Max" 4 | 5 | # Nightly 6 | max_width = 100 7 | comment_width = 100 8 | imports_granularity = "Crate" 9 | wrap_comments = true 10 | format_code_in_doc_comments = true 11 | doc_comment_code_block_width = 100 12 | format_macro_matchers = true 13 | -------------------------------------------------------------------------------- /scripts/check_no_std.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | no_std_packages=( 5 | alloy-evm 6 | alloy-op-evm 7 | ) 8 | 9 | for package in "${no_std_packages[@]}"; do 10 | cmd="cargo +stable build -p $package --target riscv32imac-unknown-none-elf --no-default-features" 11 | if [ -n "$CI" ]; then 12 | echo "::group::$cmd" 13 | else 14 | printf "\n%s:\n %s\n" "$package" "$cmd" 15 | fi 16 | 17 | $cmd 18 | 19 | if [ -n "$CI" ]; then 20 | echo "::endgroup::" 21 | fi 22 | done 23 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | # Configuration file for [`cargo-release`](https://github.com/crate-ci/cargo-release) 2 | # See: https://github.com/crate-ci/cargo-release/blob/master/docs/reference.md 3 | 4 | allow-branch = ["main"] 5 | sign-commit = true 6 | sign-tag = true 7 | shared-version = true 8 | pre-release-commit-message = "chore: release {{version}}" 9 | tag-prefix = "" # tag only once instead of per every crate 10 | pre-release-hook = ["sh", "-c", "$WORKSPACE_ROOT/scripts/changelog.sh --tag {{version}}"] 11 | owners = ["github:alloy-rs:core"] 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG-FORM.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: File a bug report 3 | labels: ["bug"] 4 | title: "[Bug] " 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please ensure that the bug has not already been filed in the issue tracker. 10 | 11 | Thanks for taking the time to report this bug! 12 | - type: input 13 | attributes: 14 | label: What version of Alloy are you on? 15 | placeholder: "Run `cargo tree | grep alloy` and paste the output here" 16 | - type: textarea 17 | attributes: 18 | label: Describe the bug 19 | description: Please include code snippets as well if relevant. 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE-FORM.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a feature 3 | labels: ["enhancement"] 4 | title: "[Feature] " 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Please ensure that the feature has not already been requested in the issue tracker. 10 | - type: textarea 11 | attributes: 12 | label: Describe the feature you would like 13 | description: 14 | Please also describe your goals for the feature. What problems it solves, how it would be 15 | used, etc. 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Additional context 21 | description: Add any other context to the feature (like screenshots, resources) 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :construction: This repository is a work in progress. 2 | 3 | # Alloy-EVM 4 | 5 | ## Overview 6 | 7 | `alloy-evm` is an abstraction layer on top of [revm](https://github.com/bluealloy/revm) providing common implementations of EVMs. Currently, alloy-evm is only used in Reth but is designed to be consumed by any project that needs to execute/trace transactions or blocks on EVM compatible chains. 8 | 9 | `alloy-evm` is compatible with no_std and riscv targets. 10 | 11 | #### License 12 | 13 | 14 | Licensed under either of Apache License, Version 15 | 2.0 or MIT license at your option. 16 | 17 | 18 |
19 | 20 | 21 | Unless you explicitly state otherwise, any contribution intentionally submitted 22 | for inclusion in these crates by you, as defined in the Apache-2.0 license, 23 | shall be dual licensed as above, without any additional terms or conditions. 24 | 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | ## Motivation 16 | 17 | 22 | 23 | ## Solution 24 | 25 | 29 | 30 | ## PR Checklist 31 | 32 | - [ ] Added Tests 33 | - [ ] Added Documentation 34 | - [ ] Breaking changes 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /crates/evm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![doc( 3 | html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg", 4 | html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico" 5 | )] 6 | #![cfg_attr(not(test), warn(unused_crate_dependencies))] 7 | #![cfg_attr(docsrs, feature(doc_cfg))] 8 | #![cfg_attr(not(feature = "std"), no_std)] 9 | 10 | extern crate alloc; 11 | 12 | pub mod block; 13 | pub mod evm; 14 | pub use evm::{Database, Evm, EvmFactory}; 15 | pub mod eth; 16 | pub use eth::{EthEvm, EthEvmFactory}; 17 | pub mod env; 18 | pub use env::EvmEnv; 19 | pub mod error; 20 | pub use error::*; 21 | pub mod tx; 22 | pub use tx::*; 23 | pub mod traits; 24 | pub use traits::*; 25 | #[cfg(feature = "call-util")] 26 | pub mod call; 27 | #[cfg(feature = "op")] 28 | pub mod op; 29 | #[cfg(feature = "overrides")] 30 | pub mod overrides; 31 | pub mod precompiles; 32 | #[cfg(feature = "rpc")] 33 | pub mod rpc; 34 | pub mod tracing; 35 | 36 | mod either; 37 | 38 | // re-export revm and op-revm 39 | #[cfg(feature = "op")] 40 | pub use op_revm; 41 | pub use revm; 42 | 43 | pub use eth::spec_id::{spec, spec_by_timestamp_and_block_number}; 44 | -------------------------------------------------------------------------------- /crates/op-evm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alloy-op-evm" 3 | description = "OP EVM implementation" 4 | 5 | version.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | 13 | [lints] 14 | workspace = true 15 | 16 | [dependencies] 17 | alloy-evm = { workspace = true, features = ["op"] } 18 | 19 | alloy-eips.workspace = true 20 | alloy-consensus.workspace = true 21 | alloy-primitives.workspace = true 22 | 23 | alloy-op-hardforks.workspace = true 24 | op-alloy.workspace = true 25 | 26 | revm.workspace = true 27 | op-revm.workspace = true 28 | 29 | thiserror.workspace = true 30 | 31 | auto_impl.workspace = true 32 | 33 | [dev-dependencies] 34 | alloy-hardforks.workspace = true 35 | test-case.workspace = true 36 | 37 | [features] 38 | default = ["std"] 39 | std = [ 40 | "alloy-primitives/std", 41 | "revm/std", 42 | "alloy-evm/std", 43 | "op-revm/std", 44 | "alloy-consensus/std", 45 | "alloy-eips/std", 46 | "op-alloy/std", 47 | "thiserror/std" 48 | ] 49 | gmp = ["alloy-evm/gmp"] 50 | asm-keccak = ["alloy-evm/asm-keccak", "alloy-primitives/asm-keccak", "revm/asm-keccak"] -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [advisories] 2 | version = 2 3 | yanked = "warn" 4 | ignore = [ 5 | # https://rustsec.org/advisories/RUSTSEC-2024-0437, trezor-client dependency, no fix available yet 6 | "RUSTSEC-2024-0437", 7 | # https://rustsec.org/advisories/RUSTSEC-2024-0436 8 | "RUSTSEC-2024-0436", 9 | ] 10 | 11 | [bans] 12 | multiple-versions = "warn" 13 | wildcards = "deny" 14 | highlight = "all" 15 | 16 | [licenses] 17 | version = 2 18 | confidence-threshold = 0.8 19 | 20 | allow = [ 21 | "MIT", 22 | "Apache-2.0", 23 | "Apache-2.0 WITH LLVM-exception", 24 | "BSD-2-Clause", 25 | "BSD-3-Clause", 26 | "Unicode-3.0", 27 | "Unlicense", 28 | "Zlib", 29 | "CC0-1.0", 30 | ] 31 | 32 | exceptions = [ 33 | # gmp feature (optional, LGPL-licensed) 34 | { allow = ["LGPL-3.0-or-later"], crate = "rug" }, 35 | { allow = ["LGPL-3.0-or-later"], crate = "gmp-mpfr-sys" }, 36 | ] 37 | 38 | [[licenses.clarify]] 39 | name = "ring" 40 | expression = "LicenseRef-ring" 41 | license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] 42 | 43 | [[licenses.clarify]] 44 | name = "webpki" 45 | expression = "LicenseRef-webpki" 46 | license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] 47 | 48 | [sources] 49 | unknown-registry = "deny" 50 | unknown-git = "deny" 51 | allow-git = [ 52 | "https://github.com/bluealloy/revm", 53 | "https://github.com/alloy-rs/hardforks", 54 | ] 55 | -------------------------------------------------------------------------------- /.config/zepter.yaml: -------------------------------------------------------------------------------- 1 | version: 2 | format: 1 3 | # Minimum zepter version that is expected to work. This is just for printing a nice error 4 | # message when someone tries to use an older version. 5 | binary: 0.13.2 6 | 7 | # The examples in the following comments assume crate `A` to have a dependency on crate `B`. 8 | workflows: 9 | check: 10 | - [ 11 | "lint", 12 | # Check that `A` activates the features of `B`. 13 | "propagate-feature", 14 | # These are the features to check: 15 | "--features=std", 16 | # Do not try to add a new section into `[features]` of `A` only because `B` expose that feature. There are edge-cases where this is still needed, but we can add them manually. 17 | "--left-side-feature-missing=ignore", 18 | # Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on. 19 | "--left-side-outside-workspace=ignore", 20 | # limit to the workspace 21 | "--show-path", 22 | "--quiet", 23 | ] 24 | default: 25 | # Running `zepter` with no subcommand will check & fix. 26 | - [$check.0, "--fix"] 27 | 28 | # Will be displayed when any workflow fails: 29 | help: 30 | text: | 31 | Alloy uses the Zepter CLI to detect abnormalities in Cargo features, e.g. missing propagation. 32 | 33 | It looks like one more checks failed; please check the console output. 34 | 35 | You can try to automatically address them by installing zepter (`cargo install zepter --locked`) and simply running `zepter` in the workspace root. 36 | links: 37 | - "https://github.com/ggwpez/zepter" 38 | -------------------------------------------------------------------------------- /crates/evm/src/block/state_hook.rs: -------------------------------------------------------------------------------- 1 | use revm::state::EvmState; 2 | 3 | /// A hook that is called after each state change. 4 | pub trait OnStateHook: Send + 'static { 5 | /// Invoked with the source of the change and the state after each system call. 6 | fn on_state(&mut self, source: StateChangeSource, state: &EvmState); 7 | } 8 | 9 | /// Source of the state change 10 | #[derive(Debug, Clone, Copy)] 11 | pub enum StateChangeSource { 12 | /// Transaction with its index 13 | Transaction(usize), 14 | /// Pre-block state transition 15 | PreBlock(StateChangePreBlockSource), 16 | /// Post-block state transition 17 | PostBlock(StateChangePostBlockSource), 18 | } 19 | 20 | /// Source of the pre-block state change 21 | #[derive(Debug, Clone, Copy)] 22 | pub enum StateChangePreBlockSource { 23 | /// EIP-2935 blockhashes contract 24 | BlockHashesContract, 25 | /// EIP-4788 beacon root contract 26 | BeaconRootContract, 27 | /// EIP-7002 withdrawal requests contract 28 | WithdrawalRequestsContract, 29 | } 30 | 31 | /// Source of the post-block state change 32 | #[derive(Debug, Clone, Copy)] 33 | pub enum StateChangePostBlockSource { 34 | /// Balance increments from block rewards and withdrawals 35 | BalanceIncrements, 36 | /// EIP-7002 withdrawal requests contract 37 | WithdrawalRequestsContract, 38 | /// EIP-7251 consolidation requests contract 39 | ConsolidationRequestsContract, 40 | } 41 | 42 | impl OnStateHook for F 43 | where 44 | F: FnMut(StateChangeSource, &EvmState) + Send + 'static, 45 | { 46 | fn on_state(&mut self, source: StateChangeSource, state: &EvmState) { 47 | self(source, state) 48 | } 49 | } 50 | 51 | /// An [`OnStateHook`] that does nothing. 52 | #[derive(Default, Debug, Clone)] 53 | #[non_exhaustive] 54 | pub struct NoopHook; 55 | 56 | impl OnStateHook for NoopHook { 57 | fn on_state(&mut self, _source: StateChangeSource, _state: &EvmState) {} 58 | } 59 | -------------------------------------------------------------------------------- /crates/evm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alloy-evm" 3 | description = "EVM abstraction for Alloy" 4 | 5 | version.workspace = true 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | homepage.workspace = true 11 | repository.workspace = true 12 | 13 | [lints] 14 | workspace = true 15 | 16 | [dependencies] 17 | alloy-consensus = { workspace = true, features = ["k256"] } 18 | alloy-primitives.workspace = true 19 | alloy-sol-types.workspace = true 20 | alloy-eips.workspace = true 21 | alloy-hardforks.workspace = true 22 | alloy-op-hardforks = { workspace = true, optional = true } 23 | alloy-rpc-types-eth = { workspace = true, optional = true } 24 | alloy-rpc-types-engine = { workspace = true, optional = true } 25 | 26 | revm.workspace = true 27 | op-revm = { workspace = true, optional = true } 28 | op-alloy = { workspace = true, optional = true } 29 | 30 | auto_impl.workspace = true 31 | derive_more.workspace = true 32 | thiserror.workspace = true 33 | 34 | [dev-dependencies] 35 | alloy-primitives = { workspace = true, features = ["serde"] } 36 | serde_json.workspace = true 37 | test-case.workspace = true 38 | 39 | [features] 40 | default = ["std"] 41 | secp256k1 = [ 42 | "std", 43 | "alloy-consensus/secp256k1", 44 | ] 45 | std = [ 46 | "alloy-primitives/std", 47 | "revm/std", 48 | "alloy-consensus/std", 49 | "alloy-eips/std", 50 | "alloy-sol-types/std", 51 | "derive_more/std", 52 | "op-revm?/std", 53 | "thiserror/std", 54 | "op-alloy?/std", 55 | "alloy-rpc-types-eth?/std", 56 | "alloy-rpc-types-engine?/std" 57 | ] 58 | gmp = [ 59 | "revm/gmp", 60 | ] 61 | op = ["op-revm", "op-alloy", "alloy-op-hardforks"] 62 | overrides = ["dep:alloy-rpc-types-eth"] 63 | call-util = ["overrides"] 64 | engine = ["dep:alloy-rpc-types-engine", "op-alloy?/rpc-types-engine"] 65 | asm-keccak = ["alloy-primitives/asm-keccak", "revm/asm-keccak"] 66 | rpc = ["dep:alloy-rpc-types-eth", "op-alloy?/rpc-types"] 67 | -------------------------------------------------------------------------------- /crates/evm/src/block/state.rs: -------------------------------------------------------------------------------- 1 | //! State database abstraction. 2 | 3 | use alloy_primitives::Address; 4 | use revm::database::State; 5 | 6 | /// A type which has the state of the blockchain. 7 | /// 8 | /// This trait encapsulates some of the functionality found in [`State`] 9 | pub trait StateDB: revm::Database { 10 | /// State clear EIP-161 is enabled in Spurious Dragon hardfork. 11 | fn set_state_clear_flag(&mut self, has_state_clear: bool); 12 | 13 | /// Iterates over received balances and increment all account balances. 14 | /// 15 | /// **Note**: If account is not found inside cache state it will be loaded from database. 16 | /// 17 | /// Update will create transitions for all accounts that are updated. 18 | /// 19 | /// If using this to implement withdrawals, zero balances must be filtered out before calling 20 | /// this function. 21 | fn increment_balances( 22 | &mut self, 23 | balances: impl IntoIterator, 24 | ) -> Result<(), Self::Error>; 25 | } 26 | 27 | /// auto_impl unable to reconcile return associated type from supertrait 28 | impl StateDB for &mut T { 29 | fn set_state_clear_flag(&mut self, has_state_clear: bool) { 30 | StateDB::set_state_clear_flag(*self, has_state_clear); 31 | } 32 | 33 | fn increment_balances( 34 | &mut self, 35 | balances: impl IntoIterator, 36 | ) -> Result<(), Self::Error> { 37 | StateDB::increment_balances(*self, balances) 38 | } 39 | } 40 | 41 | impl StateDB for State { 42 | fn set_state_clear_flag(&mut self, has_state_clear: bool) { 43 | self.cache.set_state_clear_flag(has_state_clear); 44 | } 45 | 46 | fn increment_balances( 47 | &mut self, 48 | balances: impl IntoIterator, 49 | ) -> Result<(), Self::Error> { 50 | Self::increment_balances(self, balances) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /crates/evm/src/block/system_calls/eip2935.rs: -------------------------------------------------------------------------------- 1 | //! [EIP-2935](https://eips.ethereum.org/EIPS/eip-2935) system call implementation. 2 | 3 | use crate::{ 4 | block::{BlockExecutionError, BlockValidationError}, 5 | Evm, 6 | }; 7 | use alloc::string::ToString; 8 | use alloy_eips::eip2935::HISTORY_STORAGE_ADDRESS; 9 | use alloy_hardforks::EthereumHardforks; 10 | use alloy_primitives::B256; 11 | use revm::{context::Block, context_interface::result::ResultAndState}; 12 | 13 | /// Applies the pre-block call to the [EIP-2935] blockhashes contract, using the given block, 14 | /// chain specification, and EVM. 15 | /// 16 | /// If Prague is not activated, or the block is the genesis block, then this is a no-op, and no 17 | /// state changes are made. 18 | /// 19 | /// Note: this does not commit the state changes to the database, it only transacts the call. 20 | /// 21 | /// Returns `None` if Prague is not active or the block is the genesis block, otherwise returns the 22 | /// result of the call. 23 | /// 24 | /// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935 25 | #[inline] 26 | pub(crate) fn transact_blockhashes_contract_call( 27 | spec: impl EthereumHardforks, 28 | parent_block_hash: B256, 29 | evm: &mut impl Evm, 30 | ) -> Result>, BlockExecutionError> { 31 | if !spec.is_prague_active_at_timestamp(evm.block().timestamp().saturating_to()) { 32 | return Ok(None); 33 | } 34 | 35 | // if the block number is zero (genesis block) then no system transaction may occur as per 36 | // EIP-2935 37 | if evm.block().number().is_zero() { 38 | return Ok(None); 39 | } 40 | 41 | let res = match evm.transact_system_call( 42 | alloy_eips::eip4788::SYSTEM_ADDRESS, 43 | HISTORY_STORAGE_ADDRESS, 44 | parent_block_hash.0.into(), 45 | ) { 46 | Ok(res) => res, 47 | Err(e) => { 48 | return Err( 49 | BlockValidationError::BlockHashContractCall { message: e.to_string() }.into() 50 | ) 51 | } 52 | }; 53 | 54 | Ok(Some(res)) 55 | } 56 | -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # Configuration file for [`git-cliff`](https://github.com/orhun/git-cliff) 2 | # See https://git-cliff.org/docs/configuration 3 | 4 | [changelog] 5 | header = """ 6 | # Changelog 7 | 8 | All notable changes to this project will be documented in this file. 9 | 10 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 11 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n 12 | """ 13 | # https://tera.netlify.app/docs/#introduction 14 | body = """ 15 | {% if version %}\ 16 | ## [{{ version | trim_start_matches(pat="v") }}](https://github.com/alloy-rs/evm/releases/tag/v{{ version | trim_start_matches(pat="v") }}) - {{ timestamp | date(format="%Y-%m-%d") }} 17 | {% endif %}\ 18 | {% for group, commits in commits | group_by(attribute="group") %} 19 | ### {{ group | title }} 20 | {% for commit in commits %} 21 | - {% if commit.scope %}[{{ commit.scope }}] {% endif %}{{ commit.message | upper_first | split(pat="\\n") | first }}\ 22 | {% endfor %} 23 | {% endfor %}\n 24 | """ 25 | trim = true 26 | footer = "" 27 | 28 | [git] 29 | conventional_commits = true 30 | filter_unconventional = false 31 | commit_preprocessors = [ 32 | { pattern = '#(\d+)', replace = "[#$1](https://github.com/alloy-rs/evm/issues/$1)" }, 33 | ] 34 | commit_parsers = [ 35 | { message = "^[Ff]eat", group = "Features" }, 36 | { message = "^[Ff]ix", group = "Bug Fixes" }, 37 | { message = "^[Dd]oc", group = "Documentation" }, 38 | { message = ".*\\b([Dd]eps|[Dd]ependencies|[Bb]ump)\\b", group = "Dependencies" }, 39 | { message = "^[Pp]erf", group = "Performance" }, 40 | { message = "^[Rr]efactor", group = "Refactor" }, 41 | { message = ".*\\b([Ss]tyle|[Ff]mt|[Ff]ormat)\\b", group = "Styling" }, 42 | { message = "^[Tt]est", group = "Testing" }, 43 | { message = "^[Cc]hore", group = "Miscellaneous Tasks" }, 44 | 45 | { message = ".*", group = "Other" }, 46 | ] 47 | protect_breaking_commits = false 48 | filter_commits = false 49 | tag_pattern = "v[0-9]*" 50 | skip_tags = "beta|alpha" 51 | ignore_tags = "rc" 52 | sort_commits = "newest" 53 | -------------------------------------------------------------------------------- /crates/evm/src/eth/spec.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over configuration object for [`super::EthBlockExecutor`]. 2 | 3 | use alloy_eips::eip6110::MAINNET_DEPOSIT_CONTRACT_ADDRESS; 4 | use alloy_hardforks::{EthereumChainHardforks, EthereumHardfork, EthereumHardforks, ForkCondition}; 5 | use alloy_primitives::{address, Address}; 6 | 7 | /// A configuration object for [`super::EthBlockExecutor`] 8 | #[auto_impl::auto_impl(&, Arc)] 9 | pub trait EthExecutorSpec: EthereumHardforks { 10 | /// Address of deposit contract emitting deposit events. 11 | /// 12 | /// Used by [`super::eip6110::parse_deposits_from_receipts`]. 13 | fn deposit_contract_address(&self) -> Option
; 14 | } 15 | 16 | /// Basic Ethereum specification. 17 | #[derive(Debug, Clone)] 18 | pub struct EthSpec { 19 | hardforks: EthereumChainHardforks, 20 | deposit_contract_address: Option
, 21 | } 22 | 23 | impl EthSpec { 24 | /// Creates [`EthSpec`] for Ethereum mainnet. 25 | pub fn mainnet() -> Self { 26 | Self { 27 | hardforks: EthereumChainHardforks::mainnet(), 28 | deposit_contract_address: Some(MAINNET_DEPOSIT_CONTRACT_ADDRESS), 29 | } 30 | } 31 | 32 | /// Creates [`EthSpec`] for Ethereum Sepolia. 33 | pub fn sepolia() -> Self { 34 | Self { 35 | hardforks: EthereumChainHardforks::sepolia(), 36 | deposit_contract_address: Some(address!("0x7f02c3e3c98b133055b8b348b2ac625669ed295d")), 37 | } 38 | } 39 | 40 | /// Creates [`EthSpec`] for Ethereum Holesky. 41 | pub fn holesky() -> Self { 42 | Self { 43 | hardforks: EthereumChainHardforks::holesky(), 44 | deposit_contract_address: Some(address!("0x4242424242424242424242424242424242424242")), 45 | } 46 | } 47 | } 48 | 49 | impl EthereumHardforks for EthSpec { 50 | fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition { 51 | self.hardforks.ethereum_fork_activation(fork) 52 | } 53 | } 54 | 55 | impl EthExecutorSpec for EthSpec { 56 | fn deposit_contract_address(&self) -> Option
{ 57 | self.deposit_contract_address 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/evm/src/call.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for dealing with eth_call and adjacent RPC endpoints. 2 | 3 | use alloy_primitives::U256; 4 | use revm::Database; 5 | 6 | /// Insufficient funds error 7 | #[derive(Debug, thiserror::Error)] 8 | #[error("insufficient funds: cost {cost} > balance {balance}")] 9 | pub struct InsufficientFundsError { 10 | /// Transaction cost 11 | pub cost: U256, 12 | /// Account balance 13 | pub balance: U256, 14 | } 15 | 16 | /// Error type for call utilities 17 | #[derive(Debug, thiserror::Error)] 18 | pub enum CallError { 19 | /// Database error 20 | #[error(transparent)] 21 | Database(E), 22 | /// Insufficient funds error 23 | #[error(transparent)] 24 | InsufficientFunds(#[from] InsufficientFundsError), 25 | } 26 | 27 | /// Calculates the caller gas allowance. 28 | /// 29 | /// `allowance = (account.balance - tx.value) / tx.gas_price` 30 | /// 31 | /// Returns an error if the caller has insufficient funds. 32 | /// Caution: This assumes non-zero `env.gas_price`. Otherwise, zero allowance will be returned. 33 | /// 34 | /// Note: this takes the mut [Database] trait because the loaded sender can be reused for the 35 | /// following operation like `eth_call`. 36 | pub fn caller_gas_allowance(db: &mut DB, env: &T) -> Result> 37 | where 38 | DB: Database, 39 | T: revm::context_interface::Transaction, 40 | { 41 | // Get the caller account. 42 | let caller = db.basic(env.caller()).map_err(CallError::Database)?; 43 | // Get the caller balance. 44 | let balance = caller.map(|acc| acc.balance).unwrap_or_default(); 45 | // Get transaction value. 46 | let value = env.value(); 47 | // Subtract transferred value from the caller balance. Return error if the caller has 48 | // insufficient funds. 49 | let balance = 50 | balance.checked_sub(env.value()).ok_or(InsufficientFundsError { cost: value, balance })?; 51 | 52 | Ok(balance 53 | // Calculate the amount of gas the caller can afford with the specified gas price. 54 | .checked_div(U256::from(env.gas_price())) 55 | // This will be 0 if gas price is 0. It is fine, because we check it before. 56 | .unwrap_or_default() 57 | .saturating_to()) 58 | } 59 | -------------------------------------------------------------------------------- /crates/evm/src/op/rpc.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | env::BlockEnvironment, 3 | rpc::{EthTxEnvError, TryIntoTxEnv}, 4 | EvmEnv, 5 | }; 6 | use alloy_primitives::Bytes; 7 | use op_alloy::rpc_types::OpTransactionRequest; 8 | use op_revm::OpTransaction; 9 | use revm::context::TxEnv; 10 | 11 | impl TryIntoTxEnv, Block> for OpTransactionRequest { 12 | type Err = EthTxEnvError; 13 | 14 | fn try_into_tx_env( 15 | self, 16 | evm_env: &EvmEnv, 17 | ) -> Result, Self::Err> { 18 | Ok(OpTransaction { 19 | base: self.as_ref().clone().try_into_tx_env(evm_env)?, 20 | enveloped_tx: Some(Bytes::new()), 21 | deposit: Default::default(), 22 | }) 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::*; 29 | 30 | #[test] 31 | fn test_op_into_tx_env() { 32 | use op_revm::{transaction::OpTxTr, OpSpecId}; 33 | use revm::context::{BlockEnv, Transaction}; 34 | 35 | let s = r#"{"from":"0x0000000000000000000000000000000000000000","to":"0x6d362b9c3ab68c0b7c79e8a714f1d7f3af63655f","input":"0x1626ba7ec8ee0d506e864589b799a645ddb88b08f5d39e8049f9f702b3b61fa15e55fc73000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000550000002d6db27c52e3c11c1cf24072004ac75cba49b25bf45f513902e469755e1f3bf2ca8324ad16930b0a965c012a24bb1101f876ebebac047bd3b6bf610205a27171eaaeffe4b5e5589936f4e542d637b627311b0000000000000000000000","data":"0x1626ba7ec8ee0d506e864589b799a645ddb88b08f5d39e8049f9f702b3b61fa15e55fc73000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000550000002d6db27c52e3c11c1cf24072004ac75cba49b25bf45f513902e469755e1f3bf2ca8324ad16930b0a965c012a24bb1101f876ebebac047bd3b6bf610205a27171eaaeffe4b5e5589936f4e542d637b627311b0000000000000000000000","chainId":"0x7a69"}"#; 36 | 37 | let req: OpTransactionRequest = serde_json::from_str(s).unwrap(); 38 | 39 | let evm_env = EvmEnv::::default(); 40 | let tx_env = req.try_into_tx_env(&evm_env).unwrap(); 41 | assert_eq!(tx_env.gas_limit(), evm_env.block_env().gas_limit); 42 | assert_eq!(tx_env.gas_price(), 0); 43 | assert!(tx_env.enveloped_tx().unwrap().is_empty()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/evm/src/block/system_calls/eip4788.rs: -------------------------------------------------------------------------------- 1 | //! [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) system call implementation. 2 | 3 | use crate::{ 4 | block::{BlockExecutionError, BlockValidationError}, 5 | Evm, 6 | }; 7 | use alloc::{boxed::Box, string::ToString}; 8 | use alloy_eips::eip4788::BEACON_ROOTS_ADDRESS; 9 | use alloy_hardforks::EthereumHardforks; 10 | use alloy_primitives::B256; 11 | use revm::{context::Block, context_interface::result::ResultAndState}; 12 | 13 | /// Applies the pre-block call to the [EIP-4788] beacon block root contract, using the given block, 14 | /// chain spec, EVM. 15 | /// 16 | /// Note: this does not commit the state changes to the database, it only transacts the call. 17 | /// 18 | /// Returns `None` if Cancun is not active or the block is the genesis block, otherwise returns the 19 | /// result of the call. 20 | /// 21 | /// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788 22 | #[inline] 23 | pub(crate) fn transact_beacon_root_contract_call( 24 | spec: impl EthereumHardforks, 25 | parent_beacon_block_root: Option, 26 | evm: &mut impl Evm, 27 | ) -> Result>, BlockExecutionError> { 28 | if !spec.is_cancun_active_at_timestamp(evm.block().timestamp().saturating_to()) { 29 | return Ok(None); 30 | } 31 | 32 | let parent_beacon_block_root = 33 | parent_beacon_block_root.ok_or(BlockValidationError::MissingParentBeaconBlockRoot)?; 34 | 35 | // if the block number is zero (genesis block) then the parent beacon block root must 36 | // be 0x0 and no system transaction may occur as per EIP-4788 37 | if evm.block().number().is_zero() { 38 | if !parent_beacon_block_root.is_zero() { 39 | return Err(BlockValidationError::CancunGenesisParentBeaconBlockRootNotZero { 40 | parent_beacon_block_root, 41 | } 42 | .into()); 43 | } 44 | return Ok(None); 45 | } 46 | 47 | let res = match evm.transact_system_call( 48 | alloy_eips::eip4788::SYSTEM_ADDRESS, 49 | BEACON_ROOTS_ADDRESS, 50 | parent_beacon_block_root.0.into(), 51 | ) { 52 | Ok(res) => res, 53 | Err(e) => { 54 | return Err(BlockValidationError::BeaconRootContractCall { 55 | parent_beacon_block_root: Box::new(parent_beacon_block_root), 56 | message: e.to_string(), 57 | } 58 | .into()) 59 | } 60 | }; 61 | 62 | Ok(Some(res)) 63 | } 64 | -------------------------------------------------------------------------------- /crates/evm/src/eth/receipt_builder.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over receipt building logic to allow plugging different primitive types into 2 | //! [`super::EthBlockExecutor`]. 3 | 4 | use crate::Evm; 5 | use alloy_consensus::{Eip658Value, ReceiptEnvelope, TxEnvelope, TxType}; 6 | use revm::{context::result::ExecutionResult, state::EvmState}; 7 | 8 | /// Context for building a receipt. 9 | #[derive(Debug)] 10 | pub struct ReceiptBuilderCtx<'a, T, E: Evm> { 11 | /// Transaction 12 | pub tx: &'a T, 13 | /// Reference to EVM. State changes should not be committed to inner database when building 14 | /// receipt so that [`ReceiptBuilder`] can use data from state before transaction execution. 15 | pub evm: &'a E, 16 | /// Result of transaction execution. 17 | pub result: ExecutionResult, 18 | /// Reference to EVM state after execution. 19 | pub state: &'a EvmState, 20 | /// Cumulative gas used. 21 | pub cumulative_gas_used: u64, 22 | } 23 | 24 | /// Type that knows how to build a receipt based on execution result. 25 | #[auto_impl::auto_impl(&, Arc)] 26 | pub trait ReceiptBuilder { 27 | /// Transaction type. 28 | type Transaction; 29 | /// Receipt type. 30 | type Receipt; 31 | 32 | /// Builds a receipt given a transaction and the result of the execution. 33 | fn build_receipt( 34 | &self, 35 | ctx: ReceiptBuilderCtx<'_, Self::Transaction, E>, 36 | ) -> Self::Receipt; 37 | } 38 | 39 | /// Receipt builder operating on Alloy types. 40 | #[derive(Debug, Default, Clone, Copy)] 41 | #[non_exhaustive] 42 | pub struct AlloyReceiptBuilder; 43 | 44 | impl ReceiptBuilder for AlloyReceiptBuilder { 45 | type Transaction = TxEnvelope; 46 | type Receipt = ReceiptEnvelope; 47 | 48 | fn build_receipt(&self, ctx: ReceiptBuilderCtx<'_, TxEnvelope, E>) -> Self::Receipt { 49 | let receipt = alloy_consensus::Receipt { 50 | status: Eip658Value::Eip658(ctx.result.is_success()), 51 | cumulative_gas_used: ctx.cumulative_gas_used, 52 | logs: ctx.result.into_logs(), 53 | } 54 | .with_bloom(); 55 | 56 | match ctx.tx.tx_type() { 57 | TxType::Legacy => ReceiptEnvelope::Legacy(receipt), 58 | TxType::Eip2930 => ReceiptEnvelope::Eip2930(receipt), 59 | TxType::Eip1559 => ReceiptEnvelope::Eip1559(receipt), 60 | TxType::Eip4844 => ReceiptEnvelope::Eip4844(receipt), 61 | TxType::Eip7702 => ReceiptEnvelope::Eip7702(receipt), 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/evm/src/block/system_calls/eip7002.rs: -------------------------------------------------------------------------------- 1 | //! [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) system call implementation. 2 | 3 | use crate::{ 4 | block::{BlockExecutionError, BlockValidationError}, 5 | Evm, 6 | }; 7 | use alloc::format; 8 | use alloy_eips::eip7002::WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS; 9 | use alloy_primitives::Bytes; 10 | use core::fmt::Debug; 11 | use revm::context_interface::result::{ExecutionResult, ResultAndState}; 12 | 13 | /// Applies the post-block call to the EIP-7002 withdrawal requests contract. 14 | /// 15 | /// If Prague is not active at the given timestamp, then this is a no-op. 16 | /// 17 | /// Note: this does not commit the state changes to the database, it only transacts the call. 18 | #[inline] 19 | pub(crate) fn transact_withdrawal_requests_contract_call( 20 | evm: &mut impl Evm, 21 | ) -> Result, BlockExecutionError> { 22 | // Execute EIP-7002 withdrawal requests contract call. 23 | // 24 | // The requirement for the withdrawal requests contract call defined by 25 | // [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) is: 26 | // 27 | // At the end of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. 28 | // after processing all transactions and after performing the block body withdrawal requests 29 | // validations), call the contract as `SYSTEM_ADDRESS`. 30 | let res = match evm.transact_system_call( 31 | alloy_eips::eip7002::SYSTEM_ADDRESS, 32 | WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, 33 | Bytes::new(), 34 | ) { 35 | Ok(res) => res, 36 | Err(e) => { 37 | return Err(BlockValidationError::WithdrawalRequestsContractCall { 38 | message: format!("execution failed: {e}"), 39 | } 40 | .into()) 41 | } 42 | }; 43 | 44 | Ok(res) 45 | } 46 | 47 | /// Calls the withdrawals requests system contract, and returns the requests from the execution 48 | /// output. 49 | #[inline] 50 | pub(crate) fn post_commit( 51 | result: ExecutionResult, 52 | ) -> Result { 53 | match result { 54 | ExecutionResult::Success { output, .. } => Ok(output.into_data()), 55 | ExecutionResult::Revert { output, .. } => { 56 | Err(BlockValidationError::WithdrawalRequestsContractCall { 57 | message: format!("execution reverted: {output}"), 58 | } 59 | .into()) 60 | } 61 | ExecutionResult::Halt { reason, .. } => { 62 | Err(BlockValidationError::WithdrawalRequestsContractCall { 63 | message: format!("execution halted: {reason:?}"), 64 | } 65 | .into()) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | version = "0.25.2" 7 | edition = "2021" 8 | rust-version = "1.88" 9 | authors = ["Alloy Contributors"] 10 | license = "MIT OR Apache-2.0" 11 | homepage = "https://github.com/alloy-rs/alloy-evm" 12 | repository = "https://github.com/alloy-rs/alloy-evm" 13 | exclude = ["benches/", "tests/"] 14 | 15 | [workspace.lints.rustdoc] 16 | all = "warn" 17 | 18 | [workspace.lints.rust] 19 | missing-debug-implementations = "warn" 20 | missing-docs = "warn" 21 | unreachable-pub = "warn" 22 | unused-must-use = "deny" 23 | rust-2018-idioms = "deny" 24 | unnameable-types = "warn" 25 | 26 | [workspace.lints.clippy] 27 | all = { level = "warn", priority = -1 } 28 | missing-const-for-fn = "warn" 29 | use-self = "warn" 30 | option-if-let-else = "allow" 31 | redundant-clone = "warn" 32 | 33 | [workspace.metadata.docs.rs] 34 | all-features = true 35 | rustdoc-args = ["--cfg", "docsrs"] 36 | 37 | [workspace.dependencies] 38 | alloy-evm = { version = "0.25.2", path = "crates/evm", default-features = false } 39 | alloy-op-evm = { version = "0.25.2", path = "crates/op-evm", default-features = false } 40 | 41 | # alloy 42 | alloy-eip2124 = { version = "0.2", default-features = false } 43 | alloy-chains = { version = "0.2.0", default-features = false } 44 | alloy-eips = { version = "1.0.34", default-features = false } 45 | alloy-consensus = { version = "1.0.27", default-features = false } 46 | alloy-primitives = { version = "1.0.0", default-features = false } 47 | alloy-sol-types = { version = "1.0.0", default-features = false } 48 | alloy-hardforks = { version = "0.4.2" } 49 | alloy-rpc-types-eth = { version = "1.0.27", default-features = false } 50 | alloy-rpc-types-engine = { version = "1.0.27", default-features = false } 51 | 52 | # op-alloy 53 | alloy-op-hardforks = { version = "0.4.2" } 54 | op-alloy = { version = "0.23", default-features = false, features = [ 55 | "consensus", 56 | ] } 57 | 58 | # revm 59 | revm = { version = "33.0.0", default-features = false } 60 | op-revm = { version = "14.0.0", default-features = false } 61 | 62 | # misc 63 | auto_impl = "1" 64 | derive_more = { version = "2", default-features = false, features = ["full"] } 65 | serde = { version = "1", default-features = false, features = ["derive"] } 66 | thiserror = { version = "2.0.0", default-features = false } 67 | serde_json = "1" 68 | test-case = "3" 69 | 70 | [patch.crates-io] 71 | # revm = { git = "https://github.com/bluealloy/revm", rev = "11b16259" } 72 | # op-revm = { git = "https://github.com/bluealloy/revm", rev = "11b16259" } 73 | 74 | #alloy-hardforks = { git = "https://github.com/alloy-rs/hardforks", rev = "0fd230f5aa24c4f6a8c593918b7449df0c20f60a" } 75 | #alloy-op-hardforks = { git = "https://github.com/alloy-rs/hardforks", rev = "0fd230f5aa24c4f6a8c593918b7449df0c20f60a" } 76 | -------------------------------------------------------------------------------- /crates/op-evm/src/block/receipt_builder.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over receipt building logic to allow plugging different primitive types into 2 | //! [`super::OpBlockExecutor`]. 3 | 4 | use alloy_consensus::Eip658Value; 5 | use alloy_evm::{eth::receipt_builder::ReceiptBuilderCtx, Evm}; 6 | use core::fmt::Debug; 7 | use op_alloy::consensus::{OpDepositReceipt, OpReceiptEnvelope, OpTxEnvelope, OpTxType}; 8 | 9 | /// Type that knows how to build a receipt based on execution result. 10 | #[auto_impl::auto_impl(&, Arc)] 11 | pub trait OpReceiptBuilder: Debug { 12 | /// Transaction type. 13 | type Transaction; 14 | /// Receipt type. 15 | type Receipt; 16 | 17 | /// Builds a receipt given a transaction and the result of the execution. 18 | /// 19 | /// Note: this method should return `Err` if the transaction is a deposit transaction. In that 20 | /// case, the `build_deposit_receipt` method will be called. 21 | #[expect(clippy::result_large_err)] // Err(_) is always consumed 22 | fn build_receipt<'a, E: Evm>( 23 | &self, 24 | ctx: ReceiptBuilderCtx<'a, Self::Transaction, E>, 25 | ) -> Result>; 26 | 27 | /// Builds receipt for a deposit transaction. 28 | fn build_deposit_receipt(&self, inner: OpDepositReceipt) -> Self::Receipt; 29 | } 30 | 31 | /// Receipt builder operating on op-alloy types. 32 | #[derive(Debug, Default, Clone, Copy)] 33 | #[non_exhaustive] 34 | pub struct OpAlloyReceiptBuilder; 35 | 36 | impl OpReceiptBuilder for OpAlloyReceiptBuilder { 37 | type Transaction = OpTxEnvelope; 38 | type Receipt = OpReceiptEnvelope; 39 | 40 | fn build_receipt<'a, E: Evm>( 41 | &self, 42 | ctx: ReceiptBuilderCtx<'a, OpTxEnvelope, E>, 43 | ) -> Result> { 44 | match ctx.tx.tx_type() { 45 | OpTxType::Deposit => Err(ctx), 46 | ty => { 47 | let receipt = alloy_consensus::Receipt { 48 | status: Eip658Value::Eip658(ctx.result.is_success()), 49 | cumulative_gas_used: ctx.cumulative_gas_used, 50 | logs: ctx.result.into_logs(), 51 | } 52 | .with_bloom(); 53 | 54 | Ok(match ty { 55 | OpTxType::Legacy => OpReceiptEnvelope::Legacy(receipt), 56 | OpTxType::Eip2930 => OpReceiptEnvelope::Eip2930(receipt), 57 | OpTxType::Eip1559 => OpReceiptEnvelope::Eip1559(receipt), 58 | OpTxType::Eip7702 => OpReceiptEnvelope::Eip7702(receipt), 59 | OpTxType::Deposit => unreachable!(), 60 | }) 61 | } 62 | } 63 | } 64 | 65 | fn build_deposit_receipt(&self, inner: OpDepositReceipt) -> Self::Receipt { 66 | OpReceiptEnvelope::Deposit(inner.with_bloom()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /crates/evm/src/block/system_calls/eip7251.rs: -------------------------------------------------------------------------------- 1 | //! [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) system call implementation. 2 | 3 | use crate::{ 4 | block::{BlockExecutionError, BlockValidationError}, 5 | Evm, 6 | }; 7 | use alloc::format; 8 | use alloy_eips::eip7251::CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS; 9 | use alloy_primitives::Bytes; 10 | use core::fmt::Debug; 11 | use revm::context_interface::result::{ExecutionResult, ResultAndState}; 12 | 13 | /// Applies the post-block call to the EIP-7251 consolidation requests contract. 14 | /// 15 | /// If Prague is not active at the given timestamp, then this is a no-op, and an empty vector is 16 | /// returned. Otherwise, the consolidation requests are returned. 17 | /// 18 | /// Note: this does not commit the state changes to the database, it only transacts the call. 19 | #[inline] 20 | pub(crate) fn transact_consolidation_requests_contract_call( 21 | evm: &mut impl Evm, 22 | ) -> Result, BlockExecutionError> { 23 | // Execute EIP-7251 consolidation requests contract call. 24 | // 25 | // The requirement for the consolidation requests contract call defined by 26 | // [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) is: 27 | // 28 | // At the end of processing any execution block where block.timestamp >= FORK_TIMESTAMP (i.e. 29 | // after processing all transactions and after performing the block body requests validations) 30 | // client software MUST call the contract as `SYSTEM_ADDRESS` with empty input data to 31 | // trigger the system subroutine execution. 32 | let res = match evm.transact_system_call( 33 | alloy_eips::eip7002::SYSTEM_ADDRESS, 34 | CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS, 35 | Bytes::new(), 36 | ) { 37 | Ok(res) => res, 38 | Err(e) => { 39 | return Err(BlockValidationError::ConsolidationRequestsContractCall { 40 | message: format!("execution failed: {e}"), 41 | } 42 | .into()) 43 | } 44 | }; 45 | 46 | Ok(res) 47 | } 48 | 49 | /// Calls the consolidation requests system contract, and returns the requests from the execution 50 | /// output. 51 | #[inline] 52 | pub(crate) fn post_commit( 53 | result: ExecutionResult, 54 | ) -> Result { 55 | match result { 56 | ExecutionResult::Success { output, .. } => Ok(output.into_data()), 57 | ExecutionResult::Revert { output, .. } => { 58 | Err(BlockValidationError::ConsolidationRequestsContractCall { 59 | message: format!("execution reverted: {output}"), 60 | } 61 | .into()) 62 | } 63 | ExecutionResult::Halt { reason, .. } => { 64 | Err(BlockValidationError::ConsolidationRequestsContractCall { 65 | message: format!("execution halted: {reason:?}"), 66 | } 67 | .into()) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/evm/src/either.rs: -------------------------------------------------------------------------------- 1 | use crate::{Evm, EvmEnv}; 2 | use alloy_primitives::{Address, Bytes}; 3 | use revm::context::either; 4 | 5 | impl Evm for either::Either 6 | where 7 | L: Evm, 8 | R: Evm< 9 | DB = L::DB, 10 | Tx = L::Tx, 11 | Error = L::Error, 12 | HaltReason = L::HaltReason, 13 | Spec = L::Spec, 14 | BlockEnv = L::BlockEnv, 15 | Precompiles = L::Precompiles, 16 | Inspector = L::Inspector, 17 | >, 18 | { 19 | type DB = L::DB; 20 | type Tx = L::Tx; 21 | type Error = L::Error; 22 | type HaltReason = L::HaltReason; 23 | type Spec = L::Spec; 24 | type BlockEnv = L::BlockEnv; 25 | type Precompiles = L::Precompiles; 26 | type Inspector = L::Inspector; 27 | 28 | fn block(&self) -> &Self::BlockEnv { 29 | either::for_both!(self, evm => evm.block()) 30 | } 31 | 32 | fn chain_id(&self) -> u64 { 33 | either::for_both!(self, evm => evm.chain_id()) 34 | } 35 | 36 | fn transact_raw( 37 | &mut self, 38 | tx: Self::Tx, 39 | ) -> Result, Self::Error> { 40 | either::for_both!(self, evm => evm.transact_raw(tx)) 41 | } 42 | 43 | fn transact( 44 | &mut self, 45 | tx: impl crate::IntoTxEnv, 46 | ) -> Result, Self::Error> { 47 | either::for_both!(self, evm => evm.transact(tx)) 48 | } 49 | 50 | fn transact_system_call( 51 | &mut self, 52 | caller: Address, 53 | contract: Address, 54 | data: Bytes, 55 | ) -> Result, Self::Error> { 56 | either::for_both!(self, evm => evm.transact_system_call(caller, contract, data)) 57 | } 58 | 59 | fn transact_commit( 60 | &mut self, 61 | tx: impl crate::IntoTxEnv, 62 | ) -> Result, Self::Error> 63 | where 64 | Self::DB: revm::DatabaseCommit, 65 | { 66 | either::for_both!(self, evm => evm.transact_commit(tx)) 67 | } 68 | 69 | fn finish(self) -> (Self::DB, EvmEnv) 70 | where 71 | Self: Sized, 72 | { 73 | either::for_both!(self, evm => evm.finish()) 74 | } 75 | 76 | fn into_db(self) -> Self::DB 77 | where 78 | Self: Sized, 79 | { 80 | either::for_both!(self, evm => evm.into_db()) 81 | } 82 | 83 | fn into_env(self) -> EvmEnv 84 | where 85 | Self: Sized, 86 | { 87 | either::for_both!(self, evm => evm.into_env()) 88 | } 89 | 90 | fn set_inspector_enabled(&mut self, enabled: bool) { 91 | either::for_both!(self, evm => evm.set_inspector_enabled(enabled)) 92 | } 93 | 94 | fn enable_inspector(&mut self) { 95 | either::for_both!(self, evm => evm.enable_inspector()) 96 | } 97 | 98 | fn disable_inspector(&mut self) { 99 | either::for_both!(self, evm => evm.disable_inspector()) 100 | } 101 | 102 | fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { 103 | either::for_both!(self, evm => evm.components()) 104 | } 105 | 106 | fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { 107 | either::for_both!(self, evm => evm.components_mut()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /crates/evm/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over EVM errors. 2 | 3 | use core::{any::Any, error::Error}; 4 | use revm::context_interface::result::{EVMError, InvalidTransaction}; 5 | 6 | /// Abstraction over transaction validation error. 7 | pub trait InvalidTxError: Error + Send + Sync + Any + 'static { 8 | /// Returns whether the error cause by transaction having a nonce lower than expected. 9 | fn is_nonce_too_low(&self) -> bool { 10 | self.as_invalid_tx_err() 11 | .is_some_and(|err| matches!(err, InvalidTransaction::NonceTooLow { .. })) 12 | } 13 | 14 | /// Returns whether the error is due to the transaction gas limit being higher than allowed. 15 | fn is_gas_limit_too_high(&self) -> bool { 16 | self.as_invalid_tx_err().is_some_and(|err| { 17 | matches!( 18 | err, 19 | InvalidTransaction::TxGasLimitGreaterThanCap { .. } 20 | | InvalidTransaction::CallerGasLimitMoreThanBlock 21 | ) 22 | }) 23 | } 24 | 25 | /// Returns whether the error is due to the transaction gas limit being lower than required. 26 | fn is_gas_limit_too_low(&self) -> bool { 27 | self.as_invalid_tx_err().is_some_and(|err| { 28 | matches!( 29 | err, 30 | InvalidTransaction::CallGasCostMoreThanGasLimit { .. } 31 | | InvalidTransaction::GasFloorMoreThanGasLimit { .. } 32 | ) 33 | }) 34 | } 35 | 36 | /// Returns the underlying [`InvalidTransaction`] if any. 37 | /// 38 | /// This is primarily used for error conversions, e.g. for rpc responses. 39 | fn as_invalid_tx_err(&self) -> Option<&InvalidTransaction>; 40 | } 41 | 42 | impl InvalidTxError for InvalidTransaction { 43 | fn as_invalid_tx_err(&self) -> Option<&InvalidTransaction> { 44 | Some(self) 45 | } 46 | } 47 | 48 | /// Abstraction over errors that can occur during EVM execution. 49 | /// 50 | /// It's assumed that errors can occur either because of an invalid transaction, meaning that other 51 | /// transaction might still result in successful execution, or because of a general EVM 52 | /// misconfiguration. 53 | /// 54 | /// If caller occurs a error different from [`EvmError::InvalidTransaction`], it should most likely 55 | /// be treated as fatal error flagging some EVM misconfiguration. 56 | pub trait EvmError: Sized + Error + Send + Sync + 'static { 57 | /// Errors which might occur as a result of an invalid transaction. i.e unrelated to general EVM 58 | /// configuration. 59 | type InvalidTransaction: InvalidTxError; 60 | 61 | /// Returns the [`EvmError::InvalidTransaction`] if the error is an invalid transaction error. 62 | fn as_invalid_tx_err(&self) -> Option<&Self::InvalidTransaction>; 63 | 64 | /// Attempts to convert the error into [`EvmError::InvalidTransaction`]. 65 | fn try_into_invalid_tx_err(self) -> Result; 66 | 67 | /// Returns `true` if the error is an invalid transaction error. 68 | fn is_invalid_tx_err(&self) -> bool { 69 | self.as_invalid_tx_err().is_some() 70 | } 71 | } 72 | 73 | impl EvmError for EVMError 74 | where 75 | DBError: Error + Send + Sync + 'static, 76 | TxError: InvalidTxError, 77 | { 78 | type InvalidTransaction = TxError; 79 | 80 | fn as_invalid_tx_err(&self) -> Option<&Self::InvalidTransaction> { 81 | match self { 82 | Self::Transaction(err) => Some(err), 83 | _ => None, 84 | } 85 | } 86 | 87 | fn try_into_invalid_tx_err(self) -> Result { 88 | match self { 89 | Self::Transaction(err) => Ok(err), 90 | err => Err(err), 91 | } 92 | } 93 | } 94 | 95 | #[cfg(feature = "op")] 96 | impl InvalidTxError for op_revm::OpTransactionError { 97 | fn as_invalid_tx_err(&self) -> Option<&InvalidTransaction> { 98 | match self { 99 | Self::Base(tx) => Some(tx), 100 | _ => None, 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /crates/evm/src/rpc/transaction.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | env::BlockEnvironment, 3 | rpc::{CallFees, CallFeesError}, 4 | EvmEnv, 5 | }; 6 | use alloy_primitives::{TxKind, U256}; 7 | use alloy_rpc_types_eth::request::{TransactionInputError, TransactionRequest}; 8 | use core::fmt::Debug; 9 | use revm::{context::TxEnv, context_interface::either::Either}; 10 | use thiserror::Error; 11 | 12 | /// Converts `self` into `T`. 13 | /// 14 | /// Should create an executable transaction environment using [`TransactionRequest`]. 15 | pub trait TryIntoTxEnv { 16 | /// An associated error that can occur during the conversion. 17 | type Err; 18 | 19 | /// Performs the conversion. 20 | fn try_into_tx_env(self, evm_env: &EvmEnv) -> Result; 21 | } 22 | 23 | /// An Ethereum specific transaction environment error than can occur during conversion from 24 | /// [`TransactionRequest`]. 25 | #[derive(Debug, Error)] 26 | pub enum EthTxEnvError { 27 | /// Error while decoding or validating transaction request fees. 28 | #[error(transparent)] 29 | CallFees(#[from] CallFeesError), 30 | /// Both data and input fields are set and not equal. 31 | #[error(transparent)] 32 | Input(#[from] TransactionInputError), 33 | } 34 | 35 | impl TryIntoTxEnv for TransactionRequest { 36 | type Err = EthTxEnvError; 37 | 38 | fn try_into_tx_env(self, evm_env: &EvmEnv) -> Result { 39 | // Ensure that if versioned hashes are set, they're not empty 40 | if self.blob_versioned_hashes.as_ref().is_some_and(|hashes| hashes.is_empty()) { 41 | return Err(CallFeesError::BlobTransactionMissingBlobHashes.into()); 42 | } 43 | 44 | let tx_type = self.minimal_tx_type() as u8; 45 | 46 | let Self { 47 | from, 48 | to, 49 | gas_price, 50 | max_fee_per_gas, 51 | max_priority_fee_per_gas, 52 | gas, 53 | value, 54 | input, 55 | nonce, 56 | access_list, 57 | chain_id, 58 | blob_versioned_hashes, 59 | max_fee_per_blob_gas, 60 | authorization_list, 61 | transaction_type: _, 62 | sidecar: _, 63 | } = self; 64 | 65 | let CallFees { max_priority_fee_per_gas, gas_price, max_fee_per_blob_gas } = 66 | CallFees::ensure_fees( 67 | gas_price.map(U256::from), 68 | max_fee_per_gas.map(U256::from), 69 | max_priority_fee_per_gas.map(U256::from), 70 | U256::from(evm_env.block_env().basefee()), 71 | blob_versioned_hashes.as_deref(), 72 | max_fee_per_blob_gas.map(U256::from), 73 | evm_env.block_env().blob_gasprice().map(U256::from), 74 | )?; 75 | 76 | let gas_limit = gas.unwrap_or( 77 | // Use maximum allowed gas limit. The reason for this 78 | // is that both Erigon and Geth use pre-configured gas cap even if 79 | // it's possible to derive the gas limit from the block: 80 | // 82 | evm_env.block_env().gas_limit(), 83 | ); 84 | 85 | let chain_id = chain_id.unwrap_or(evm_env.cfg_env().chain_id); 86 | 87 | let caller = from.unwrap_or_default(); 88 | 89 | let nonce = nonce.unwrap_or_default(); 90 | 91 | let env = TxEnv { 92 | tx_type, 93 | gas_limit, 94 | nonce, 95 | caller, 96 | gas_price: gas_price.saturating_to(), 97 | gas_priority_fee: max_priority_fee_per_gas.map(|v| v.saturating_to()), 98 | kind: to.unwrap_or(TxKind::Create), 99 | value: value.unwrap_or_default(), 100 | data: input.try_into_unique_input().map_err(EthTxEnvError::from)?.unwrap_or_default(), 101 | chain_id: Some(chain_id), 102 | access_list: access_list.unwrap_or_default(), 103 | // EIP-4844 fields 104 | blob_hashes: blob_versioned_hashes.unwrap_or_default(), 105 | max_fee_per_blob_gas: max_fee_per_blob_gas 106 | .map(|v| v.saturating_to()) 107 | .unwrap_or_default(), 108 | // EIP-7702 fields 109 | authorization_list: authorization_list 110 | .unwrap_or_default() 111 | .into_iter() 112 | .map(Either::Left) 113 | .collect(), 114 | }; 115 | 116 | Ok(env) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/evm/src/op/env.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | eth::{EvmEnvInput, NextEvmEnvAttributes}, 3 | EvmEnv, 4 | }; 5 | use alloy_consensus::BlockHeader; 6 | use alloy_op_hardforks::OpHardforks; 7 | use alloy_primitives::{ChainId, U256}; 8 | use op_revm::OpSpecId; 9 | use revm::{ 10 | context::{BlockEnv, CfgEnv}, 11 | context_interface::block::BlobExcessGasAndPrice, 12 | primitives::hardfork::SpecId, 13 | }; 14 | 15 | impl EvmEnv { 16 | /// Create a new `EvmEnv` with [`OpSpecId`] from a block `header`, `chain_id` and `chain_spec`. 17 | /// 18 | /// # Arguments 19 | /// 20 | /// * `header` - The block to make the env out of. 21 | /// * `chain_spec` - The chain hardfork description, must implement [`OpHardforks`]. 22 | /// * `chain_id` - The chain identifier. 23 | /// * `blob_params` - Optional parameters that sets limits on gas and count for blobs. 24 | pub fn for_op_block( 25 | header: impl BlockHeader, 26 | chain_spec: impl OpHardforks, 27 | chain_id: ChainId, 28 | ) -> Self { 29 | Self::for_op(EvmEnvInput::from_block_header(header), chain_spec, chain_id) 30 | } 31 | 32 | /// Create a new `EvmEnv` with [`SpecId`] from a parent block `header`, `chain_id` and 33 | /// `chain_spec`. 34 | /// 35 | /// # Arguments 36 | /// 37 | /// * `header` - The parent block to make the env out of. 38 | /// * `base_fee_per_gas` - Base fee per gas for the next block. 39 | /// * `chain_spec` - The chain hardfork description, must implement [`OpHardforks`]. 40 | /// * `chain_id` - The chain identifier. 41 | pub fn for_op_next_block( 42 | header: impl BlockHeader, 43 | attributes: NextEvmEnvAttributes, 44 | base_fee_per_gas: u64, 45 | chain_spec: impl OpHardforks, 46 | chain_id: ChainId, 47 | ) -> Self { 48 | Self::for_op( 49 | EvmEnvInput::for_next(header, attributes, base_fee_per_gas, None), 50 | chain_spec, 51 | chain_id, 52 | ) 53 | } 54 | 55 | fn for_op(input: EvmEnvInput, chain_spec: impl OpHardforks, chain_id: ChainId) -> Self { 56 | let spec = crate::op::spec_by_timestamp_after_bedrock(&chain_spec, input.timestamp); 57 | let cfg_env = CfgEnv::new().with_chain_id(chain_id).with_spec(spec); 58 | 59 | let blob_excess_gas_and_price = spec 60 | .into_eth_spec() 61 | .is_enabled_in(SpecId::CANCUN) 62 | .then_some(BlobExcessGasAndPrice { excess_blob_gas: 0, blob_gasprice: 1 }); 63 | 64 | let is_merge_active = spec.into_eth_spec() >= SpecId::MERGE; 65 | 66 | let block_env = BlockEnv { 67 | number: U256::from(input.number), 68 | beneficiary: input.beneficiary, 69 | timestamp: U256::from(input.timestamp), 70 | difficulty: if is_merge_active { U256::ZERO } else { input.difficulty }, 71 | prevrandao: if is_merge_active { input.mix_hash } else { None }, 72 | gas_limit: input.gas_limit, 73 | basefee: input.base_fee_per_gas, 74 | // EIP-4844 excess blob gas of this block, introduced in Cancun 75 | blob_excess_gas_and_price, 76 | }; 77 | 78 | Self::new(cfg_env, block_env) 79 | } 80 | } 81 | 82 | #[cfg(feature = "engine")] 83 | mod payload { 84 | use super::*; 85 | use op_alloy::rpc_types_engine::OpExecutionPayload; 86 | 87 | impl EvmEnv { 88 | /// Create a new `EvmEnv` with [`OpSpecId`] from a `payload`, `chain_id`, `chain_spec` and 89 | /// optional `blob_params`. 90 | /// 91 | /// # Arguments 92 | /// 93 | /// * `header` - The block to make the env out of. 94 | /// * `chain_spec` - The chain hardfork description, must implement [`OpHardforks`]. 95 | /// * `chain_id` - The chain identifier. 96 | /// * `blob_params` - Optional parameters that sets limits on gas and count for blobs. 97 | pub fn for_op_payload( 98 | payload: &OpExecutionPayload, 99 | chain_spec: impl OpHardforks, 100 | chain_id: ChainId, 101 | ) -> Self { 102 | Self::for_op(EvmEnvInput::from_op_payload(payload), chain_spec, chain_id) 103 | } 104 | } 105 | 106 | impl EvmEnvInput { 107 | pub(crate) fn from_op_payload(payload: &OpExecutionPayload) -> Self { 108 | Self { 109 | timestamp: payload.timestamp(), 110 | number: payload.block_number(), 111 | beneficiary: payload.as_v1().fee_recipient, 112 | mix_hash: Some(payload.as_v1().prev_randao), 113 | difficulty: payload.as_v1().prev_randao.into(), 114 | gas_limit: payload.as_v1().gas_limit, 115 | excess_blob_gas: payload.as_v3().map(|v| v.excess_blob_gas), 116 | base_fee_per_gas: payload.as_v1().base_fee_per_gas.saturating_to(), 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /crates/evm/src/block/state_changes.rs: -------------------------------------------------------------------------------- 1 | //! State changes that are not related to transactions. 2 | 3 | use super::{calc, BlockExecutionError}; 4 | use alloy_consensus::BlockHeader; 5 | use alloy_eips::eip4895::{Withdrawal, Withdrawals}; 6 | use alloy_hardforks::EthereumHardforks; 7 | use alloy_primitives::{map::HashMap, Address}; 8 | use revm::{ 9 | context::Block, 10 | state::{Account, AccountStatus, EvmState}, 11 | Database, 12 | }; 13 | 14 | /// Collect all balance changes at the end of the block. 15 | /// 16 | /// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular 17 | /// state changes (DAO fork). 18 | #[inline] 19 | pub fn post_block_balance_increments( 20 | spec: impl EthereumHardforks, 21 | block_env: impl Block, 22 | ommers: &[H], 23 | withdrawals: Option<&Withdrawals>, 24 | ) -> HashMap 25 | where 26 | H: BlockHeader, 27 | { 28 | let mut balance_increments = HashMap::with_capacity_and_hasher( 29 | withdrawals.map_or(ommers.len(), |w| w.len()), 30 | Default::default(), 31 | ); 32 | 33 | // Add block rewards if they are enabled. 34 | if let Some(base_block_reward) = 35 | calc::base_block_reward(&spec, block_env.number().saturating_to()) 36 | { 37 | // Ommer rewards 38 | for ommer in ommers { 39 | *balance_increments.entry(ommer.beneficiary()).or_default() += calc::ommer_reward( 40 | base_block_reward, 41 | block_env.number().saturating_to(), 42 | ommer.number(), 43 | ); 44 | } 45 | 46 | // Full block reward 47 | *balance_increments.entry(block_env.beneficiary()).or_default() += 48 | calc::block_reward(base_block_reward, ommers.len()); 49 | } 50 | 51 | // process withdrawals 52 | insert_post_block_withdrawals_balance_increments( 53 | spec, 54 | block_env.timestamp().saturating_to(), 55 | withdrawals.map(|w| w.as_slice()), 56 | &mut balance_increments, 57 | ); 58 | 59 | balance_increments 60 | } 61 | 62 | /// Returns a map of addresses to their balance increments if the Shanghai hardfork is active at the 63 | /// given timestamp. 64 | /// 65 | /// Zero-valued withdrawals are filtered out. 66 | #[inline] 67 | pub fn post_block_withdrawals_balance_increments( 68 | spec: impl EthereumHardforks, 69 | block_timestamp: u64, 70 | withdrawals: &[Withdrawal], 71 | ) -> HashMap { 72 | let mut balance_increments = 73 | HashMap::with_capacity_and_hasher(withdrawals.len(), Default::default()); 74 | insert_post_block_withdrawals_balance_increments( 75 | spec, 76 | block_timestamp, 77 | Some(withdrawals), 78 | &mut balance_increments, 79 | ); 80 | balance_increments 81 | } 82 | 83 | /// Applies all withdrawal balance increments if shanghai is active at the given timestamp to the 84 | /// given `balance_increments` map. 85 | /// 86 | /// Zero-valued withdrawals are filtered out. 87 | #[inline] 88 | pub fn insert_post_block_withdrawals_balance_increments( 89 | spec: impl EthereumHardforks, 90 | block_timestamp: u64, 91 | withdrawals: Option<&[Withdrawal]>, 92 | balance_increments: &mut HashMap, 93 | ) { 94 | // Process withdrawals 95 | if spec.is_shanghai_active_at_timestamp(block_timestamp) { 96 | if let Some(withdrawals) = withdrawals { 97 | for withdrawal in withdrawals { 98 | if withdrawal.amount > 0 { 99 | *balance_increments.entry(withdrawal.address).or_default() += 100 | withdrawal.amount_wei().to::(); 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | /// Creates an `EvmState` from a map of balance increments and the current state 108 | /// to load accounts from. No balance increment is done in the function. 109 | /// Zero balance increments are ignored and won't create state entries. 110 | pub fn balance_increment_state( 111 | balance_increments: &HashMap, 112 | state: &mut DB, 113 | ) -> Result 114 | where 115 | DB: Database, 116 | { 117 | let mut load_account = |address: &Address| -> Result<(Address, Account), BlockExecutionError> { 118 | let cache_account = state.basic(*address).map_err(|_| { 119 | BlockExecutionError::msg("could not load account for balance increment") 120 | })?; 121 | 122 | let account = cache_account.as_ref().ok_or_else(|| { 123 | BlockExecutionError::msg("could not load account for balance increment") 124 | })?; 125 | 126 | Ok(( 127 | *address, 128 | Account { 129 | info: account.clone(), 130 | storage: Default::default(), 131 | status: AccountStatus::Touched, 132 | transaction_id: 0, 133 | }, 134 | )) 135 | }; 136 | 137 | balance_increments 138 | .iter() 139 | .filter(|(_, &balance)| balance != 0) 140 | .map(|(addr, _)| load_account(addr)) 141 | .collect::>() 142 | } 143 | -------------------------------------------------------------------------------- /crates/op-evm/src/block/canyon.rs: -------------------------------------------------------------------------------- 1 | use alloy_evm::Database; 2 | use alloy_op_hardforks::OpHardforks; 3 | use alloy_primitives::{address, b256, hex, Address, Bytes, B256}; 4 | use revm::{primitives::HashMap, state::Bytecode, DatabaseCommit}; 5 | 6 | /// The address of the create2 deployer 7 | const CREATE_2_DEPLOYER_ADDR: Address = address!("0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2"); 8 | 9 | /// The codehash of the create2 deployer contract. 10 | const CREATE_2_DEPLOYER_CODEHASH: B256 = 11 | b256!("0xb0550b5b431e30d38000efb7107aaa0ade03d48a7198a140edda9d27134468b2"); 12 | 13 | /// The raw bytecode of the create2 deployer contract. 14 | const CREATE_2_DEPLOYER_BYTECODE: [u8; 1584] = hex!("6080604052600436106100435760003560e01c8063076c37b21461004f578063481286e61461007157806356299481146100ba57806366cfa057146100da57600080fd5b3661004a57005b600080fd5b34801561005b57600080fd5b5061006f61006a366004610327565b6100fa565b005b34801561007d57600080fd5b5061009161008c366004610327565b61014a565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b3480156100c657600080fd5b506100916100d5366004610349565b61015d565b3480156100e657600080fd5b5061006f6100f53660046103ca565b610172565b61014582826040518060200161010f9061031a565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe082820381018352601f90910116604052610183565b505050565b600061015683836102e7565b9392505050565b600061016a8484846102f0565b949350505050565b61017d838383610183565b50505050565b6000834710156101f4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f437265617465323a20696e73756666696369656e742062616c616e636500000060448201526064015b60405180910390fd5b815160000361025f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f437265617465323a2062797465636f6465206c656e677468206973207a65726f60448201526064016101eb565b8282516020840186f5905073ffffffffffffffffffffffffffffffffffffffff8116610156576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f437265617465323a204661696c6564206f6e206465706c6f790000000000000060448201526064016101eb565b60006101568383305b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b61014e806104ad83390190565b6000806040838503121561033a57600080fd5b50508035926020909101359150565b60008060006060848603121561035e57600080fd5b8335925060208401359150604084013573ffffffffffffffffffffffffffffffffffffffff8116811461039057600080fd5b809150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000606084860312156103df57600080fd5b8335925060208401359150604084013567ffffffffffffffff8082111561040557600080fd5b818601915086601f83011261041957600080fd5b81358181111561042b5761042b61039b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156104715761047161039b565b8160405282815289602084870101111561048a57600080fd5b826020860160208301376000602084830101528095505050505050925092509256fe608060405234801561001057600080fd5b5061012e806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063249cb3fa14602d575b600080fd5b603c603836600460b1565b604e565b60405190815260200160405180910390f35b60008281526020818152604080832073ffffffffffffffffffffffffffffffffffffffff8516845290915281205460ff16608857600060aa565b7fa2ef4600d742022d532d4747cb3547474667d6f13804902513b2ec01c848f4b45b9392505050565b6000806040838503121560c357600080fd5b82359150602083013573ffffffffffffffffffffffffffffffffffffffff8116811460ed57600080fd5b80915050925092905056fea26469706673582212205ffd4e6cede7d06a5daf93d48d0541fc68189eeb16608c1999a82063b666eb1164736f6c63430008130033a2646970667358221220fdc4a0fe96e3b21c108ca155438d37c9143fb01278a3c1d274948bad89c564ba64736f6c63430008130033"); 15 | 16 | /// The Canyon hardfork issues an irregular state transition that force-deploys the create2 17 | /// deployer contract. This is done by directly setting the code of the create2 deployer account 18 | /// prior to executing any transactions on the timestamp activation of the fork. 19 | pub(crate) fn ensure_create2_deployer( 20 | chain_spec: impl OpHardforks, 21 | timestamp: u64, 22 | db: &mut DB, 23 | ) -> Result<(), DB::Error> 24 | where 25 | DB: Database + DatabaseCommit, 26 | { 27 | // If the canyon hardfork is active at the current timestamp, and it was not active at the 28 | // previous block timestamp (heuristically, block time is not perfectly constant at 2s), and the 29 | // chain is an optimism chain, then we need to force-deploy the create2 deployer contract. 30 | if chain_spec.is_canyon_active_at_timestamp(timestamp) 31 | && !chain_spec.is_canyon_active_at_timestamp(timestamp.saturating_sub(2)) 32 | { 33 | // Load the create2 deployer account from the cache. 34 | let mut acc_info = db.basic(CREATE_2_DEPLOYER_ADDR)?.unwrap_or_default(); 35 | 36 | // Update the account info with the create2 deployer codehash and bytecode. 37 | acc_info.code_hash = CREATE_2_DEPLOYER_CODEHASH; 38 | acc_info.code = Some(Bytecode::new_raw(Bytes::from_static(&CREATE_2_DEPLOYER_BYTECODE))); 39 | 40 | // Convert the cache account back into a revm account and mark it as touched. 41 | let mut revm_acc: revm::state::Account = acc_info.into(); 42 | revm_acc.mark_touch(); 43 | 44 | // Commit the create2 deployer account to the database. 45 | db.commit(HashMap::from_iter([(CREATE_2_DEPLOYER_ADDR, revm_acc)])); 46 | return Ok(()); 47 | } 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /crates/evm/src/block/calc.rs: -------------------------------------------------------------------------------- 1 | //! Helpers to perform common calculations. 2 | 3 | use alloy_consensus::constants::ETH_TO_WEI; 4 | use alloy_hardforks::EthereumHardforks; 5 | use alloy_primitives::BlockNumber; 6 | 7 | /// Calculates the base block reward. 8 | /// 9 | /// The base block reward is defined as: 10 | /// 11 | /// - For Paris and later: `None` 12 | /// - For Petersburg and later: `Some(2 ETH)` 13 | /// - For Byzantium and later: `Some(3 ETH)` 14 | /// - Otherwise: `Some(5 ETH)` 15 | /// 16 | /// # Note 17 | /// 18 | /// This does not include the reward for including ommers. To calculate the full block reward, see 19 | /// [`block_reward`]. 20 | /// 21 | /// # References 22 | /// 23 | /// - Definition: [Yellow Paper][yp] (page 15, 11.3) 24 | /// 25 | /// [yp]: https://ethereum.github.io/yellowpaper/paper.pdf 26 | pub fn base_block_reward(spec: impl EthereumHardforks, block_number: BlockNumber) -> Option { 27 | if spec.is_paris_active_at_block(block_number) { 28 | None 29 | } else { 30 | Some(base_block_reward_pre_merge(spec, block_number)) 31 | } 32 | } 33 | 34 | /// Calculates the base block reward __before__ the merge (Paris hardfork). 35 | /// 36 | /// Caution: The caller must ensure that the block number is before the merge. 37 | pub fn base_block_reward_pre_merge( 38 | spec: impl EthereumHardforks, 39 | block_number: BlockNumber, 40 | ) -> u128 { 41 | if spec.is_constantinople_active_at_block(block_number) { 42 | ETH_TO_WEI * 2 43 | } else if spec.is_byzantium_active_at_block(block_number) { 44 | ETH_TO_WEI * 3 45 | } else { 46 | ETH_TO_WEI * 5 47 | } 48 | } 49 | 50 | /// Calculates the reward for a block, including the reward for ommer inclusion. 51 | /// 52 | /// The base reward should be calculated using [`base_block_reward`]. `ommers` represents the number 53 | /// of ommers included in the block. 54 | /// 55 | /// # Examples 56 | /// 57 | /// ``` 58 | /// # use alloy_hardforks::EthereumChainHardforks; 59 | /// # use alloy_evm::block::calc::{base_block_reward, block_reward}; 60 | /// # use alloy_consensus::constants::ETH_TO_WEI; 61 | /// # use alloy_primitives::U256; 62 | /// # 63 | /// // This is block 126 on mainnet. 64 | /// let block_number = 126; 65 | /// let number_of_ommers = 1; 66 | /// 67 | /// let reward = base_block_reward(EthereumChainHardforks::mainnet(), block_number) 68 | /// .map(|reward| block_reward(reward, 1)); 69 | /// 70 | /// // The base block reward is 5 ETH, and the ommer inclusion reward is 1/32th of 5 ETH. 71 | /// assert_eq!(reward.unwrap(), ETH_TO_WEI * 5 + ((ETH_TO_WEI * 5) >> 5)); 72 | /// ``` 73 | /// 74 | /// # References 75 | /// 76 | /// - Definition: [Yellow Paper][yp] (page 15, 11.3) 77 | /// 78 | /// [yp]: https://ethereum.github.io/yellowpaper/paper.pdf 79 | pub const fn block_reward(base_block_reward: u128, ommers: usize) -> u128 { 80 | base_block_reward + (base_block_reward >> 5) * ommers as u128 81 | } 82 | 83 | /// Calculate the reward for an ommer. 84 | /// 85 | /// # Application 86 | /// 87 | /// Rewards are accumulative, so they should be added to the beneficiary addresses in addition to 88 | /// any other rewards from the same block. 89 | /// 90 | /// From the yellow paper (page 15): 91 | /// 92 | /// > If there are collisions of the beneficiary addresses between ommers and the block (i.e. two 93 | /// > ommers with the same beneficiary address or an ommer with the same beneficiary address as the 94 | /// > present block), additions are applied cumulatively. 95 | /// 96 | /// # References 97 | /// 98 | /// - Implementation: [OpenEthereum][oe] 99 | /// - Definition: [Yellow Paper][yp] (page 15, 11.3) 100 | /// 101 | /// [oe]: https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/crates/ethcore/src/ethereum/ethash.rs#L319-L333 102 | /// [yp]: https://ethereum.github.io/yellowpaper/paper.pdf 103 | pub const fn ommer_reward( 104 | base_block_reward: u128, 105 | block_number: BlockNumber, 106 | ommer_block_number: BlockNumber, 107 | ) -> u128 { 108 | ((8 + ommer_block_number - block_number) as u128 * base_block_reward) >> 3 109 | } 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | use super::*; 114 | use alloy_hardforks::EthereumChainHardforks; 115 | use alloy_primitives::U256; 116 | 117 | #[test] 118 | fn calc_base_block_reward() { 119 | // ((block number, td), reward) 120 | let cases = [ 121 | // Pre-byzantium 122 | ((0, U256::ZERO), Some(ETH_TO_WEI * 5)), 123 | // Byzantium 124 | ((4370000, U256::ZERO), Some(ETH_TO_WEI * 3)), 125 | // Petersburg 126 | ((7280000, U256::ZERO), Some(ETH_TO_WEI * 2)), 127 | // Merge 128 | ((15537394, U256::from(58_750_000_000_000_000_000_000_u128)), None), 129 | ]; 130 | 131 | for ((block_number, _td), expected_reward) in cases { 132 | assert_eq!( 133 | base_block_reward(EthereumChainHardforks::mainnet(), block_number), 134 | expected_reward 135 | ); 136 | } 137 | } 138 | 139 | #[test] 140 | fn calc_full_block_reward() { 141 | let base_reward = ETH_TO_WEI; 142 | let one_thirty_twoth_reward = base_reward >> 5; 143 | 144 | // (num_ommers, reward) 145 | let cases = [ 146 | (0, base_reward), 147 | (1, base_reward + one_thirty_twoth_reward), 148 | (2, base_reward + one_thirty_twoth_reward * 2), 149 | ]; 150 | 151 | for (num_ommers, expected_reward) in cases { 152 | assert_eq!(block_reward(base_reward, num_ommers), expected_reward); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /crates/evm/src/env.rs: -------------------------------------------------------------------------------- 1 | //! Configuration types for EVM environment. 2 | 3 | use core::fmt::Debug; 4 | 5 | use alloy_primitives::U256; 6 | use revm::{ 7 | context::{BlockEnv, CfgEnv}, 8 | primitives::hardfork::SpecId, 9 | }; 10 | 11 | /// Container type that holds both the configuration and block environment for EVM execution. 12 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 13 | pub struct EvmEnv { 14 | /// The configuration environment with handler settings 15 | pub cfg_env: CfgEnv, 16 | /// The block environment containing block-specific data 17 | pub block_env: BlockEnv, 18 | } 19 | 20 | impl EvmEnv { 21 | /// Create a new `EvmEnv` from its components. 22 | /// 23 | /// # Arguments 24 | /// 25 | /// * `cfg_env_with_handler_cfg` - The configuration environment with handler settings 26 | /// * `block` - The block environment containing block-specific data 27 | pub const fn new(cfg_env: CfgEnv, block_env: BlockEnv) -> Self { 28 | Self { cfg_env, block_env } 29 | } 30 | } 31 | 32 | impl EvmEnv { 33 | /// Sets an extension on the environment. 34 | pub fn map_block_env( 35 | self, 36 | f: impl FnOnce(BlockEnv) -> NewBlockEnv, 37 | ) -> EvmEnv { 38 | let Self { cfg_env, block_env } = self; 39 | EvmEnv { cfg_env, block_env: f(block_env) } 40 | } 41 | 42 | /// Returns a reference to the block environment. 43 | pub const fn block_env(&self) -> &BlockEnv { 44 | &self.block_env 45 | } 46 | 47 | /// Returns a reference to the configuration environment. 48 | pub const fn cfg_env(&self) -> &CfgEnv { 49 | &self.cfg_env 50 | } 51 | 52 | /// Returns the chain ID of the environment. 53 | pub const fn chainid(&self) -> u64 { 54 | self.cfg_env.chain_id 55 | } 56 | 57 | /// Returns the spec id of the chain 58 | pub const fn spec_id(&self) -> &Spec { 59 | &self.cfg_env.spec 60 | } 61 | 62 | /// Overrides the configured block number 63 | pub fn with_block_number(mut self, number: U256) -> Self { 64 | self.block_env.inner_mut().number = number; 65 | self 66 | } 67 | 68 | /// Convenience function that overrides the configured block number with the given 69 | /// `Some(number)`. 70 | /// 71 | /// This is intended for block overrides. 72 | pub fn with_block_number_opt(mut self, number: Option) -> Self { 73 | if let Some(number) = number { 74 | self.block_env.inner_mut().number = number; 75 | } 76 | self 77 | } 78 | 79 | /// Sets the block number if provided. 80 | pub fn set_block_number_opt(&mut self, number: Option) -> &mut Self { 81 | if let Some(number) = number { 82 | self.block_env.inner_mut().number = number; 83 | } 84 | self 85 | } 86 | 87 | /// Overrides the configured block timestamp. 88 | pub fn with_timestamp(mut self, timestamp: U256) -> Self { 89 | self.block_env.inner_mut().timestamp = timestamp; 90 | self 91 | } 92 | 93 | /// Convenience function that overrides the configured block timestamp with the given 94 | /// `Some(timestamp)`. 95 | /// 96 | /// This is intended for block overrides. 97 | pub fn with_timestamp_opt(mut self, timestamp: Option) -> Self { 98 | if let Some(timestamp) = timestamp { 99 | self.block_env.inner_mut().timestamp = timestamp; 100 | } 101 | self 102 | } 103 | 104 | /// Sets the block timestamp if provided. 105 | pub fn set_timestamp_opt(&mut self, timestamp: Option) -> &mut Self { 106 | if let Some(timestamp) = timestamp { 107 | self.block_env.inner_mut().timestamp = timestamp; 108 | } 109 | self 110 | } 111 | 112 | /// Overrides the configured block base fee. 113 | pub fn with_base_fee(mut self, base_fee: u64) -> Self { 114 | self.block_env.inner_mut().basefee = base_fee; 115 | self 116 | } 117 | 118 | /// Convenience function that overrides the configured block base fee with the given 119 | /// `Some(base_fee)`. 120 | /// 121 | /// This is intended for block overrides. 122 | pub fn with_base_fee_opt(mut self, base_fee: Option) -> Self { 123 | if let Some(base_fee) = base_fee { 124 | self.block_env.inner_mut().basefee = base_fee; 125 | } 126 | self 127 | } 128 | 129 | /// Sets the block base fee if provided. 130 | pub fn set_base_fee_opt(&mut self, base_fee: Option) -> &mut Self { 131 | if let Some(base_fee) = base_fee { 132 | self.block_env.inner_mut().basefee = base_fee; 133 | } 134 | self 135 | } 136 | } 137 | 138 | impl From<(CfgEnv, BlockEnv)> for EvmEnv { 139 | fn from((cfg_env, block_env): (CfgEnv, BlockEnv)) -> Self { 140 | Self { cfg_env, block_env } 141 | } 142 | } 143 | 144 | /// Trait for types that can be used as a block environment. 145 | /// 146 | /// Assumes that the type wraps an inner [`revm::context::BlockEnv`]. 147 | pub trait BlockEnvironment: revm::context::Block + Clone + Debug + Send + Sync + 'static { 148 | /// Returns a mutable reference to the inner [`revm::context::BlockEnv`]. 149 | fn inner_mut(&mut self) -> &mut revm::context::BlockEnv; 150 | } 151 | 152 | impl BlockEnvironment for BlockEnv { 153 | fn inner_mut(&mut self) -> &mut revm::context::BlockEnv { 154 | self 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /crates/evm/src/tracing.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for tracing. 2 | 3 | use crate::{Evm, IntoTxEnv}; 4 | use core::{fmt::Debug, iter::Peekable}; 5 | use revm::{ 6 | context::result::{ExecutionResult, ResultAndState}, 7 | state::EvmState, 8 | DatabaseCommit, 9 | }; 10 | 11 | /// A helper type for tracing transactions. 12 | #[derive(Debug, Clone)] 13 | pub struct TxTracer { 14 | evm: E, 15 | fused_inspector: E::Inspector, 16 | } 17 | 18 | /// Container type for context exposed in [`TxTracer`]. 19 | #[derive(Debug)] 20 | pub struct TracingCtx<'a, T, E: Evm> { 21 | /// The transaction that was just executed. 22 | pub tx: T, 23 | /// Result of transaction execution. 24 | pub result: ExecutionResult, 25 | /// State changes after transaction. 26 | pub state: &'a EvmState, 27 | /// Inspector state after transaction. 28 | pub inspector: &'a mut E::Inspector, 29 | /// Database used when executing the transaction, _before_ committing the state changes. 30 | pub db: &'a mut E::DB, 31 | /// Fused inspector. 32 | fused_inspector: &'a E::Inspector, 33 | /// Whether the inspector was fused. 34 | was_fused: &'a mut bool, 35 | } 36 | 37 | impl<'a, T, E: Evm> TracingCtx<'a, T, E> { 38 | /// Fuses the inspector and returns the current inspector state. 39 | pub fn take_inspector(&mut self) -> E::Inspector { 40 | *self.was_fused = true; 41 | core::mem::replace(self.inspector, self.fused_inspector.clone()) 42 | } 43 | } 44 | 45 | impl> TxTracer { 46 | /// Creates a new [`TxTracer`] instance. 47 | pub fn new(mut evm: E) -> Self { 48 | Self { fused_inspector: evm.inspector_mut().clone(), evm } 49 | } 50 | 51 | fn fuse_inspector(&mut self) -> E::Inspector { 52 | core::mem::replace(self.evm.inspector_mut(), self.fused_inspector.clone()) 53 | } 54 | 55 | /// Executes a transaction, and returns its outcome along with the inspector state. 56 | pub fn trace( 57 | &mut self, 58 | tx: impl IntoTxEnv, 59 | ) -> Result, E::Error> { 60 | let result = self.evm.transact_commit(tx); 61 | let inspector = self.fuse_inspector(); 62 | Ok(TraceOutput { result: result?, inspector }) 63 | } 64 | 65 | /// Executes multiple transactions, applies the closure to each transaction result, and returns 66 | /// the outcomes. 67 | #[expect(clippy::type_complexity)] 68 | pub fn trace_many( 69 | &mut self, 70 | txs: Txs, 71 | mut f: F, 72 | ) -> TracerIter<'_, E, Txs::IntoIter, impl FnMut(TracingCtx<'_, T, E>) -> Result> 73 | where 74 | T: IntoTxEnv + Clone, 75 | Txs: IntoIterator, 76 | F: FnMut(TracingCtx<'_, Txs::Item, E>) -> O, 77 | { 78 | self.try_trace_many(txs, move |ctx| Ok(f(ctx))) 79 | } 80 | 81 | /// Same as [`TxTracer::trace_many`], but operates on closures returning [`Result`]s. 82 | pub fn try_trace_many( 83 | &mut self, 84 | txs: Txs, 85 | hook: F, 86 | ) -> TracerIter<'_, E, Txs::IntoIter, F> 87 | where 88 | T: IntoTxEnv + Clone, 89 | Txs: IntoIterator, 90 | F: FnMut(TracingCtx<'_, T, E>) -> Result, 91 | Err: From, 92 | { 93 | TracerIter { 94 | inner: self, 95 | txs: txs.into_iter().peekable(), 96 | hook, 97 | skip_last_commit: true, 98 | fuse: true, 99 | } 100 | } 101 | } 102 | 103 | /// Output of tracing a transaction. 104 | #[derive(Debug, Clone)] 105 | pub struct TraceOutput { 106 | /// Inner EVM output. 107 | pub result: ExecutionResult, 108 | /// Inspector state at the end of the execution. 109 | pub inspector: I, 110 | } 111 | 112 | /// Iterator used by tracer. 113 | #[derive(derive_more::Debug)] 114 | #[debug(bound(E::Inspector: Debug))] 115 | pub struct TracerIter<'a, E: Evm, Txs: Iterator, F> { 116 | inner: &'a mut TxTracer, 117 | txs: Peekable, 118 | hook: F, 119 | skip_last_commit: bool, 120 | fuse: bool, 121 | } 122 | 123 | impl TracerIter<'_, E, Txs, F> { 124 | /// Flips the `skip_last_commit` flag thus making sure all transaction are committed. 125 | /// 126 | /// We are skipping last commit by default as it's expected that when tracing users are mostly 127 | /// interested in tracer output rather than in a state after it. 128 | pub const fn commit_last_tx(mut self) -> Self { 129 | self.skip_last_commit = false; 130 | self 131 | } 132 | 133 | /// Disables inspector fusing on every transaction and expects user to fuse it manually. 134 | pub const fn no_fuse(mut self) -> Self { 135 | self.fuse = false; 136 | self 137 | } 138 | } 139 | 140 | impl Iterator for TracerIter<'_, E, Txs, F> 141 | where 142 | E: Evm, 143 | T: IntoTxEnv + Clone, 144 | Txs: Iterator, 145 | Err: From, 146 | F: FnMut(TracingCtx<'_, T, E>) -> Result, 147 | { 148 | type Item = Result; 149 | 150 | fn next(&mut self) -> Option { 151 | let tx = self.txs.next()?; 152 | let result = self.inner.evm.transact(tx.clone()); 153 | 154 | let TxTracer { evm, fused_inspector } = self.inner; 155 | let (db, inspector, _) = evm.components_mut(); 156 | 157 | let Ok(ResultAndState { result, state }) = result else { 158 | return None; 159 | }; 160 | let mut was_fused = false; 161 | let output = (self.hook)(TracingCtx { 162 | tx, 163 | result, 164 | state: &state, 165 | inspector, 166 | db, 167 | fused_inspector: &*fused_inspector, 168 | was_fused: &mut was_fused, 169 | }); 170 | 171 | // Only commit next transaction if `skip_last_commit` is disabled or there is a next 172 | // transaction. 173 | if !self.skip_last_commit || self.txs.peek().is_some() { 174 | db.commit(state); 175 | } 176 | 177 | if self.fuse && !was_fused { 178 | self.inner.fuse_inspector(); 179 | } 180 | 181 | Some(output) 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /crates/evm/src/op/spec_id.rs: -------------------------------------------------------------------------------- 1 | use alloy_consensus::BlockHeader; 2 | use alloy_op_hardforks::OpHardforks; 3 | use op_revm::OpSpecId; 4 | 5 | /// Map the latest active hardfork at the given header to a revm [`OpSpecId`]. 6 | pub fn spec(chain_spec: impl OpHardforks, header: impl BlockHeader) -> OpSpecId { 7 | spec_by_timestamp_after_bedrock(chain_spec, header.timestamp()) 8 | } 9 | 10 | /// Returns the revm [`OpSpecId`] at the given timestamp. 11 | /// 12 | /// # Note 13 | /// 14 | /// This is only intended to be used after the Bedrock, when hardforks are activated by 15 | /// timestamp. 16 | pub fn spec_by_timestamp_after_bedrock(chain_spec: impl OpHardforks, timestamp: u64) -> OpSpecId { 17 | if chain_spec.is_interop_active_at_timestamp(timestamp) { 18 | OpSpecId::INTEROP 19 | } else if chain_spec.is_jovian_active_at_timestamp(timestamp) { 20 | OpSpecId::JOVIAN 21 | } else if chain_spec.is_isthmus_active_at_timestamp(timestamp) { 22 | OpSpecId::ISTHMUS 23 | } else if chain_spec.is_holocene_active_at_timestamp(timestamp) { 24 | OpSpecId::HOLOCENE 25 | } else if chain_spec.is_granite_active_at_timestamp(timestamp) { 26 | OpSpecId::GRANITE 27 | } else if chain_spec.is_fjord_active_at_timestamp(timestamp) { 28 | OpSpecId::FJORD 29 | } else if chain_spec.is_ecotone_active_at_timestamp(timestamp) { 30 | OpSpecId::ECOTONE 31 | } else if chain_spec.is_canyon_active_at_timestamp(timestamp) { 32 | OpSpecId::CANYON 33 | } else if chain_spec.is_regolith_active_at_timestamp(timestamp) { 34 | OpSpecId::REGOLITH 35 | } else { 36 | OpSpecId::BEDROCK 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod tests { 42 | use super::*; 43 | use alloy_consensus::Header; 44 | use alloy_hardforks::EthereumHardfork; 45 | use alloy_op_hardforks::{ 46 | EthereumHardforks, ForkCondition, OpChainHardforks, OpHardfork, 47 | OP_MAINNET_CANYON_TIMESTAMP, OP_MAINNET_ECOTONE_TIMESTAMP, OP_MAINNET_FJORD_TIMESTAMP, 48 | OP_MAINNET_GRANITE_TIMESTAMP, OP_MAINNET_HOLOCENE_TIMESTAMP, OP_MAINNET_ISTHMUS_TIMESTAMP, 49 | OP_MAINNET_JOVIAN_TIMESTAMP, OP_MAINNET_REGOLITH_TIMESTAMP, 50 | }; 51 | use alloy_primitives::BlockTimestamp; 52 | 53 | struct FakeHardfork { 54 | fork: OpHardfork, 55 | cond: ForkCondition, 56 | } 57 | 58 | impl FakeHardfork { 59 | fn interop() -> Self { 60 | Self::from_timestamp_zero(OpHardfork::Interop) 61 | } 62 | 63 | fn jovian() -> Self { 64 | Self::from_timestamp_zero(OpHardfork::Jovian) 65 | } 66 | 67 | fn isthmus() -> Self { 68 | Self::from_timestamp_zero(OpHardfork::Isthmus) 69 | } 70 | 71 | fn holocene() -> Self { 72 | Self::from_timestamp_zero(OpHardfork::Holocene) 73 | } 74 | 75 | fn granite() -> Self { 76 | Self::from_timestamp_zero(OpHardfork::Granite) 77 | } 78 | 79 | fn fjord() -> Self { 80 | Self::from_timestamp_zero(OpHardfork::Fjord) 81 | } 82 | 83 | fn ecotone() -> Self { 84 | Self::from_timestamp_zero(OpHardfork::Ecotone) 85 | } 86 | 87 | fn canyon() -> Self { 88 | Self::from_timestamp_zero(OpHardfork::Canyon) 89 | } 90 | 91 | fn regolith() -> Self { 92 | Self::from_timestamp_zero(OpHardfork::Regolith) 93 | } 94 | 95 | fn bedrock() -> Self { 96 | Self::from_block_zero(OpHardfork::Bedrock) 97 | } 98 | 99 | fn from_block_zero(fork: OpHardfork) -> Self { 100 | Self { fork, cond: ForkCondition::Block(0) } 101 | } 102 | 103 | fn from_timestamp_zero(fork: OpHardfork) -> Self { 104 | Self { fork, cond: ForkCondition::Timestamp(0) } 105 | } 106 | } 107 | 108 | impl EthereumHardforks for FakeHardfork { 109 | fn ethereum_fork_activation(&self, _: EthereumHardfork) -> ForkCondition { 110 | unimplemented!() 111 | } 112 | } 113 | 114 | impl OpHardforks for FakeHardfork { 115 | fn op_fork_activation(&self, fork: OpHardfork) -> ForkCondition { 116 | if fork == self.fork { 117 | self.cond 118 | } else { 119 | ForkCondition::Never 120 | } 121 | } 122 | } 123 | 124 | #[test_case::test_case(FakeHardfork::interop(), OpSpecId::INTEROP; "Interop")] 125 | #[test_case::test_case(FakeHardfork::jovian(), OpSpecId::JOVIAN; "Jovian")] 126 | #[test_case::test_case(FakeHardfork::isthmus(), OpSpecId::ISTHMUS; "Isthmus")] 127 | #[test_case::test_case(FakeHardfork::holocene(), OpSpecId::HOLOCENE; "Holocene")] 128 | #[test_case::test_case(FakeHardfork::granite(), OpSpecId::GRANITE; "Granite")] 129 | #[test_case::test_case(FakeHardfork::fjord(), OpSpecId::FJORD; "Fjord")] 130 | #[test_case::test_case(FakeHardfork::ecotone(), OpSpecId::ECOTONE; "Ecotone")] 131 | #[test_case::test_case(FakeHardfork::canyon(), OpSpecId::CANYON; "Canyon")] 132 | #[test_case::test_case(FakeHardfork::regolith(), OpSpecId::REGOLITH; "Regolith")] 133 | #[test_case::test_case(FakeHardfork::bedrock(), OpSpecId::BEDROCK; "Bedrock")] 134 | fn test_spec_maps_hardfork_successfully(fork: impl OpHardforks, expected_spec: OpSpecId) { 135 | let header = Header::default(); 136 | let actual_spec = spec(fork, &header); 137 | 138 | assert_eq!(actual_spec, expected_spec); 139 | } 140 | 141 | #[test_case::test_case(OP_MAINNET_JOVIAN_TIMESTAMP, OpSpecId::JOVIAN; "Jovian")] 142 | #[test_case::test_case(OP_MAINNET_ISTHMUS_TIMESTAMP, OpSpecId::ISTHMUS; "Isthmus")] 143 | #[test_case::test_case(OP_MAINNET_HOLOCENE_TIMESTAMP, OpSpecId::HOLOCENE; "Holocene")] 144 | #[test_case::test_case(OP_MAINNET_GRANITE_TIMESTAMP, OpSpecId::GRANITE; "Granite")] 145 | #[test_case::test_case(OP_MAINNET_FJORD_TIMESTAMP, OpSpecId::FJORD; "Fjord")] 146 | #[test_case::test_case(OP_MAINNET_ECOTONE_TIMESTAMP, OpSpecId::ECOTONE; "Ecotone")] 147 | #[test_case::test_case(OP_MAINNET_CANYON_TIMESTAMP, OpSpecId::CANYON; "Canyon")] 148 | #[test_case::test_case(OP_MAINNET_REGOLITH_TIMESTAMP, OpSpecId::REGOLITH; "Regolith")] 149 | fn test_op_spec_maps_hardfork_successfully(timestamp: BlockTimestamp, expected_spec: OpSpecId) { 150 | let fork = OpChainHardforks::op_mainnet(); 151 | let header = Header { timestamp, ..Default::default() }; 152 | let actual_spec = spec(&fork, &header); 153 | 154 | assert_eq!(actual_spec, expected_spec); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | test: 17 | name: test ${{ matrix.os }} ${{ matrix.rust }} ${{ matrix.flags }} 18 | timeout-minutes: 30 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: ["ubuntu-latest", "windows-latest"] 24 | rust: 25 | - "stable" 26 | - "nightly" 27 | - "1.88" # MSRV 28 | flags: 29 | # No features 30 | - "--no-default-features" 31 | # Default features 32 | - "" 33 | # All features 34 | - "--all-features" 35 | exclude: 36 | # All features on MSRV 37 | - rust: "1.88" # MSRV 38 | flags: "--all-features" 39 | # gmp doesn't compile on Windows 40 | - os: windows-latest 41 | flags: "--all-features" 42 | include: 43 | # Windows: all features except gmp 44 | - os: windows-latest 45 | rust: stable 46 | flags: "--features std,secp256k1,op,overrides,call-util,engine,asm-keccak,rpc" 47 | - os: windows-latest 48 | rust: nightly 49 | flags: "--features std,secp256k1,op,overrides,call-util,engine,asm-keccak,rpc" 50 | steps: 51 | - uses: actions/checkout@v5 52 | - uses: dtolnay/rust-toolchain@master 53 | with: 54 | toolchain: ${{ matrix.rust }} 55 | - uses: Swatinem/rust-cache@v2 56 | with: 57 | cache-on-failure: true 58 | # Only run tests on latest stable and above 59 | - name: Install cargo-nextest 60 | if: ${{ matrix.rust != '1.88' }} # MSRV 61 | uses: taiki-e/install-action@nextest 62 | - name: build 63 | if: ${{ matrix.rust == '1.88' }} # MSRV 64 | run: cargo build --workspace ${{ matrix.flags }} 65 | - name: test 66 | shell: bash 67 | if: ${{ matrix.rust != '1.88' }} # MSRV 68 | run: cargo nextest run --workspace ${{ matrix.flags }} 69 | 70 | doctest: 71 | runs-on: ubuntu-latest 72 | timeout-minutes: 30 73 | steps: 74 | - uses: actions/checkout@v5 75 | - uses: dtolnay/rust-toolchain@stable 76 | - uses: Swatinem/rust-cache@v2 77 | with: 78 | cache-on-failure: true 79 | - run: cargo test --workspace --doc 80 | - run: cargo test --all-features --workspace --doc 81 | 82 | wasm-unknown: 83 | runs-on: ubuntu-latest 84 | timeout-minutes: 30 85 | steps: 86 | - uses: actions/checkout@v5 87 | - uses: dtolnay/rust-toolchain@stable 88 | with: 89 | target: wasm32-unknown-unknown 90 | - uses: taiki-e/install-action@cargo-hack 91 | - uses: Swatinem/rust-cache@v2 92 | with: 93 | cache-on-failure: true 94 | - name: cargo hack 95 | run: | 96 | cargo hack build --workspace --ignore-unknown-features --features ws --target wasm32-unknown-unknown --no-default-features 97 | 98 | wasm-wasi: 99 | runs-on: ubuntu-latest 100 | timeout-minutes: 30 101 | steps: 102 | - uses: actions/checkout@v5 103 | - uses: dtolnay/rust-toolchain@stable 104 | with: 105 | target: wasm32-wasip1 106 | - uses: taiki-e/install-action@cargo-hack 107 | - uses: Swatinem/rust-cache@v2 108 | with: 109 | cache-on-failure: true 110 | - name: cargo hack 111 | run: | 112 | cargo hack build --workspace --target wasm32-wasip1 113 | 114 | feature-checks: 115 | runs-on: ubuntu-latest 116 | timeout-minutes: 30 117 | steps: 118 | - uses: actions/checkout@v5 119 | - uses: dtolnay/rust-toolchain@stable 120 | - uses: taiki-e/install-action@cargo-hack 121 | - uses: Swatinem/rust-cache@v2 122 | with: 123 | cache-on-failure: true 124 | - name: cargo hack 125 | run: cargo hack check --feature-powerset --depth 1 126 | 127 | check-no-std: 128 | name: check no_std ${{ matrix.features }} 129 | runs-on: ubuntu-latest 130 | timeout-minutes: 30 131 | steps: 132 | - uses: actions/checkout@v5 133 | - uses: dtolnay/rust-toolchain@stable 134 | with: 135 | targets: riscv32imac-unknown-none-elf 136 | - uses: taiki-e/install-action@cargo-hack 137 | - uses: Swatinem/rust-cache@v2 138 | with: 139 | cache-on-failure: true 140 | - run: ./scripts/check_no_std.sh 141 | 142 | clippy: 143 | runs-on: ubuntu-latest 144 | timeout-minutes: 30 145 | steps: 146 | - uses: actions/checkout@v5 147 | - uses: dtolnay/rust-toolchain@master 148 | with: 149 | toolchain: nightly 150 | components: clippy 151 | - uses: Swatinem/rust-cache@v2 152 | with: 153 | cache-on-failure: true 154 | - run: cargo +nightly clippy --workspace --all-targets --all-features 155 | env: 156 | RUSTFLAGS: -Dwarnings 157 | 158 | docs: 159 | runs-on: ubuntu-latest 160 | timeout-minutes: 30 161 | steps: 162 | - uses: actions/checkout@v5 163 | - uses: dtolnay/rust-toolchain@nightly 164 | - uses: Swatinem/rust-cache@v2 165 | with: 166 | cache-on-failure: true 167 | - name: Build documentation 168 | run: cargo doc --workspace --all-features --no-deps --document-private-items 169 | env: 170 | RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition -Zunstable-options 171 | 172 | fmt: 173 | runs-on: ubuntu-latest 174 | timeout-minutes: 30 175 | steps: 176 | - uses: actions/checkout@v5 177 | - uses: dtolnay/rust-toolchain@nightly 178 | with: 179 | components: rustfmt 180 | - run: cargo fmt --all --check 181 | 182 | deny: 183 | uses: ithacaxyz/ci/.github/workflows/deny.yml@main 184 | 185 | ci-success: 186 | name: ci success 187 | runs-on: ubuntu-latest 188 | if: always() 189 | needs: 190 | - test 191 | - doctest 192 | - wasm-unknown 193 | - wasm-wasi 194 | - feature-checks 195 | - check-no-std 196 | - clippy 197 | - docs 198 | - fmt 199 | - deny 200 | steps: 201 | - name: Decide whether the needed jobs succeeded or failed 202 | uses: re-actors/alls-green@release/v1 203 | with: 204 | jobs: ${{ toJSON(needs) }} 205 | 206 | # Check crates correctly propagate features 207 | feature-propagation: 208 | runs-on: ubuntu-latest 209 | timeout-minutes: 20 210 | steps: 211 | - uses: actions/checkout@v5 212 | - name: run zepter 213 | run: | 214 | cargo install zepter -f --locked 215 | zepter --version 216 | time zepter run check 217 | -------------------------------------------------------------------------------- /crates/evm/src/block/system_calls/mod.rs: -------------------------------------------------------------------------------- 1 | //! System contract call functions. 2 | 3 | use crate::{ 4 | block::{BlockExecutionError, OnStateHook}, 5 | Evm, 6 | }; 7 | use alloc::{borrow::Cow, boxed::Box}; 8 | use alloy_consensus::BlockHeader; 9 | use alloy_eips::{ 10 | eip7002::WITHDRAWAL_REQUEST_TYPE, eip7251::CONSOLIDATION_REQUEST_TYPE, eip7685::Requests, 11 | }; 12 | use alloy_hardforks::EthereumHardforks; 13 | use alloy_primitives::{Bytes, B256}; 14 | use revm::{state::EvmState, DatabaseCommit}; 15 | 16 | use super::{StateChangePostBlockSource, StateChangePreBlockSource, StateChangeSource}; 17 | 18 | mod eip2935; 19 | mod eip4788; 20 | mod eip7002; 21 | mod eip7251; 22 | 23 | /// An ephemeral helper type for executing system calls. 24 | /// 25 | /// This can be used to chain system transaction calls. 26 | #[derive(derive_more::Debug)] 27 | pub struct SystemCaller { 28 | spec: Spec, 29 | /// Optional hook to be called after each state change. 30 | #[debug(skip)] 31 | hook: Option>, 32 | } 33 | 34 | impl SystemCaller { 35 | /// Create a new system caller with the given EVM config, database, and chain spec, and creates 36 | /// the EVM with the given initialized config and block environment. 37 | pub const fn new(spec: Spec) -> Self { 38 | Self { spec, hook: None } 39 | } 40 | 41 | /// Installs a custom hook to be called after each state change. 42 | pub fn with_state_hook(&mut self, hook: Option>) -> &mut Self { 43 | self.hook = hook; 44 | self 45 | } 46 | } 47 | 48 | impl SystemCaller 49 | where 50 | Spec: EthereumHardforks, 51 | { 52 | /// Apply pre execution changes. 53 | pub fn apply_pre_execution_changes( 54 | &mut self, 55 | header: impl BlockHeader, 56 | evm: &mut impl Evm, 57 | ) -> Result<(), BlockExecutionError> { 58 | self.apply_blockhashes_contract_call(header.parent_hash(), evm)?; 59 | self.apply_beacon_root_contract_call(header.parent_beacon_block_root(), evm)?; 60 | 61 | Ok(()) 62 | } 63 | 64 | /// Apply post execution changes. 65 | pub fn apply_post_execution_changes( 66 | &mut self, 67 | evm: &mut impl Evm, 68 | ) -> Result { 69 | let mut requests = Requests::default(); 70 | 71 | // Collect all EIP-7685 requests 72 | let withdrawal_requests = self.apply_withdrawal_requests_contract_call(evm)?; 73 | if !withdrawal_requests.is_empty() { 74 | requests.push_request_with_type(WITHDRAWAL_REQUEST_TYPE, withdrawal_requests); 75 | } 76 | 77 | // Collect all EIP-7251 requests 78 | let consolidation_requests = self.apply_consolidation_requests_contract_call(evm)?; 79 | if !consolidation_requests.is_empty() { 80 | requests.push_request_with_type(CONSOLIDATION_REQUEST_TYPE, consolidation_requests); 81 | } 82 | 83 | Ok(requests) 84 | } 85 | 86 | /// Applies the pre-block call to the EIP-2935 blockhashes contract. 87 | pub fn apply_blockhashes_contract_call( 88 | &mut self, 89 | parent_block_hash: B256, 90 | evm: &mut impl Evm, 91 | ) -> Result<(), BlockExecutionError> { 92 | let result_and_state = 93 | eip2935::transact_blockhashes_contract_call(&self.spec, parent_block_hash, evm)?; 94 | 95 | if let Some(res) = result_and_state { 96 | if let Some(hook) = &mut self.hook { 97 | hook.on_state( 98 | StateChangeSource::PreBlock(StateChangePreBlockSource::BlockHashesContract), 99 | &res.state, 100 | ); 101 | } 102 | evm.db_mut().commit(res.state); 103 | } 104 | 105 | Ok(()) 106 | } 107 | 108 | /// Applies the pre-block call to the EIP-4788 beacon root contract. 109 | pub fn apply_beacon_root_contract_call( 110 | &mut self, 111 | parent_beacon_block_root: Option, 112 | evm: &mut impl Evm, 113 | ) -> Result<(), BlockExecutionError> { 114 | let result_and_state = 115 | eip4788::transact_beacon_root_contract_call(&self.spec, parent_beacon_block_root, evm)?; 116 | 117 | if let Some(res) = result_and_state { 118 | if let Some(hook) = &mut self.hook { 119 | hook.on_state( 120 | StateChangeSource::PreBlock(StateChangePreBlockSource::BeaconRootContract), 121 | &res.state, 122 | ); 123 | } 124 | evm.db_mut().commit(res.state); 125 | } 126 | 127 | Ok(()) 128 | } 129 | 130 | /// Applies the post-block call to the EIP-7002 withdrawal requests contract. 131 | pub fn apply_withdrawal_requests_contract_call( 132 | &mut self, 133 | evm: &mut impl Evm, 134 | ) -> Result { 135 | let result_and_state = eip7002::transact_withdrawal_requests_contract_call(evm)?; 136 | 137 | if let Some(ref mut hook) = &mut self.hook { 138 | hook.on_state( 139 | StateChangeSource::PostBlock( 140 | StateChangePostBlockSource::WithdrawalRequestsContract, 141 | ), 142 | &result_and_state.state, 143 | ); 144 | } 145 | evm.db_mut().commit(result_and_state.state); 146 | 147 | eip7002::post_commit(result_and_state.result) 148 | } 149 | 150 | /// Applies the post-block call to the EIP-7251 consolidation requests contract. 151 | pub fn apply_consolidation_requests_contract_call( 152 | &mut self, 153 | evm: &mut impl Evm, 154 | ) -> Result { 155 | let result_and_state = eip7251::transact_consolidation_requests_contract_call(evm)?; 156 | 157 | if let Some(ref mut hook) = &mut self.hook { 158 | hook.on_state( 159 | StateChangeSource::PostBlock( 160 | StateChangePostBlockSource::ConsolidationRequestsContract, 161 | ), 162 | &result_and_state.state, 163 | ); 164 | } 165 | evm.db_mut().commit(result_and_state.state); 166 | 167 | eip7251::post_commit(result_and_state.result) 168 | } 169 | 170 | /// Delegate to stored `OnStateHook`, noop if hook is `None`. 171 | pub fn on_state(&mut self, source: StateChangeSource, state: &EvmState) { 172 | if let Some(hook) = &mut self.hook { 173 | hook.on_state(source, state); 174 | } 175 | } 176 | 177 | /// Invokes the state hook with the outcome of the given closure, forwards error if any. 178 | pub fn try_on_state_with<'a, F, E>(&mut self, f: F) -> Result<(), E> 179 | where 180 | F: FnOnce() -> Result<(StateChangeSource, Cow<'a, EvmState>), E>, 181 | { 182 | self.invoke_hook_with(|hook| { 183 | let (source, state) = f()?; 184 | hook.on_state(source, &state); 185 | Ok(()) 186 | }) 187 | .unwrap_or(Ok(())) 188 | } 189 | 190 | /// Invokes the state hook with the outcome of the given closure. 191 | pub fn on_state_with<'a, F>(&mut self, f: F) 192 | where 193 | F: FnOnce() -> (StateChangeSource, Cow<'a, EvmState>), 194 | { 195 | self.invoke_hook_with(|hook| { 196 | let (source, state) = f(); 197 | hook.on_state(source, &state); 198 | }); 199 | } 200 | 201 | /// Invokes the given closure with the configured state hook if any. 202 | pub fn invoke_hook_with(&mut self, f: F) -> Option 203 | where 204 | F: FnOnce(&mut Box) -> R, 205 | { 206 | self.hook.as_mut().map(f) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /crates/evm/src/eth/dao_fork.rs: -------------------------------------------------------------------------------- 1 | //! DAO Fork related constants from [EIP-779](https://eips.ethereum.org/EIPS/eip-779). 2 | //! It happened on Ethereum block 1_920_000 3 | 4 | use alloy_primitives::{address, Address}; 5 | 6 | /// Dao hardfork beneficiary that received ether from accounts from DAO and DAO creator children. 7 | pub static DAO_HARDFORK_BENEFICIARY: Address = 8 | address!("0xbf4ed7b27f1d666546e30d74d50d173d20bca754"); 9 | 10 | /// DAO hardfork account that ether was taken and added to beneficiary 11 | pub static DAO_HARDFORK_ACCOUNTS: [Address; 116] = [ 12 | address!("0xd4fe7bc31cedb7bfb8a345f31e668033056b2728"), 13 | address!("0xb3fb0e5aba0e20e5c49d252dfd30e102b171a425"), 14 | address!("0x2c19c7f9ae8b751e37aeb2d93a699722395ae18f"), 15 | address!("0xecd135fa4f61a655311e86238c92adcd779555d2"), 16 | address!("0x1975bd06d486162d5dc297798dfc41edd5d160a7"), 17 | address!("0xa3acf3a1e16b1d7c315e23510fdd7847b48234f6"), 18 | address!("0x319f70bab6845585f412ec7724b744fec6095c85"), 19 | address!("0x06706dd3f2c9abf0a21ddcc6941d9b86f0596936"), 20 | address!("0x5c8536898fbb74fc7445814902fd08422eac56d0"), 21 | address!("0x6966ab0d485353095148a2155858910e0965b6f9"), 22 | address!("0x779543a0491a837ca36ce8c635d6154e3c4911a6"), 23 | address!("0x2a5ed960395e2a49b1c758cef4aa15213cfd874c"), 24 | address!("0x5c6e67ccd5849c0d29219c4f95f1a7a93b3f5dc5"), 25 | address!("0x9c50426be05db97f5d64fc54bf89eff947f0a321"), 26 | address!("0x200450f06520bdd6c527622a273333384d870efb"), 27 | address!("0xbe8539bfe837b67d1282b2b1d61c3f723966f049"), 28 | address!("0x6b0c4d41ba9ab8d8cfb5d379c69a612f2ced8ecb"), 29 | address!("0xf1385fb24aad0cd7432824085e42aff90886fef5"), 30 | address!("0xd1ac8b1ef1b69ff51d1d401a476e7e612414f091"), 31 | address!("0x8163e7fb499e90f8544ea62bbf80d21cd26d9efd"), 32 | address!("0x51e0ddd9998364a2eb38588679f0d2c42653e4a6"), 33 | address!("0x627a0a960c079c21c34f7612d5d230e01b4ad4c7"), 34 | address!("0xf0b1aa0eb660754448a7937c022e30aa692fe0c5"), 35 | address!("0x24c4d950dfd4dd1902bbed3508144a54542bba94"), 36 | address!("0x9f27daea7aca0aa0446220b98d028715e3bc803d"), 37 | address!("0xa5dc5acd6a7968a4554d89d65e59b7fd3bff0f90"), 38 | address!("0xd9aef3a1e38a39c16b31d1ace71bca8ef58d315b"), 39 | address!("0x63ed5a272de2f6d968408b4acb9024f4cc208ebf"), 40 | address!("0x6f6704e5a10332af6672e50b3d9754dc460dfa4d"), 41 | address!("0x77ca7b50b6cd7e2f3fa008e24ab793fd56cb15f6"), 42 | address!("0x492ea3bb0f3315521c31f273e565b868fc090f17"), 43 | address!("0x0ff30d6de14a8224aa97b78aea5388d1c51c1f00"), 44 | address!("0x9ea779f907f0b315b364b0cfc39a0fde5b02a416"), 45 | address!("0xceaeb481747ca6c540a000c1f3641f8cef161fa7"), 46 | address!("0xcc34673c6c40e791051898567a1222daf90be287"), 47 | address!("0x579a80d909f346fbfb1189493f521d7f48d52238"), 48 | address!("0xe308bd1ac5fda103967359b2712dd89deffb7973"), 49 | address!("0x4cb31628079fb14e4bc3cd5e30c2f7489b00960c"), 50 | address!("0xac1ecab32727358dba8962a0f3b261731aad9723"), 51 | address!("0x4fd6ace747f06ece9c49699c7cabc62d02211f75"), 52 | address!("0x440c59b325d2997a134c2c7c60a8c61611212bad"), 53 | address!("0x4486a3d68fac6967006d7a517b889fd3f98c102b"), 54 | address!("0x9c15b54878ba618f494b38f0ae7443db6af648ba"), 55 | address!("0x27b137a85656544b1ccb5a0f2e561a5703c6a68f"), 56 | address!("0x21c7fdb9ed8d291d79ffd82eb2c4356ec0d81241"), 57 | address!("0x23b75c2f6791eef49c69684db4c6c1f93bf49a50"), 58 | address!("0x1ca6abd14d30affe533b24d7a21bff4c2d5e1f3b"), 59 | address!("0xb9637156d330c0d605a791f1c31ba5890582fe1c"), 60 | address!("0x6131c42fa982e56929107413a9d526fd99405560"), 61 | address!("0x1591fc0f688c81fbeb17f5426a162a7024d430c2"), 62 | address!("0x542a9515200d14b68e934e9830d91645a980dd7a"), 63 | address!("0xc4bbd073882dd2add2424cf47d35213405b01324"), 64 | address!("0x782495b7b3355efb2833d56ecb34dc22ad7dfcc4"), 65 | address!("0x58b95c9a9d5d26825e70a82b6adb139d3fd829eb"), 66 | address!("0x3ba4d81db016dc2890c81f3acec2454bff5aada5"), 67 | address!("0xb52042c8ca3f8aa246fa79c3feaa3d959347c0ab"), 68 | address!("0xe4ae1efdfc53b73893af49113d8694a057b9c0d1"), 69 | address!("0x3c02a7bc0391e86d91b7d144e61c2c01a25a79c5"), 70 | address!("0x0737a6b837f97f46ebade41b9bc3e1c509c85c53"), 71 | address!("0x97f43a37f595ab5dd318fb46e7a155eae057317a"), 72 | address!("0x52c5317c848ba20c7504cb2c8052abd1fde29d03"), 73 | address!("0x4863226780fe7c0356454236d3b1c8792785748d"), 74 | address!("0x5d2b2e6fcbe3b11d26b525e085ff818dae332479"), 75 | address!("0x5f9f3392e9f62f63b8eac0beb55541fc8627f42c"), 76 | address!("0x057b56736d32b86616a10f619859c6cd6f59092a"), 77 | address!("0x9aa008f65de0b923a2a4f02012ad034a5e2e2192"), 78 | address!("0x304a554a310c7e546dfe434669c62820b7d83490"), 79 | address!("0x914d1b8b43e92723e64fd0a06f5bdb8dd9b10c79"), 80 | address!("0x4deb0033bb26bc534b197e61d19e0733e5679784"), 81 | address!("0x07f5c1e1bc2c93e0402f23341973a0e043f7bf8a"), 82 | address!("0x35a051a0010aba705c9008d7a7eff6fb88f6ea7b"), 83 | address!("0x4fa802324e929786dbda3b8820dc7834e9134a2a"), 84 | address!("0x9da397b9e80755301a3b32173283a91c0ef6c87e"), 85 | address!("0x8d9edb3054ce5c5774a420ac37ebae0ac02343c6"), 86 | address!("0x0101f3be8ebb4bbd39a2e3b9a3639d4259832fd9"), 87 | address!("0x5dc28b15dffed94048d73806ce4b7a4612a1d48f"), 88 | address!("0xbcf899e6c7d9d5a215ab1e3444c86806fa854c76"), 89 | address!("0x12e626b0eebfe86a56d633b9864e389b45dcb260"), 90 | address!("0xa2f1ccba9395d7fcb155bba8bc92db9bafaeade7"), 91 | address!("0xec8e57756626fdc07c63ad2eafbd28d08e7b0ca5"), 92 | address!("0xd164b088bd9108b60d0ca3751da4bceb207b0782"), 93 | address!("0x6231b6d0d5e77fe001c2a460bd9584fee60d409b"), 94 | address!("0x1cba23d343a983e9b5cfd19496b9a9701ada385f"), 95 | address!("0xa82f360a8d3455c5c41366975bde739c37bfeb8a"), 96 | address!("0x9fcd2deaff372a39cc679d5c5e4de7bafb0b1339"), 97 | address!("0x005f5cee7a43331d5a3d3eec71305925a62f34b6"), 98 | address!("0x0e0da70933f4c7849fc0d203f5d1d43b9ae4532d"), 99 | address!("0xd131637d5275fd1a68a3200f4ad25c71a2a9522e"), 100 | address!("0xbc07118b9ac290e4622f5e77a0853539789effbe"), 101 | address!("0x47e7aa56d6bdf3f36be34619660de61275420af8"), 102 | address!("0xacd87e28b0c9d1254e868b81cba4cc20d9a32225"), 103 | address!("0xadf80daec7ba8dcf15392f1ac611fff65d94f880"), 104 | address!("0x5524c55fb03cf21f549444ccbecb664d0acad706"), 105 | address!("0x40b803a9abce16f50f36a77ba41180eb90023925"), 106 | address!("0xfe24cdd8648121a43a7c86d289be4dd2951ed49f"), 107 | address!("0x17802f43a0137c506ba92291391a8a8f207f487d"), 108 | address!("0x253488078a4edf4d6f42f113d1e62836a942cf1a"), 109 | address!("0x86af3e9626fce1957c82e88cbf04ddf3a2ed7915"), 110 | address!("0xb136707642a4ea12fb4bae820f03d2562ebff487"), 111 | address!("0xdbe9b615a3ae8709af8b93336ce9b477e4ac0940"), 112 | address!("0xf14c14075d6c4ed84b86798af0956deef67365b5"), 113 | address!("0xca544e5c4687d109611d0f8f928b53a25af72448"), 114 | address!("0xaeeb8ff27288bdabc0fa5ebb731b6f409507516c"), 115 | address!("0xcbb9d3703e651b0d496cdefb8b92c25aeb2171f7"), 116 | address!("0x6d87578288b6cb5549d5076a207456a1f6a63dc0"), 117 | address!("0xb2c6f0dfbb716ac562e2d85d6cb2f8d5ee87603e"), 118 | address!("0xaccc230e8a6e5be9160b8cdf2864dd2a001c28b6"), 119 | address!("0x2b3455ec7fedf16e646268bf88846bd7a2319bb2"), 120 | address!("0x4613f3bca5c44ea06337a9e439fbc6d42e501d0a"), 121 | address!("0xd343b217de44030afaa275f54d31a9317c7f441e"), 122 | address!("0x84ef4b2357079cd7a7c69fd7a37cd0609a679106"), 123 | address!("0xda2fef9e4a3230988ff17df2165440f37e8b1708"), 124 | address!("0xf4c64518ea10f995918a454158c6b61407ea345c"), 125 | address!("0x7602b46df5390e432ef1c307d4f2c9ff6d65cc97"), 126 | address!("0xbb9bc244d798123fde783fcc1c72d3bb8c189413"), 127 | address!("0x807640a13483f8ac783c557fcdf27be11ea4ac7a"), 128 | ]; 129 | -------------------------------------------------------------------------------- /crates/evm/src/eth/spec_id.rs: -------------------------------------------------------------------------------- 1 | use alloy_consensus::BlockHeader; 2 | use alloy_hardforks::EthereumHardforks; 3 | use alloy_primitives::{BlockNumber, BlockTimestamp}; 4 | use revm::primitives::hardfork::SpecId; 5 | 6 | /// Map the latest active hardfork at the given header to a [`SpecId`]. 7 | pub fn spec(chain_spec: &C, header: &H) -> SpecId 8 | where 9 | C: EthereumHardforks, 10 | H: BlockHeader, 11 | { 12 | spec_by_timestamp_and_block_number(chain_spec, header.timestamp(), header.number()) 13 | } 14 | 15 | /// Map the latest active hardfork at the given timestamp or block number to a [`SpecId`]. 16 | pub fn spec_by_timestamp_and_block_number( 17 | chain_spec: &C, 18 | timestamp: BlockTimestamp, 19 | block_number: BlockNumber, 20 | ) -> SpecId 21 | where 22 | C: EthereumHardforks, 23 | { 24 | if chain_spec.is_osaka_active_at_timestamp(timestamp) { 25 | SpecId::OSAKA 26 | } else if chain_spec.is_prague_active_at_timestamp(timestamp) { 27 | SpecId::PRAGUE 28 | } else if chain_spec.is_cancun_active_at_timestamp(timestamp) { 29 | SpecId::CANCUN 30 | } else if chain_spec.is_shanghai_active_at_timestamp(timestamp) { 31 | SpecId::SHANGHAI 32 | } else if chain_spec.is_paris_active_at_block(block_number) { 33 | SpecId::MERGE 34 | } else if chain_spec.is_london_active_at_block(block_number) { 35 | SpecId::LONDON 36 | } else if chain_spec.is_berlin_active_at_block(block_number) { 37 | SpecId::BERLIN 38 | } else if chain_spec.is_istanbul_active_at_block(block_number) { 39 | SpecId::ISTANBUL 40 | } else if chain_spec.is_petersburg_active_at_block(block_number) { 41 | SpecId::PETERSBURG 42 | } else if chain_spec.is_byzantium_active_at_block(block_number) { 43 | SpecId::BYZANTIUM 44 | } else if chain_spec.is_spurious_dragon_active_at_block(block_number) { 45 | SpecId::SPURIOUS_DRAGON 46 | } else if chain_spec.is_tangerine_whistle_active_at_block(block_number) { 47 | SpecId::TANGERINE 48 | } else if chain_spec.is_homestead_active_at_block(block_number) { 49 | SpecId::HOMESTEAD 50 | } else { 51 | SpecId::FRONTIER 52 | } 53 | } 54 | 55 | #[cfg(test)] 56 | mod tests { 57 | use super::*; 58 | use crate::eth::spec::EthSpec; 59 | use alloy_consensus::Header; 60 | use alloy_hardforks::{ 61 | mainnet::{ 62 | MAINNET_BERLIN_BLOCK, MAINNET_BYZANTIUM_BLOCK, MAINNET_CANCUN_TIMESTAMP, 63 | MAINNET_FRONTIER_BLOCK, MAINNET_HOMESTEAD_BLOCK, MAINNET_ISTANBUL_BLOCK, 64 | MAINNET_LONDON_BLOCK, MAINNET_PARIS_BLOCK, MAINNET_PETERSBURG_BLOCK, 65 | MAINNET_PRAGUE_TIMESTAMP, MAINNET_SHANGHAI_TIMESTAMP, MAINNET_SPURIOUS_DRAGON_BLOCK, 66 | MAINNET_TANGERINE_BLOCK, 67 | }, 68 | EthereumHardfork, ForkCondition, 69 | }; 70 | use alloy_primitives::{BlockNumber, BlockTimestamp}; 71 | 72 | struct FakeHardfork { 73 | fork: EthereumHardfork, 74 | cond: ForkCondition, 75 | } 76 | 77 | impl FakeHardfork { 78 | fn osaka() -> Self { 79 | Self::from_timestamp_zero(EthereumHardfork::Osaka) 80 | } 81 | 82 | fn prague() -> Self { 83 | Self::from_timestamp_zero(EthereumHardfork::Prague) 84 | } 85 | 86 | fn cancun() -> Self { 87 | Self::from_timestamp_zero(EthereumHardfork::Cancun) 88 | } 89 | 90 | fn shanghai() -> Self { 91 | Self::from_timestamp_zero(EthereumHardfork::Shanghai) 92 | } 93 | 94 | fn paris() -> Self { 95 | Self::from_block_zero(EthereumHardfork::Paris) 96 | } 97 | 98 | fn london() -> Self { 99 | Self::from_block_zero(EthereumHardfork::London) 100 | } 101 | 102 | fn berlin() -> Self { 103 | Self::from_block_zero(EthereumHardfork::Berlin) 104 | } 105 | 106 | fn istanbul() -> Self { 107 | Self::from_block_zero(EthereumHardfork::Istanbul) 108 | } 109 | 110 | fn petersburg() -> Self { 111 | Self::from_block_zero(EthereumHardfork::Petersburg) 112 | } 113 | 114 | fn spurious_dragon() -> Self { 115 | Self::from_block_zero(EthereumHardfork::SpuriousDragon) 116 | } 117 | 118 | fn homestead() -> Self { 119 | Self::from_block_zero(EthereumHardfork::Homestead) 120 | } 121 | 122 | fn frontier() -> Self { 123 | Self::from_block_zero(EthereumHardfork::Frontier) 124 | } 125 | 126 | fn from_block_zero(fork: EthereumHardfork) -> Self { 127 | Self { fork, cond: ForkCondition::Block(0) } 128 | } 129 | 130 | fn from_timestamp_zero(fork: EthereumHardfork) -> Self { 131 | Self { fork, cond: ForkCondition::Timestamp(0) } 132 | } 133 | } 134 | 135 | impl EthereumHardforks for FakeHardfork { 136 | fn ethereum_fork_activation(&self, fork: EthereumHardfork) -> ForkCondition { 137 | if fork == self.fork { 138 | self.cond 139 | } else { 140 | ForkCondition::Never 141 | } 142 | } 143 | } 144 | 145 | #[test_case::test_case(FakeHardfork::osaka(), SpecId::OSAKA; "Osaka")] 146 | #[test_case::test_case(FakeHardfork::prague(), SpecId::PRAGUE; "Prague")] 147 | #[test_case::test_case(FakeHardfork::cancun(), SpecId::CANCUN; "Cancun")] 148 | #[test_case::test_case(FakeHardfork::shanghai(), SpecId::SHANGHAI; "Shanghai")] 149 | #[test_case::test_case(FakeHardfork::paris(), SpecId::MERGE; "Merge")] 150 | #[test_case::test_case(FakeHardfork::london(), SpecId::LONDON; "London")] 151 | #[test_case::test_case(FakeHardfork::berlin(), SpecId::BERLIN; "Berlin")] 152 | #[test_case::test_case(FakeHardfork::istanbul(), SpecId::ISTANBUL; "Istanbul")] 153 | #[test_case::test_case(FakeHardfork::petersburg(), SpecId::PETERSBURG; "Petersburg")] 154 | #[test_case::test_case(FakeHardfork::spurious_dragon(), SpecId::SPURIOUS_DRAGON; "Spurious dragon")] 155 | #[test_case::test_case(FakeHardfork::homestead(), SpecId::HOMESTEAD; "Homestead")] 156 | #[test_case::test_case(FakeHardfork::frontier(), SpecId::FRONTIER; "Frontier")] 157 | fn test_spec_maps_hardfork_successfully(fork: impl EthereumHardforks, expected_spec: SpecId) { 158 | let header = Header::default(); 159 | let actual_spec = spec(&fork, &header); 160 | 161 | assert_eq!(actual_spec, expected_spec); 162 | } 163 | 164 | #[test_case::test_case(MAINNET_PRAGUE_TIMESTAMP, 0, SpecId::PRAGUE; "Prague")] 165 | #[test_case::test_case(MAINNET_CANCUN_TIMESTAMP, 0, SpecId::CANCUN; "Cancun")] 166 | #[test_case::test_case(MAINNET_SHANGHAI_TIMESTAMP, 0, SpecId::SHANGHAI; "Shanghai")] 167 | #[test_case::test_case(0, MAINNET_PARIS_BLOCK, SpecId::MERGE; "Merge")] 168 | #[test_case::test_case(0, MAINNET_LONDON_BLOCK, SpecId::LONDON; "London")] 169 | #[test_case::test_case(0, MAINNET_BERLIN_BLOCK, SpecId::BERLIN; "Berlin")] 170 | #[test_case::test_case(0, MAINNET_ISTANBUL_BLOCK, SpecId::ISTANBUL; "Istanbul")] 171 | #[test_case::test_case(0, MAINNET_PETERSBURG_BLOCK, SpecId::PETERSBURG; "Petersburg")] 172 | #[test_case::test_case(0, MAINNET_BYZANTIUM_BLOCK, SpecId::BYZANTIUM; "Byzantium")] 173 | #[test_case::test_case(0, MAINNET_SPURIOUS_DRAGON_BLOCK, SpecId::SPURIOUS_DRAGON; "Spurious dragon")] 174 | #[test_case::test_case(0, MAINNET_TANGERINE_BLOCK, SpecId::TANGERINE; "Tangerine")] 175 | #[test_case::test_case(0, MAINNET_HOMESTEAD_BLOCK, SpecId::HOMESTEAD; "Homestead")] 176 | #[test_case::test_case(0, MAINNET_FRONTIER_BLOCK, SpecId::FRONTIER; "Frontier")] 177 | fn test_eth_spec_maps_hardfork_successfully( 178 | timestamp: BlockTimestamp, 179 | number: BlockNumber, 180 | expected_spec: SpecId, 181 | ) { 182 | let fork = EthSpec::mainnet(); 183 | let header = Header { timestamp, number, ..Default::default() }; 184 | let actual_spec = spec(&fork, &header); 185 | 186 | assert_eq!(actual_spec, expected_spec); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /crates/evm/src/op/tx.rs: -------------------------------------------------------------------------------- 1 | use crate::{FromRecoveredTx, FromTxWithEncoded}; 2 | 3 | use alloy_consensus::{ 4 | Signed, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxEip7702, TxLegacy, 5 | }; 6 | use alloy_eips::{eip7594::Encodable7594, Encodable2718, Typed2718}; 7 | use alloy_primitives::{Address, Bytes}; 8 | use op_alloy::consensus::{OpTxEnvelope, TxDeposit}; 9 | use op_revm::{transaction::deposit::DepositTransactionParts, OpTransaction}; 10 | use revm::context::TxEnv; 11 | 12 | impl FromRecoveredTx for TxEnv { 13 | fn from_recovered_tx(tx: &OpTxEnvelope, caller: Address) -> Self { 14 | match tx { 15 | OpTxEnvelope::Legacy(tx) => Self::from_recovered_tx(tx.tx(), caller), 16 | OpTxEnvelope::Eip1559(tx) => Self::from_recovered_tx(tx.tx(), caller), 17 | OpTxEnvelope::Eip2930(tx) => Self::from_recovered_tx(tx.tx(), caller), 18 | OpTxEnvelope::Eip7702(tx) => Self::from_recovered_tx(tx.tx(), caller), 19 | OpTxEnvelope::Deposit(tx) => Self::from_recovered_tx(tx.inner(), caller), 20 | } 21 | } 22 | } 23 | 24 | impl FromRecoveredTx for TxEnv { 25 | fn from_recovered_tx(tx: &TxDeposit, caller: Address) -> Self { 26 | let TxDeposit { 27 | to, 28 | value, 29 | gas_limit, 30 | input, 31 | source_hash: _, 32 | from: _, 33 | mint: _, 34 | is_system_transaction: _, 35 | } = tx; 36 | Self { 37 | tx_type: tx.ty(), 38 | caller, 39 | gas_limit: *gas_limit, 40 | kind: *to, 41 | value: *value, 42 | data: input.clone(), 43 | ..Default::default() 44 | } 45 | } 46 | } 47 | 48 | impl FromTxWithEncoded for TxEnv { 49 | fn from_encoded_tx(tx: &OpTxEnvelope, caller: Address, _encoded: Bytes) -> Self { 50 | Self::from_recovered_tx(tx, caller) 51 | } 52 | } 53 | 54 | impl FromRecoveredTx for OpTransaction { 55 | fn from_recovered_tx(tx: &OpTxEnvelope, sender: Address) -> Self { 56 | let encoded = tx.encoded_2718(); 57 | Self::from_encoded_tx(tx, sender, encoded.into()) 58 | } 59 | } 60 | 61 | impl FromTxWithEncoded for OpTransaction { 62 | fn from_encoded_tx(tx: &OpTxEnvelope, caller: Address, encoded: Bytes) -> Self { 63 | match tx { 64 | OpTxEnvelope::Legacy(tx) => Self::from_encoded_tx(tx, caller, encoded), 65 | OpTxEnvelope::Eip1559(tx) => Self::from_encoded_tx(tx, caller, encoded), 66 | OpTxEnvelope::Eip2930(tx) => Self::from_encoded_tx(tx, caller, encoded), 67 | OpTxEnvelope::Eip7702(tx) => Self::from_encoded_tx(tx, caller, encoded), 68 | OpTxEnvelope::Deposit(tx) => Self::from_encoded_tx(tx.inner(), caller, encoded), 69 | } 70 | } 71 | } 72 | 73 | impl FromRecoveredTx> for OpTransaction { 74 | fn from_recovered_tx(tx: &Signed, sender: Address) -> Self { 75 | let encoded = tx.encoded_2718(); 76 | Self::from_encoded_tx(tx, sender, encoded.into()) 77 | } 78 | } 79 | 80 | impl FromTxWithEncoded> for OpTransaction { 81 | fn from_encoded_tx(tx: &Signed, caller: Address, encoded: Bytes) -> Self { 82 | Self::from_encoded_tx(tx.tx(), caller, encoded) 83 | } 84 | } 85 | 86 | impl FromTxWithEncoded for OpTransaction { 87 | fn from_encoded_tx(tx: &TxLegacy, caller: Address, encoded: Bytes) -> Self { 88 | let base = TxEnv::from_recovered_tx(tx, caller); 89 | Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } 90 | } 91 | } 92 | 93 | impl FromRecoveredTx> for OpTransaction { 94 | fn from_recovered_tx(tx: &Signed, sender: Address) -> Self { 95 | let encoded = tx.encoded_2718(); 96 | Self::from_encoded_tx(tx, sender, encoded.into()) 97 | } 98 | } 99 | 100 | impl FromTxWithEncoded> for OpTransaction { 101 | fn from_encoded_tx(tx: &Signed, caller: Address, encoded: Bytes) -> Self { 102 | Self::from_encoded_tx(tx.tx(), caller, encoded) 103 | } 104 | } 105 | 106 | impl FromTxWithEncoded for OpTransaction { 107 | fn from_encoded_tx(tx: &TxEip2930, caller: Address, encoded: Bytes) -> Self { 108 | let base = TxEnv::from_recovered_tx(tx, caller); 109 | Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } 110 | } 111 | } 112 | 113 | impl FromRecoveredTx> for OpTransaction { 114 | fn from_recovered_tx(tx: &Signed, sender: Address) -> Self { 115 | let encoded = tx.encoded_2718(); 116 | Self::from_encoded_tx(tx, sender, encoded.into()) 117 | } 118 | } 119 | 120 | impl FromTxWithEncoded> for OpTransaction { 121 | fn from_encoded_tx(tx: &Signed, caller: Address, encoded: Bytes) -> Self { 122 | Self::from_encoded_tx(tx.tx(), caller, encoded) 123 | } 124 | } 125 | 126 | impl FromTxWithEncoded for OpTransaction { 127 | fn from_encoded_tx(tx: &TxEip1559, caller: Address, encoded: Bytes) -> Self { 128 | let base = TxEnv::from_recovered_tx(tx, caller); 129 | Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } 130 | } 131 | } 132 | 133 | impl FromRecoveredTx> for OpTransaction { 134 | fn from_recovered_tx(tx: &Signed, sender: Address) -> Self { 135 | let encoded = tx.encoded_2718(); 136 | Self::from_encoded_tx(tx, sender, encoded.into()) 137 | } 138 | } 139 | 140 | impl FromTxWithEncoded> for OpTransaction { 141 | fn from_encoded_tx(tx: &Signed, caller: Address, encoded: Bytes) -> Self { 142 | Self::from_encoded_tx(tx.tx(), caller, encoded) 143 | } 144 | } 145 | 146 | impl FromTxWithEncoded for OpTransaction { 147 | fn from_encoded_tx(tx: &TxEip4844, caller: Address, encoded: Bytes) -> Self { 148 | let base = TxEnv::from_recovered_tx(tx, caller); 149 | Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } 150 | } 151 | } 152 | 153 | /// `TxEip4844Variant` conversion is not necessary for `OpTransaction`, but it's useful 154 | /// sugar for Foundry. 155 | impl FromRecoveredTx>> for OpTransaction 156 | where 157 | T: Encodable7594 + Send + Sync, 158 | { 159 | fn from_recovered_tx(tx: &Signed>, sender: Address) -> Self { 160 | let encoded = tx.encoded_2718(); 161 | Self::from_encoded_tx(tx, sender, encoded.into()) 162 | } 163 | } 164 | 165 | impl FromTxWithEncoded>> for OpTransaction { 166 | fn from_encoded_tx(tx: &Signed>, caller: Address, encoded: Bytes) -> Self { 167 | Self::from_encoded_tx(tx.tx(), caller, encoded) 168 | } 169 | } 170 | 171 | impl FromTxWithEncoded> for OpTransaction { 172 | fn from_encoded_tx(tx: &TxEip4844Variant, caller: Address, encoded: Bytes) -> Self { 173 | let base = TxEnv::from_recovered_tx(tx, caller); 174 | Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } 175 | } 176 | } 177 | 178 | impl FromRecoveredTx> for OpTransaction { 179 | fn from_recovered_tx(tx: &Signed, sender: Address) -> Self { 180 | let encoded = tx.encoded_2718(); 181 | Self::from_encoded_tx(tx, sender, encoded.into()) 182 | } 183 | } 184 | 185 | impl FromTxWithEncoded> for OpTransaction { 186 | fn from_encoded_tx(tx: &Signed, caller: Address, encoded: Bytes) -> Self { 187 | Self::from_encoded_tx(tx.tx(), caller, encoded) 188 | } 189 | } 190 | 191 | impl FromTxWithEncoded for OpTransaction { 192 | fn from_encoded_tx(tx: &TxEip7702, caller: Address, encoded: Bytes) -> Self { 193 | let base = TxEnv::from_recovered_tx(tx, caller); 194 | Self { base, enveloped_tx: Some(encoded), deposit: Default::default() } 195 | } 196 | } 197 | 198 | impl FromRecoveredTx for OpTransaction { 199 | fn from_recovered_tx(tx: &TxDeposit, sender: Address) -> Self { 200 | let encoded = tx.encoded_2718(); 201 | Self::from_encoded_tx(tx, sender, encoded.into()) 202 | } 203 | } 204 | 205 | impl FromTxWithEncoded for OpTransaction { 206 | fn from_encoded_tx(tx: &TxDeposit, caller: Address, encoded: Bytes) -> Self { 207 | let base = TxEnv::from_recovered_tx(tx, caller); 208 | let deposit = DepositTransactionParts { 209 | source_hash: tx.source_hash, 210 | mint: Some(tx.mint), 211 | is_system_transaction: tx.is_system_transaction, 212 | }; 213 | Self { base, enveloped_tx: Some(encoded), deposit } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /crates/evm/src/block/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{EvmError, InvalidTxError}; 2 | use alloc::{ 3 | boxed::Box, 4 | string::{String, ToString}, 5 | }; 6 | use alloy_primitives::B256; 7 | 8 | /// Block validation error. 9 | #[derive(Debug, thiserror::Error)] 10 | pub enum BlockValidationError { 11 | /// EVM error with transaction hash and message 12 | #[error("EVM reported invalid transaction ({hash}): {error}")] 13 | InvalidTx { 14 | /// The hash of the transaction 15 | hash: B256, 16 | /// The EVM error. 17 | error: Box, 18 | }, 19 | /// Error when incrementing balance in post execution 20 | #[error("incrementing balance in post execution failed")] 21 | IncrementBalanceFailed, 22 | /// Error when transaction gas limit exceeds available block gas 23 | #[error( 24 | "transaction gas limit {transaction_gas_limit} is more than blocks available gas {block_available_gas}" 25 | )] 26 | TransactionGasLimitMoreThanAvailableBlockGas { 27 | /// The transaction's gas limit 28 | transaction_gas_limit: u64, 29 | /// The available block gas 30 | block_available_gas: u64, 31 | }, 32 | /// Error for EIP-4788 when parent beacon block root is missing 33 | #[error("EIP-4788 parent beacon block root missing for active Cancun block")] 34 | MissingParentBeaconBlockRoot, 35 | /// Error for Cancun genesis block when parent beacon block root is not zero 36 | #[error( 37 | "the parent beacon block root is not zero for Cancun genesis block: {parent_beacon_block_root}" 38 | )] 39 | CancunGenesisParentBeaconBlockRootNotZero { 40 | /// The beacon block root 41 | parent_beacon_block_root: B256, 42 | }, 43 | /// EVM error during [EIP-4788] beacon root contract call. 44 | /// 45 | /// [EIP-4788]: https://eips.ethereum.org/EIPS/eip-4788 46 | #[error("failed to apply beacon root contract call at {parent_beacon_block_root}: {message}")] 47 | BeaconRootContractCall { 48 | /// The beacon block root 49 | parent_beacon_block_root: Box, 50 | /// The error message. 51 | message: String, 52 | }, 53 | /// EVM error during [EIP-2935] blockhash contract call. 54 | /// 55 | /// [EIP-2935]: https://eips.ethereum.org/EIPS/eip-2935 56 | #[error("failed to apply blockhash contract call: {message}")] 57 | BlockHashContractCall { 58 | /// The error message. 59 | message: String, 60 | }, 61 | /// EVM error during withdrawal requests contract call [EIP-7002] 62 | /// 63 | /// [EIP-7002]: https://eips.ethereum.org/EIPS/eip-7002 64 | #[error("failed to apply withdrawal requests contract call: {message}")] 65 | WithdrawalRequestsContractCall { 66 | /// The error message. 67 | message: String, 68 | }, 69 | /// EVM error during consolidation requests contract call [EIP-7251] 70 | /// 71 | /// [EIP-7251]: https://eips.ethereum.org/EIPS/eip-7251 72 | #[error("failed to apply consolidation requests contract call: {message}")] 73 | ConsolidationRequestsContractCall { 74 | /// The error message. 75 | message: String, 76 | }, 77 | /// Error when decoding deposit requests from receipts [EIP-6110] 78 | /// 79 | /// [EIP-6110]: https://eips.ethereum.org/EIPS/eip-6110 80 | #[error("failed to decode deposit requests from receipts: {_0}")] 81 | DepositRequestDecode(String), 82 | /// Arbitrary Block validation errors. 83 | #[error(transparent)] 84 | Other(Box), 85 | } 86 | 87 | impl BlockValidationError { 88 | /// Create a new [`BlockValidationError::Other`] variant. 89 | pub fn other(error: E) -> Self 90 | where 91 | E: core::error::Error + Send + Sync + 'static, 92 | { 93 | Self::Other(Box::new(error)) 94 | } 95 | 96 | /// Create a new [`BlockValidationError::Other`] variant from a given message. 97 | pub fn msg(msg: impl core::fmt::Display) -> Self { 98 | Self::Other(msg.to_string().into()) 99 | } 100 | } 101 | 102 | /// `BlockExecutor` Errors 103 | #[derive(Debug, thiserror::Error)] 104 | pub enum BlockExecutionError { 105 | /// Validation error, transparently wrapping [`BlockValidationError`] 106 | #[error(transparent)] 107 | Validation(#[from] BlockValidationError), 108 | /// Internal, i.e. non consensus or validation related Block Executor Errors 109 | #[error(transparent)] 110 | Internal(#[from] InternalBlockExecutionError), 111 | } 112 | 113 | impl BlockExecutionError { 114 | /// Create a new [`BlockExecutionError::Internal`] variant, containing a 115 | /// [`InternalBlockExecutionError::Other`] error. 116 | pub fn other(error: E) -> Self 117 | where 118 | E: core::error::Error + Send + Sync + 'static, 119 | { 120 | Self::Internal(InternalBlockExecutionError::other(error)) 121 | } 122 | 123 | /// Create a new [`BlockExecutionError::Internal`] variant, containing a 124 | /// [`InternalBlockExecutionError::Other`] error with the given message. 125 | pub fn msg(msg: impl core::fmt::Display) -> Self { 126 | Self::Internal(InternalBlockExecutionError::msg(msg)) 127 | } 128 | 129 | /// Returns the inner `BlockValidationError` if the error is a validation error. 130 | pub const fn as_validation(&self) -> Option<&BlockValidationError> { 131 | match self { 132 | Self::Validation(err) => Some(err), 133 | _ => None, 134 | } 135 | } 136 | 137 | /// Handles an EVM error occurred when executing a transaction. 138 | /// 139 | /// If an error matches [`EvmError::InvalidTransaction`], it will be wrapped into 140 | /// [`BlockValidationError::InvalidTx`], otherwise into [`InternalBlockExecutionError::EVM`]. 141 | pub fn evm(error: E, hash: B256) -> Self { 142 | match error.try_into_invalid_tx_err() { 143 | Ok(err) => { 144 | Self::Validation(BlockValidationError::InvalidTx { hash, error: Box::new(err) }) 145 | } 146 | Err(err) => { 147 | Self::Internal(InternalBlockExecutionError::EVM { hash, error: Box::new(err) }) 148 | } 149 | } 150 | } 151 | } 152 | 153 | /// Internal (i.e., not validation or consensus related) `BlockExecutor` Errors 154 | #[derive(Debug, thiserror::Error)] 155 | pub enum InternalBlockExecutionError { 156 | /// EVM error occurred when executing transaction. This is different from 157 | /// [`BlockValidationError::InvalidTx`] because it will only contain EVM errors which are not 158 | /// transaction validation errors and are assumed to be fatal. 159 | #[error("internal EVM error occurred when executing transaction {hash}: {error}")] 160 | EVM { 161 | /// The hash of the transaction 162 | hash: B256, 163 | /// The EVM error. 164 | error: Box, 165 | }, 166 | /// Arbitrary Block Executor Errors 167 | #[error(transparent)] 168 | Other(Box), 169 | } 170 | 171 | impl InternalBlockExecutionError { 172 | /// Create a new [`InternalBlockExecutionError::Other`] variant. 173 | pub fn other(error: E) -> Self 174 | where 175 | E: core::error::Error + Send + Sync + 'static, 176 | { 177 | Self::Other(Box::new(error)) 178 | } 179 | 180 | /// Create a new [`InternalBlockExecutionError::Other`] from a given message. 181 | pub fn msg(msg: impl core::fmt::Display) -> Self { 182 | Self::Other(msg.to_string().into()) 183 | } 184 | 185 | /// Returns the arbitrary error if it is [`InternalBlockExecutionError::Other`] 186 | pub fn as_other(&self) -> Option<&(dyn core::error::Error + Send + Sync + 'static)> { 187 | match self { 188 | Self::Other(err) => Some(&**err), 189 | _ => None, 190 | } 191 | } 192 | 193 | /// Attempts to downcast the [`InternalBlockExecutionError::Other`] variant to a concrete type 194 | pub fn downcast(self) -> Result, Self> { 195 | match self { 196 | Self::Other(err) => err.downcast().map_err(Self::Other), 197 | err => Err(err), 198 | } 199 | } 200 | 201 | /// Returns a reference to the [`InternalBlockExecutionError::Other`] value if this type is a 202 | /// [`InternalBlockExecutionError::Other`] of that type. Returns None otherwise. 203 | pub fn downcast_other(&self) -> Option<&T> { 204 | let other = self.as_other()?; 205 | other.downcast_ref() 206 | } 207 | 208 | /// Returns true if the this type is a [`InternalBlockExecutionError::Other`] of that error 209 | /// type. Returns false otherwise. 210 | pub fn is_other(&self) -> bool { 211 | self.as_other().map(|err| err.is::()).unwrap_or(false) 212 | } 213 | } 214 | 215 | #[cfg(test)] 216 | mod tests { 217 | use super::*; 218 | 219 | #[derive(thiserror::Error, Debug)] 220 | #[error("err")] 221 | struct E; 222 | 223 | #[test] 224 | fn other_downcast() { 225 | let err = InternalBlockExecutionError::other(E); 226 | assert!(err.is_other::()); 227 | 228 | assert!(err.downcast_other::().is_some()); 229 | assert!(err.downcast::().is_ok()); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /crates/evm/src/eth/env.rs: -------------------------------------------------------------------------------- 1 | use crate::EvmEnv; 2 | use alloy_consensus::BlockHeader; 3 | use alloy_eips::{eip7825::MAX_TX_GAS_LIMIT_OSAKA, eip7840::BlobParams}; 4 | use alloy_hardforks::EthereumHardforks; 5 | use alloy_primitives::{Address, BlockNumber, BlockTimestamp, ChainId, B256, U256}; 6 | use revm::{ 7 | context::{BlockEnv, CfgEnv}, 8 | context_interface::block::BlobExcessGasAndPrice, 9 | primitives::hardfork::SpecId, 10 | }; 11 | 12 | impl EvmEnv { 13 | /// Create a new `EvmEnv` with [`SpecId`] from a block `header`, `chain_id`, `chain_spec` and 14 | /// optional `blob_params`. 15 | /// 16 | /// # Arguments 17 | /// 18 | /// * `header` - The block to make the env out of. 19 | /// * `chain_spec` - The chain hardfork description, must implement [`EthereumHardforks`]. 20 | /// * `chain_id` - The chain identifier. 21 | /// * `blob_params` - Optional parameters that sets limits on gas and count for blobs. 22 | pub fn for_eth_block( 23 | header: impl BlockHeader, 24 | chain_spec: impl EthereumHardforks, 25 | chain_id: ChainId, 26 | blob_params: Option, 27 | ) -> Self { 28 | Self::for_eth(EvmEnvInput::from_block_header(header), chain_spec, chain_id, blob_params) 29 | } 30 | 31 | /// Create a new `EvmEnv` with [`SpecId`] from a parent block `header`, `chain_id`, `chain_spec` 32 | /// and optional `blob_params`. 33 | /// 34 | /// # Arguments 35 | /// 36 | /// * `header` - The parent block to make the env out of. 37 | /// * `base_fee_per_gas` - Base fee per gas for the next block. 38 | /// * `chain_spec` - The chain hardfork description, must implement [`EthereumHardforks`]. 39 | /// * `chain_id` - The chain identifier. 40 | /// * `blob_params` - Optional parameters that sets limits on gas and count for blobs. 41 | pub fn for_eth_next_block( 42 | header: impl BlockHeader, 43 | attributes: NextEvmEnvAttributes, 44 | base_fee_per_gas: u64, 45 | chain_spec: impl EthereumHardforks, 46 | chain_id: ChainId, 47 | blob_params: Option, 48 | ) -> Self { 49 | Self::for_eth( 50 | EvmEnvInput::for_next(header, attributes, base_fee_per_gas, blob_params), 51 | chain_spec, 52 | chain_id, 53 | blob_params, 54 | ) 55 | } 56 | 57 | fn for_eth( 58 | input: EvmEnvInput, 59 | chain_spec: impl EthereumHardforks, 60 | chain_id: ChainId, 61 | blob_params: Option, 62 | ) -> Self { 63 | let spec = 64 | crate::spec_by_timestamp_and_block_number(&chain_spec, input.timestamp, input.number); 65 | let mut cfg_env = CfgEnv::new_with_spec(spec).with_chain_id(chain_id); 66 | 67 | if let Some(blob_params) = &blob_params { 68 | cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx); 69 | } 70 | 71 | if chain_spec.is_osaka_active_at_timestamp(input.timestamp) { 72 | cfg_env.tx_gas_limit_cap = Some(MAX_TX_GAS_LIMIT_OSAKA); 73 | } 74 | 75 | // derive the EIP-4844 blob fees from the header's `excess_blob_gas` and the current 76 | // blob-params 77 | let blob_excess_gas_and_price = 78 | input.excess_blob_gas.zip(blob_params).map(|(excess_blob_gas, params)| { 79 | let blob_gasprice = params.calc_blob_fee(excess_blob_gas); 80 | BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } 81 | }); 82 | 83 | let is_merge_active = chain_spec.is_paris_active_at_block(input.number); 84 | 85 | let block_env = BlockEnv { 86 | number: U256::from(input.number), 87 | beneficiary: input.beneficiary, 88 | timestamp: U256::from(input.timestamp), 89 | difficulty: if is_merge_active { U256::ZERO } else { input.difficulty }, 90 | prevrandao: if is_merge_active { input.mix_hash } else { None }, 91 | gas_limit: input.gas_limit, 92 | basefee: input.base_fee_per_gas, 93 | blob_excess_gas_and_price, 94 | }; 95 | 96 | Self::new(cfg_env, block_env) 97 | } 98 | } 99 | 100 | pub(crate) struct EvmEnvInput { 101 | pub(crate) timestamp: BlockTimestamp, 102 | pub(crate) number: BlockNumber, 103 | pub(crate) beneficiary: Address, 104 | pub(crate) mix_hash: Option, 105 | pub(crate) difficulty: U256, 106 | pub(crate) gas_limit: u64, 107 | pub(crate) excess_blob_gas: Option, 108 | pub(crate) base_fee_per_gas: u64, 109 | } 110 | 111 | impl EvmEnvInput { 112 | pub(crate) fn from_block_header(header: impl BlockHeader) -> Self { 113 | Self { 114 | timestamp: header.timestamp(), 115 | number: header.number(), 116 | beneficiary: header.beneficiary(), 117 | mix_hash: header.mix_hash(), 118 | difficulty: header.difficulty(), 119 | gas_limit: header.gas_limit(), 120 | excess_blob_gas: header.excess_blob_gas(), 121 | base_fee_per_gas: header.base_fee_per_gas().unwrap_or_default(), 122 | } 123 | } 124 | 125 | pub(crate) fn for_next( 126 | parent: impl BlockHeader, 127 | attributes: NextEvmEnvAttributes, 128 | base_fee_per_gas: u64, 129 | blob_params: Option, 130 | ) -> Self { 131 | Self { 132 | timestamp: attributes.timestamp, 133 | number: parent.number() + 1, 134 | beneficiary: attributes.suggested_fee_recipient, 135 | mix_hash: Some(attributes.prev_randao), 136 | difficulty: U256::ZERO, 137 | gas_limit: attributes.gas_limit, 138 | // If header does not have blob fields, but we have blob params, assume that excess blob 139 | // gas is 0. 140 | excess_blob_gas: parent 141 | .maybe_next_block_excess_blob_gas(blob_params) 142 | .or_else(|| blob_params.map(|_| 0)), 143 | base_fee_per_gas, 144 | } 145 | } 146 | } 147 | 148 | /// Represents additional attributes required to configure the next block. 149 | /// 150 | /// This struct contains all the information needed to build a new block that cannot be 151 | /// derived from the parent block header alone. These attributes are typically provided 152 | /// by the consensus layer (CL) through the Engine API during payload building. 153 | #[derive(Debug, Clone, PartialEq, Eq)] 154 | pub struct NextEvmEnvAttributes { 155 | /// The timestamp of the next block. 156 | pub timestamp: u64, 157 | /// The suggested fee recipient for the next block. 158 | pub suggested_fee_recipient: Address, 159 | /// The randomness value for the next block. 160 | pub prev_randao: B256, 161 | /// Block gas limit. 162 | pub gas_limit: u64, 163 | } 164 | 165 | #[cfg(feature = "engine")] 166 | mod payload { 167 | use super::*; 168 | use alloy_rpc_types_engine::ExecutionPayload; 169 | 170 | impl EvmEnv { 171 | /// Create a new `EvmEnv` with [`SpecId`] from a `payload`, `chain_id`, `chain_spec` and 172 | /// optional `blob_params`. 173 | /// 174 | /// # Arguments 175 | /// 176 | /// * `header` - The block to make the env out of. 177 | /// * `chain_spec` - The chain hardfork description, must implement [`EthereumHardforks`]. 178 | /// * `chain_id` - The chain identifier. 179 | /// * `blob_params` - Optional parameters that sets limits on gas and count for blobs. 180 | pub fn for_eth_payload( 181 | payload: &ExecutionPayload, 182 | chain_spec: impl EthereumHardforks, 183 | chain_id: ChainId, 184 | blob_params: Option, 185 | ) -> Self { 186 | Self::for_eth(EvmEnvInput::from_payload(payload), chain_spec, chain_id, blob_params) 187 | } 188 | } 189 | 190 | impl EvmEnvInput { 191 | pub(crate) fn from_payload(payload: &ExecutionPayload) -> Self { 192 | Self { 193 | timestamp: payload.timestamp(), 194 | number: payload.block_number(), 195 | beneficiary: payload.fee_recipient(), 196 | mix_hash: Some(payload.as_v1().prev_randao), 197 | difficulty: payload.as_v1().prev_randao.into(), 198 | gas_limit: payload.gas_limit(), 199 | excess_blob_gas: payload.excess_blob_gas(), 200 | base_fee_per_gas: payload.saturated_base_fee_per_gas(), 201 | } 202 | } 203 | } 204 | } 205 | 206 | #[cfg(test)] 207 | mod tests { 208 | use super::*; 209 | use crate::eth::spec::EthSpec; 210 | use alloy_consensus::Header; 211 | use alloy_hardforks::ethereum::MAINNET_PARIS_BLOCK; 212 | use alloy_primitives::B256; 213 | 214 | #[test_case::test_case( 215 | Header::default(), 216 | EvmEnv { 217 | cfg_env: CfgEnv::new_with_spec(SpecId::FRONTIER).with_chain_id(2), 218 | block_env: BlockEnv { 219 | timestamp: U256::ZERO, 220 | gas_limit: 0, 221 | prevrandao: None, 222 | blob_excess_gas_and_price: None, 223 | ..BlockEnv::default() 224 | }, 225 | }; 226 | "Frontier" 227 | )] 228 | #[test_case::test_case( 229 | Header { 230 | number: MAINNET_PARIS_BLOCK, 231 | mix_hash: B256::with_last_byte(2), 232 | ..Header::default() 233 | }, 234 | EvmEnv { 235 | cfg_env: CfgEnv::new_with_spec(SpecId::MERGE).with_chain_id(2), 236 | block_env: BlockEnv { 237 | number: U256::from(MAINNET_PARIS_BLOCK), 238 | timestamp: U256::ZERO, 239 | gas_limit: 0, 240 | prevrandao: Some(B256::with_last_byte(2)), 241 | blob_excess_gas_and_price: None, 242 | ..BlockEnv::default() 243 | }, 244 | }; 245 | "Paris" 246 | )] 247 | fn test_evm_env_is_consistent_with_given_block( 248 | header: Header, 249 | expected_evm_env: EvmEnv, 250 | ) { 251 | let chain_id = 2; 252 | let spec = EthSpec::mainnet(); 253 | let blob_params = None; 254 | let actual_evm_env = EvmEnv::for_eth_block(header, spec, chain_id, blob_params); 255 | 256 | assert_eq!(actual_evm_env, expected_evm_env); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /crates/evm/src/overrides.rs: -------------------------------------------------------------------------------- 1 | //! RPC utilities for working with EVM. 2 | //! 3 | //! This module provides helper functions for RPC implementations, including: 4 | //! - Block and state overrides 5 | 6 | use alloc::collections::BTreeMap; 7 | use alloy_primitives::{keccak256, map::HashMap, Address, B256, U256}; 8 | use alloy_rpc_types_eth::{ 9 | state::{AccountOverride, StateOverride}, 10 | BlockOverrides, 11 | }; 12 | use revm::{ 13 | bytecode::BytecodeDecodeError, 14 | context::BlockEnv, 15 | database::{CacheDB, State}, 16 | state::{Account, AccountStatus, Bytecode, EvmStorageSlot}, 17 | Database, DatabaseCommit, 18 | }; 19 | 20 | /// Errors that can occur when applying state overrides. 21 | #[derive(Debug, thiserror::Error)] 22 | pub enum StateOverrideError { 23 | /// Invalid bytecode provided in override. 24 | #[error(transparent)] 25 | InvalidBytecode(#[from] BytecodeDecodeError), 26 | /// Both state and state_diff were provided for an account. 27 | #[error("Both 'state' and 'stateDiff' fields are set for account {0}")] 28 | BothStateAndStateDiff(Address), 29 | /// Database error occurred. 30 | #[error(transparent)] 31 | Database(E), 32 | } 33 | 34 | /// Helper trait implemented for databases that support overriding block hashes. 35 | /// 36 | /// Used for applying [`BlockOverrides::block_hash`] 37 | pub trait OverrideBlockHashes { 38 | /// Overrides the given block hashes. 39 | fn override_block_hashes(&mut self, block_hashes: BTreeMap); 40 | 41 | /// Applies the given block overrides to the env and updates overridden block hashes. 42 | fn apply_block_overrides(&mut self, overrides: BlockOverrides, env: &mut BlockEnv) 43 | where 44 | Self: Sized, 45 | { 46 | apply_block_overrides(overrides, self, env); 47 | } 48 | } 49 | 50 | impl OverrideBlockHashes for CacheDB { 51 | fn override_block_hashes(&mut self, block_hashes: BTreeMap) { 52 | self.cache 53 | .block_hashes 54 | .extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash))) 55 | } 56 | } 57 | 58 | impl OverrideBlockHashes for State { 59 | fn override_block_hashes(&mut self, block_hashes: BTreeMap) { 60 | self.block_hashes.extend(block_hashes); 61 | } 62 | } 63 | 64 | /// Applies the given block overrides to the env and updates overridden block hashes in the db. 65 | pub fn apply_block_overrides(overrides: BlockOverrides, db: &mut DB, env: &mut BlockEnv) 66 | where 67 | DB: OverrideBlockHashes, 68 | { 69 | let BlockOverrides { 70 | number, 71 | difficulty, 72 | time, 73 | gas_limit, 74 | coinbase, 75 | random, 76 | base_fee, 77 | block_hash, 78 | } = overrides; 79 | 80 | if let Some(block_hashes) = block_hash { 81 | // override block hashes 82 | db.override_block_hashes(block_hashes); 83 | } 84 | 85 | if let Some(number) = number { 86 | env.number = number.saturating_to(); 87 | } 88 | if let Some(difficulty) = difficulty { 89 | env.difficulty = difficulty; 90 | } 91 | if let Some(time) = time { 92 | env.timestamp = U256::from(time); 93 | } 94 | if let Some(gas_limit) = gas_limit { 95 | env.gas_limit = gas_limit; 96 | } 97 | if let Some(coinbase) = coinbase { 98 | env.beneficiary = coinbase; 99 | } 100 | if let Some(random) = random { 101 | env.prevrandao = Some(random); 102 | } 103 | if let Some(base_fee) = base_fee { 104 | env.basefee = base_fee.saturating_to(); 105 | } 106 | } 107 | 108 | /// Applies the given state overrides (a set of [`AccountOverride`]) to the database. 109 | pub fn apply_state_overrides( 110 | overrides: StateOverride, 111 | db: &mut DB, 112 | ) -> Result<(), StateOverrideError> 113 | where 114 | DB: Database + DatabaseCommit, 115 | { 116 | for (account, account_overrides) in overrides { 117 | apply_account_override(account, account_overrides, db)?; 118 | } 119 | Ok(()) 120 | } 121 | 122 | /// Applies a single [`AccountOverride`] to the database. 123 | fn apply_account_override( 124 | account: Address, 125 | account_override: AccountOverride, 126 | db: &mut DB, 127 | ) -> Result<(), StateOverrideError> 128 | where 129 | DB: Database + DatabaseCommit, 130 | { 131 | let mut info = db.basic(account).map_err(StateOverrideError::Database)?.unwrap_or_default(); 132 | 133 | if let Some(nonce) = account_override.nonce { 134 | info.nonce = nonce; 135 | } 136 | if let Some(code) = account_override.code { 137 | // we need to set both the bytecode and the codehash 138 | info.code_hash = keccak256(&code); 139 | info.code = Some(Bytecode::new_raw_checked(code)?); 140 | } 141 | if let Some(balance) = account_override.balance { 142 | info.balance = balance; 143 | } 144 | 145 | // Create a new account marked as touched 146 | let mut acc = revm::state::Account { 147 | info, 148 | status: AccountStatus::Touched, 149 | storage: Default::default(), 150 | transaction_id: 0, 151 | }; 152 | 153 | let storage_diff = match (account_override.state, account_override.state_diff) { 154 | (Some(_), Some(_)) => return Err(StateOverrideError::BothStateAndStateDiff(account)), 155 | (None, None) => None, 156 | // If we need to override the entire state, we firstly mark account as destroyed to clear 157 | // its storage, and then we mark it is "NewlyCreated" to make sure that old storage won't be 158 | // used. 159 | (Some(state), None) => { 160 | // Destroy the account to ensure that its storage is cleared 161 | db.commit(HashMap::from_iter([( 162 | account, 163 | Account { 164 | status: AccountStatus::SelfDestructed | AccountStatus::Touched, 165 | ..Default::default() 166 | }, 167 | )])); 168 | // Mark the account as created to ensure that old storage is not read 169 | acc.mark_created(); 170 | Some(state) 171 | } 172 | (None, Some(state)) => Some(state), 173 | }; 174 | 175 | if let Some(state) = storage_diff { 176 | for (slot, value) in state { 177 | acc.storage.insert( 178 | slot.into(), 179 | EvmStorageSlot { 180 | // we use inverted value here to ensure that storage is treated as changed 181 | original_value: (!value).into(), 182 | present_value: value.into(), 183 | is_cold: false, 184 | transaction_id: 0, 185 | }, 186 | ); 187 | } 188 | } 189 | 190 | db.commit(HashMap::from_iter([(account, acc)])); 191 | 192 | Ok(()) 193 | } 194 | 195 | #[cfg(test)] 196 | mod tests { 197 | use super::*; 198 | use alloy_primitives::{address, bytes}; 199 | use revm::database::EmptyDB; 200 | 201 | #[test] 202 | fn test_state_override_state() { 203 | let code = bytes!( 204 | "0x63d0e30db05f525f5f6004601c3473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af15f5260205ff3" 205 | ); 206 | let to = address!("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); 207 | 208 | let mut db = State::builder().with_database(CacheDB::new(EmptyDB::new())).build(); 209 | 210 | let acc_override = AccountOverride::default().with_code(code.clone()); 211 | apply_account_override(to, acc_override, &mut db).unwrap(); 212 | 213 | let account = db.basic(to).unwrap().unwrap(); 214 | assert!(account.code.is_some()); 215 | assert_eq!(account.code_hash, keccak256(&code)); 216 | } 217 | 218 | #[test] 219 | fn test_state_override_cache_db() { 220 | let code = bytes!( 221 | "0x63d0e30db05f525f5f6004601c3473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af15f5260205ff3" 222 | ); 223 | let to = address!("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); 224 | 225 | let mut db = CacheDB::new(EmptyDB::new()); 226 | 227 | let acc_override = AccountOverride::default().with_code(code.clone()); 228 | apply_account_override(to, acc_override, &mut db).unwrap(); 229 | 230 | let account = db.basic(to).unwrap().unwrap(); 231 | assert!(account.code.is_some()); 232 | assert_eq!(account.code_hash, keccak256(&code)); 233 | } 234 | 235 | #[test] 236 | fn test_state_override_storage() { 237 | let account = address!("0x1234567890123456789012345678901234567890"); 238 | let slot1 = B256::from(U256::from(1)); 239 | let slot2 = B256::from(U256::from(2)); 240 | let value1 = B256::from(U256::from(100)); 241 | let value2 = B256::from(U256::from(200)); 242 | 243 | let mut db = CacheDB::new(EmptyDB::new()); 244 | 245 | // Create storage overrides 246 | let mut storage = HashMap::::default(); 247 | storage.insert(slot1, value1); 248 | storage.insert(slot2, value2); 249 | 250 | let acc_override = AccountOverride::default().with_state_diff(storage); 251 | apply_account_override(account, acc_override, &mut db).unwrap(); 252 | 253 | // Get the storage value using the database interface 254 | let storage1 = db.storage(account, U256::from(1)).unwrap(); 255 | let storage2 = db.storage(account, U256::from(2)).unwrap(); 256 | 257 | assert_eq!(storage1, U256::from(100)); 258 | assert_eq!(storage2, U256::from(200)); 259 | } 260 | 261 | #[test] 262 | fn test_state_override_full_state() { 263 | let account = address!("0x1234567890123456789012345678901234567890"); 264 | let slot1 = B256::from(U256::from(1)); 265 | let slot2 = B256::from(U256::from(2)); 266 | let value1 = B256::from(U256::from(100)); 267 | let value2 = B256::from(U256::from(200)); 268 | 269 | let mut db = State::builder().with_database(CacheDB::new(EmptyDB::new())).build(); 270 | 271 | // Create storage overrides using state (not state_diff) 272 | let mut storage = HashMap::::default(); 273 | storage.insert(slot1, value1); 274 | storage.insert(slot2, value2); 275 | 276 | let acc_override = AccountOverride::default().with_state(storage); 277 | let mut state_overrides = StateOverride::default(); 278 | state_overrides.insert(account, acc_override); 279 | apply_state_overrides(state_overrides, &mut db).unwrap(); 280 | 281 | // Get the storage value using the database interface 282 | let storage1 = db.storage(account, U256::from(1)).unwrap(); 283 | let storage2 = db.storage(account, U256::from(2)).unwrap(); 284 | 285 | assert_eq!(storage1, U256::from(100)); 286 | assert_eq!(storage2, U256::from(200)); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /crates/evm/src/rpc/fees.rs: -------------------------------------------------------------------------------- 1 | use alloy_primitives::{B256, U256}; 2 | use core::cmp::min; 3 | use thiserror::Error; 4 | 5 | /// Helper type for representing the fees of a `TransactionRequest` 6 | #[derive(Debug)] 7 | pub struct CallFees { 8 | /// EIP-1559 priority fee 9 | pub max_priority_fee_per_gas: Option, 10 | /// Unified gas price setting 11 | /// 12 | /// Will be the configured `basefee` if unset in the request 13 | /// 14 | /// `gasPrice` for legacy, 15 | /// `maxFeePerGas` for EIP-1559 16 | pub gas_price: U256, 17 | /// Max Fee per Blob gas for EIP-4844 transactions 18 | pub max_fee_per_blob_gas: Option, 19 | } 20 | 21 | impl CallFees { 22 | /// Ensures the fields of a `TransactionRequest` are not conflicting. 23 | /// 24 | /// # EIP-4844 transactions 25 | /// 26 | /// Blob transactions have an additional fee parameter `maxFeePerBlobGas`. 27 | /// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844 28 | /// transaction. 29 | /// 30 | /// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee` 31 | /// is always `Some` 32 | /// 33 | /// ## Notable design decisions 34 | /// 35 | /// For compatibility reasons, this contains several exceptions when fee values are validated: 36 | /// - If both `maxFeePerGas` and `maxPriorityFeePerGas` are set to `0` they are treated as 37 | /// missing values, bypassing fee checks wrt. `baseFeePerGas`. 38 | /// 39 | /// This mirrors geth's behaviour when transaction requests are executed: 40 | /// 41 | /// [`BlockEnv`]: revm::context::BlockEnv 42 | pub fn ensure_fees( 43 | call_gas_price: Option, 44 | call_max_fee: Option, 45 | call_priority_fee: Option, 46 | block_base_fee: U256, 47 | blob_versioned_hashes: Option<&[B256]>, 48 | max_fee_per_blob_gas: Option, 49 | block_blob_fee: Option, 50 | ) -> Result { 51 | /// Get the effective gas price of a transaction as specified in EIP-1559 with relevant 52 | /// checks. 53 | fn get_effective_gas_price( 54 | max_fee_per_gas: Option, 55 | max_priority_fee_per_gas: Option, 56 | block_base_fee: U256, 57 | ) -> Result { 58 | match max_fee_per_gas { 59 | Some(max_fee) => { 60 | let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or(U256::ZERO); 61 | 62 | // only enforce the fee cap if provided input is not zero 63 | if !(max_fee.is_zero() && max_priority_fee_per_gas.is_zero()) 64 | && max_fee < block_base_fee 65 | { 66 | // `base_fee_per_gas` is greater than the `max_fee_per_gas` 67 | return Err(CallFeesError::FeeCapTooLow); 68 | } 69 | if max_fee < max_priority_fee_per_gas { 70 | return Err( 71 | // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas` 72 | CallFeesError::TipAboveFeeCap, 73 | ); 74 | } 75 | // ref 76 | Ok(min( 77 | max_fee, 78 | block_base_fee 79 | .checked_add(max_priority_fee_per_gas) 80 | .ok_or(CallFeesError::TipVeryHigh)?, 81 | )) 82 | } 83 | None => Ok(block_base_fee 84 | .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO)) 85 | .ok_or(CallFeesError::TipVeryHigh)?), 86 | } 87 | } 88 | 89 | let has_blob_hashes = 90 | blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false); 91 | 92 | match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) { 93 | (gas_price, None, None, None) => { 94 | // either legacy transaction or no fee fields are specified 95 | // when no fields are specified, set gas price to zero 96 | let gas_price = gas_price.unwrap_or(U256::ZERO); 97 | Ok(Self { 98 | gas_price, 99 | max_priority_fee_per_gas: None, 100 | max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(), 101 | }) 102 | } 103 | (None, max_fee_per_gas, max_priority_fee_per_gas, None) => { 104 | // request for eip-1559 transaction 105 | let effective_gas_price = get_effective_gas_price( 106 | max_fee_per_gas, 107 | max_priority_fee_per_gas, 108 | block_base_fee, 109 | )?; 110 | let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten(); 111 | 112 | Ok(Self { 113 | gas_price: effective_gas_price, 114 | max_priority_fee_per_gas, 115 | max_fee_per_blob_gas, 116 | }) 117 | } 118 | (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => { 119 | // request for eip-4844 transaction 120 | let effective_gas_price = get_effective_gas_price( 121 | max_fee_per_gas, 122 | max_priority_fee_per_gas, 123 | block_base_fee, 124 | )?; 125 | // Ensure blob_hashes are present 126 | if !has_blob_hashes { 127 | // Blob transaction but no blob hashes 128 | return Err(CallFeesError::BlobTransactionMissingBlobHashes); 129 | } 130 | 131 | Ok(Self { 132 | gas_price: effective_gas_price, 133 | max_priority_fee_per_gas, 134 | max_fee_per_blob_gas: Some(max_fee_per_blob_gas), 135 | }) 136 | } 137 | _ => { 138 | // this fallback covers incompatible combinations of fields 139 | Err(CallFeesError::ConflictingFeeFieldsInRequest) 140 | } 141 | } 142 | } 143 | } 144 | 145 | /// Error coming from decoding and validating transaction request fees. 146 | #[derive(Debug, Error)] 147 | pub enum CallFeesError { 148 | /// Thrown when a call or transaction request (`eth_call`, `eth_estimateGas`, 149 | /// `eth_sendTransaction`) contains conflicting fields (legacy, EIP-1559) 150 | #[error("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")] 151 | ConflictingFeeFieldsInRequest, 152 | /// Thrown post London if the transaction's fee is less than the base fee of the block 153 | #[error("max fee per gas less than block base fee")] 154 | FeeCapTooLow, 155 | /// Thrown to ensure no one is able to specify a transaction with a tip higher than the total 156 | /// fee cap. 157 | #[error("max priority fee per gas higher than max fee per gas")] 158 | TipAboveFeeCap, 159 | /// A sanity error to avoid huge numbers specified in the tip field. 160 | #[error("max priority fee per gas higher than 2^256-1")] 161 | TipVeryHigh, 162 | /// Blob transaction has no versioned hashes 163 | #[error("blob transaction missing blob hashes")] 164 | BlobTransactionMissingBlobHashes, 165 | } 166 | 167 | #[cfg(test)] 168 | mod tests { 169 | use super::*; 170 | use alloy_consensus::constants::GWEI_TO_WEI; 171 | 172 | #[test] 173 | fn test_ensure_0_fallback() { 174 | let CallFees { gas_price, .. } = 175 | CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) 176 | .unwrap(); 177 | assert!(gas_price.is_zero()); 178 | } 179 | 180 | #[test] 181 | fn test_ensure_max_fee_0_exception() { 182 | let CallFees { gas_price, .. } = 183 | CallFees::ensure_fees(None, Some(U256::ZERO), None, U256::from(99), None, None, None) 184 | .unwrap(); 185 | assert!(gas_price.is_zero()); 186 | } 187 | 188 | #[test] 189 | fn test_blob_fees() { 190 | let CallFees { gas_price, max_fee_per_blob_gas, .. } = 191 | CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO)) 192 | .unwrap(); 193 | assert!(gas_price.is_zero()); 194 | assert_eq!(max_fee_per_blob_gas, None); 195 | 196 | let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees( 197 | None, 198 | None, 199 | None, 200 | U256::from(99), 201 | Some(&[B256::from(U256::ZERO)]), 202 | None, 203 | Some(U256::from(99)), 204 | ) 205 | .unwrap(); 206 | assert!(gas_price.is_zero()); 207 | assert_eq!(max_fee_per_blob_gas, Some(U256::from(99))); 208 | } 209 | 210 | #[test] 211 | fn test_eip_1559_fees() { 212 | let CallFees { gas_price, .. } = CallFees::ensure_fees( 213 | None, 214 | Some(U256::from(25 * GWEI_TO_WEI)), 215 | Some(U256::from(15 * GWEI_TO_WEI)), 216 | U256::from(15 * GWEI_TO_WEI), 217 | None, 218 | None, 219 | Some(U256::ZERO), 220 | ) 221 | .unwrap(); 222 | assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI)); 223 | 224 | let CallFees { gas_price, .. } = CallFees::ensure_fees( 225 | None, 226 | Some(U256::from(25 * GWEI_TO_WEI)), 227 | Some(U256::from(5 * GWEI_TO_WEI)), 228 | U256::from(15 * GWEI_TO_WEI), 229 | None, 230 | None, 231 | Some(U256::ZERO), 232 | ) 233 | .unwrap(); 234 | assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI)); 235 | 236 | let CallFees { gas_price, .. } = CallFees::ensure_fees( 237 | None, 238 | Some(U256::from(30 * GWEI_TO_WEI)), 239 | Some(U256::from(30 * GWEI_TO_WEI)), 240 | U256::from(15 * GWEI_TO_WEI), 241 | None, 242 | None, 243 | Some(U256::ZERO), 244 | ) 245 | .unwrap(); 246 | assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI)); 247 | 248 | let call_fees = CallFees::ensure_fees( 249 | None, 250 | Some(U256::from(30 * GWEI_TO_WEI)), 251 | Some(U256::from(31 * GWEI_TO_WEI)), 252 | U256::from(15 * GWEI_TO_WEI), 253 | None, 254 | None, 255 | Some(U256::ZERO), 256 | ); 257 | assert!(call_fees.is_err()); 258 | 259 | let call_fees = CallFees::ensure_fees( 260 | None, 261 | Some(U256::from(5 * GWEI_TO_WEI)), 262 | Some(U256::from(GWEI_TO_WEI)), 263 | U256::from(15 * GWEI_TO_WEI), 264 | None, 265 | None, 266 | Some(U256::ZERO), 267 | ); 268 | assert!(call_fees.is_err()); 269 | 270 | let call_fees = CallFees::ensure_fees( 271 | None, 272 | Some(U256::MAX), 273 | Some(U256::MAX), 274 | U256::from(5 * GWEI_TO_WEI), 275 | None, 276 | None, 277 | Some(U256::ZERO), 278 | ); 279 | assert!(call_fees.is_err()); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /crates/evm/src/eth/block.rs: -------------------------------------------------------------------------------- 1 | //! Ethereum block executor. 2 | 3 | use super::{ 4 | dao_fork, eip6110, 5 | receipt_builder::{AlloyReceiptBuilder, ReceiptBuilder, ReceiptBuilderCtx}, 6 | spec::{EthExecutorSpec, EthSpec}, 7 | EthEvmFactory, 8 | }; 9 | use crate::{ 10 | block::{ 11 | state_changes::{balance_increment_state, post_block_balance_increments}, 12 | BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, 13 | BlockExecutorFor, BlockValidationError, ExecutableTx, OnStateHook, 14 | StateChangePostBlockSource, StateChangeSource, SystemCaller, 15 | }, 16 | Database, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded, 17 | }; 18 | use alloc::{borrow::Cow, boxed::Box, vec::Vec}; 19 | use alloy_consensus::{Header, Transaction, TxReceipt}; 20 | use alloy_eips::{eip4895::Withdrawals, eip7685::Requests, Encodable2718}; 21 | use alloy_hardforks::EthereumHardfork; 22 | use alloy_primitives::{Bytes, Log, B256}; 23 | use revm::{ 24 | context::Block, context_interface::result::ResultAndState, database::State, DatabaseCommit, 25 | Inspector, 26 | }; 27 | 28 | /// Context for Ethereum block execution. 29 | #[derive(Debug, Clone)] 30 | pub struct EthBlockExecutionCtx<'a> { 31 | /// Parent block hash. 32 | pub parent_hash: B256, 33 | /// Parent beacon block root. 34 | pub parent_beacon_block_root: Option, 35 | /// Block ommers 36 | pub ommers: &'a [Header], 37 | /// Block withdrawals. 38 | pub withdrawals: Option>, 39 | /// Block extra data. 40 | pub extra_data: Bytes, 41 | } 42 | 43 | /// Block executor for Ethereum. 44 | #[derive(Debug)] 45 | pub struct EthBlockExecutor<'a, Evm, Spec, R: ReceiptBuilder> { 46 | /// Reference to the specification object. 47 | pub spec: Spec, 48 | 49 | /// Context for block execution. 50 | pub ctx: EthBlockExecutionCtx<'a>, 51 | /// Inner EVM. 52 | pub evm: Evm, 53 | /// Utility to call system smart contracts. 54 | pub system_caller: SystemCaller, 55 | /// Receipt builder. 56 | pub receipt_builder: R, 57 | 58 | /// Receipts of executed transactions. 59 | pub receipts: Vec, 60 | /// Total gas used by transactions in this block. 61 | pub gas_used: u64, 62 | 63 | /// Blob gas used by the block. 64 | /// Before cancun activation, this is always 0. 65 | pub blob_gas_used: u64, 66 | } 67 | 68 | impl<'a, Evm, Spec, R> EthBlockExecutor<'a, Evm, Spec, R> 69 | where 70 | Spec: Clone, 71 | R: ReceiptBuilder, 72 | { 73 | /// Creates a new [`EthBlockExecutor`] 74 | pub fn new(evm: Evm, ctx: EthBlockExecutionCtx<'a>, spec: Spec, receipt_builder: R) -> Self { 75 | Self { 76 | evm, 77 | ctx, 78 | receipts: Vec::new(), 79 | gas_used: 0, 80 | blob_gas_used: 0, 81 | system_caller: SystemCaller::new(spec.clone()), 82 | spec, 83 | receipt_builder, 84 | } 85 | } 86 | } 87 | 88 | impl<'db, DB, E, Spec, R> BlockExecutor for EthBlockExecutor<'_, E, Spec, R> 89 | where 90 | DB: Database + 'db, 91 | E: Evm< 92 | DB = &'db mut State, 93 | Tx: FromRecoveredTx + FromTxWithEncoded, 94 | >, 95 | Spec: EthExecutorSpec, 96 | R: ReceiptBuilder>, 97 | { 98 | type Transaction = R::Transaction; 99 | type Receipt = R::Receipt; 100 | type Evm = E; 101 | 102 | fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { 103 | // Set state clear flag if the block is after the Spurious Dragon hardfork. 104 | let state_clear_flag = 105 | self.spec.is_spurious_dragon_active_at_block(self.evm.block().number().saturating_to()); 106 | self.evm.db_mut().set_state_clear_flag(state_clear_flag); 107 | 108 | self.system_caller.apply_blockhashes_contract_call(self.ctx.parent_hash, &mut self.evm)?; 109 | self.system_caller 110 | .apply_beacon_root_contract_call(self.ctx.parent_beacon_block_root, &mut self.evm)?; 111 | 112 | Ok(()) 113 | } 114 | 115 | fn execute_transaction_without_commit( 116 | &mut self, 117 | tx: impl ExecutableTx, 118 | ) -> Result::HaltReason>, BlockExecutionError> { 119 | // The sum of the transaction's gas limit, Tg, and the gas utilized in this block prior, 120 | // must be no greater than the block's gasLimit. 121 | let block_available_gas = self.evm.block().gas_limit() - self.gas_used; 122 | 123 | if tx.tx().gas_limit() > block_available_gas { 124 | return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { 125 | transaction_gas_limit: tx.tx().gas_limit(), 126 | block_available_gas, 127 | } 128 | .into()); 129 | } 130 | 131 | // Execute transaction and return the result 132 | self.evm.transact(&tx).map_err(|err| { 133 | let hash = tx.tx().trie_hash(); 134 | BlockExecutionError::evm(err, hash) 135 | }) 136 | } 137 | 138 | fn commit_transaction( 139 | &mut self, 140 | output: ResultAndState<::HaltReason>, 141 | tx: impl ExecutableTx, 142 | ) -> Result { 143 | let ResultAndState { result, state } = output; 144 | 145 | self.system_caller.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); 146 | 147 | let gas_used = result.gas_used(); 148 | 149 | // append gas used 150 | self.gas_used += gas_used; 151 | 152 | // only determine cancun fields when active 153 | if self.spec.is_cancun_active_at_timestamp(self.evm.block().timestamp().saturating_to()) { 154 | let tx_blob_gas_used = tx.tx().blob_gas_used().unwrap_or_default(); 155 | 156 | self.blob_gas_used = self.blob_gas_used.saturating_add(tx_blob_gas_used); 157 | } 158 | 159 | // Push transaction changeset and calculate header bloom filter for receipt. 160 | self.receipts.push(self.receipt_builder.build_receipt(ReceiptBuilderCtx { 161 | tx: tx.tx(), 162 | evm: &self.evm, 163 | result, 164 | state: &state, 165 | cumulative_gas_used: self.gas_used, 166 | })); 167 | 168 | // Commit the state changes. 169 | self.evm.db_mut().commit(state); 170 | 171 | Ok(gas_used) 172 | } 173 | 174 | fn finish( 175 | mut self, 176 | ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { 177 | let requests = if self 178 | .spec 179 | .is_prague_active_at_timestamp(self.evm.block().timestamp().saturating_to()) 180 | { 181 | // Collect all EIP-6110 deposits 182 | let deposit_requests = 183 | eip6110::parse_deposits_from_receipts(&self.spec, &self.receipts)?; 184 | 185 | let mut requests = Requests::default(); 186 | 187 | if !deposit_requests.is_empty() { 188 | requests.push_request_with_type(eip6110::DEPOSIT_REQUEST_TYPE, deposit_requests); 189 | } 190 | 191 | requests.extend(self.system_caller.apply_post_execution_changes(&mut self.evm)?); 192 | requests 193 | } else { 194 | Requests::default() 195 | }; 196 | 197 | let mut balance_increments = post_block_balance_increments( 198 | &self.spec, 199 | self.evm.block(), 200 | self.ctx.ommers, 201 | self.ctx.withdrawals.as_deref(), 202 | ); 203 | 204 | // Irregular state change at Ethereum DAO hardfork 205 | if self 206 | .spec 207 | .ethereum_fork_activation(EthereumHardfork::Dao) 208 | .transitions_at_block(self.evm.block().number().saturating_to()) 209 | { 210 | // drain balances from hardcoded addresses. 211 | let drained_balance: u128 = self 212 | .evm 213 | .db_mut() 214 | .drain_balances(dao_fork::DAO_HARDFORK_ACCOUNTS) 215 | .map_err(|_| BlockValidationError::IncrementBalanceFailed)? 216 | .into_iter() 217 | .sum(); 218 | 219 | // return balance to DAO beneficiary. 220 | *balance_increments.entry(dao_fork::DAO_HARDFORK_BENEFICIARY).or_default() += 221 | drained_balance; 222 | } 223 | // increment balances 224 | self.evm 225 | .db_mut() 226 | .increment_balances(balance_increments.clone()) 227 | .map_err(|_| BlockValidationError::IncrementBalanceFailed)?; 228 | 229 | // call state hook with changes due to balance increments. 230 | self.system_caller.try_on_state_with(|| { 231 | balance_increment_state(&balance_increments, self.evm.db_mut()).map(|state| { 232 | ( 233 | StateChangeSource::PostBlock(StateChangePostBlockSource::BalanceIncrements), 234 | Cow::Owned(state), 235 | ) 236 | }) 237 | })?; 238 | 239 | Ok(( 240 | self.evm, 241 | BlockExecutionResult { 242 | receipts: self.receipts, 243 | requests, 244 | gas_used: self.gas_used, 245 | blob_gas_used: self.blob_gas_used, 246 | }, 247 | )) 248 | } 249 | 250 | fn set_state_hook(&mut self, hook: Option>) { 251 | self.system_caller.with_state_hook(hook); 252 | } 253 | 254 | fn evm_mut(&mut self) -> &mut Self::Evm { 255 | &mut self.evm 256 | } 257 | 258 | fn evm(&self) -> &Self::Evm { 259 | &self.evm 260 | } 261 | } 262 | 263 | /// Ethereum block executor factory. 264 | #[derive(Debug, Clone, Default, Copy)] 265 | pub struct EthBlockExecutorFactory< 266 | R = AlloyReceiptBuilder, 267 | Spec = EthSpec, 268 | EvmFactory = EthEvmFactory, 269 | > { 270 | /// Receipt builder. 271 | receipt_builder: R, 272 | /// Chain specification. 273 | spec: Spec, 274 | /// EVM factory. 275 | evm_factory: EvmFactory, 276 | } 277 | 278 | impl EthBlockExecutorFactory { 279 | /// Creates a new [`EthBlockExecutorFactory`] with the given spec, [`EvmFactory`], and 280 | /// [`ReceiptBuilder`]. 281 | pub const fn new(receipt_builder: R, spec: Spec, evm_factory: EvmFactory) -> Self { 282 | Self { receipt_builder, spec, evm_factory } 283 | } 284 | 285 | /// Exposes the receipt builder. 286 | pub const fn receipt_builder(&self) -> &R { 287 | &self.receipt_builder 288 | } 289 | 290 | /// Exposes the chain specification. 291 | pub const fn spec(&self) -> &Spec { 292 | &self.spec 293 | } 294 | 295 | /// Exposes the EVM factory. 296 | pub const fn evm_factory(&self) -> &EvmFactory { 297 | &self.evm_factory 298 | } 299 | } 300 | 301 | impl BlockExecutorFactory for EthBlockExecutorFactory 302 | where 303 | R: ReceiptBuilder>, 304 | Spec: EthExecutorSpec, 305 | EvmF: EvmFactory + FromTxWithEncoded>, 306 | Self: 'static, 307 | { 308 | type EvmFactory = EvmF; 309 | type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; 310 | type Transaction = R::Transaction; 311 | type Receipt = R::Receipt; 312 | 313 | fn evm_factory(&self) -> &Self::EvmFactory { 314 | &self.evm_factory 315 | } 316 | 317 | fn create_executor<'a, DB, I>( 318 | &'a self, 319 | evm: EvmF::Evm<&'a mut State, I>, 320 | ctx: Self::ExecutionCtx<'a>, 321 | ) -> impl BlockExecutorFor<'a, Self, DB, I> 322 | where 323 | DB: Database + 'a, 324 | I: Inspector>> + 'a, 325 | { 326 | EthBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /crates/evm/src/eth/mod.rs: -------------------------------------------------------------------------------- 1 | //! Ethereum EVM implementation. 2 | 3 | pub use env::NextEvmEnvAttributes; 4 | 5 | #[cfg(feature = "op")] 6 | pub(crate) use env::EvmEnvInput; 7 | 8 | use crate::{env::EvmEnv, evm::EvmFactory, precompiles::PrecompilesMap, Database, Evm}; 9 | use alloy_primitives::{Address, Bytes}; 10 | use core::{ 11 | fmt::Debug, 12 | ops::{Deref, DerefMut}, 13 | }; 14 | use revm::{ 15 | context::{BlockEnv, CfgEnv, Evm as RevmEvm, TxEnv}, 16 | context_interface::result::{EVMError, HaltReason, ResultAndState}, 17 | handler::{instructions::EthInstructions, EthFrame, EthPrecompiles, PrecompileProvider}, 18 | inspector::NoOpInspector, 19 | interpreter::{interpreter::EthInterpreter, InterpreterResult}, 20 | precompile::{PrecompileSpecId, Precompiles}, 21 | primitives::hardfork::SpecId, 22 | Context, ExecuteEvm, InspectEvm, Inspector, MainBuilder, MainContext, SystemCallEvm, 23 | }; 24 | 25 | mod block; 26 | pub use block::*; 27 | 28 | pub mod dao_fork; 29 | pub mod eip6110; 30 | pub mod receipt_builder; 31 | pub mod spec; 32 | 33 | mod env; 34 | pub(crate) mod spec_id; 35 | 36 | /// The Ethereum EVM context type. 37 | pub type EthEvmContext = Context; 38 | 39 | /// Helper builder to construct `EthEvm` instances in a unified way. 40 | #[derive(Debug)] 41 | pub struct EthEvmBuilder { 42 | db: DB, 43 | block_env: BlockEnv, 44 | cfg_env: CfgEnv, 45 | inspector: I, 46 | inspect: bool, 47 | precompiles: Option, 48 | } 49 | 50 | impl EthEvmBuilder { 51 | /// Creates a builder from the provided `EvmEnv` and database. 52 | pub const fn new(db: DB, env: EvmEnv) -> Self { 53 | Self { 54 | db, 55 | block_env: env.block_env, 56 | cfg_env: env.cfg_env, 57 | inspector: NoOpInspector {}, 58 | inspect: false, 59 | precompiles: None, 60 | } 61 | } 62 | } 63 | 64 | impl EthEvmBuilder { 65 | /// Sets a custom inspector 66 | pub fn inspector(self, inspector: J) -> EthEvmBuilder { 67 | EthEvmBuilder { 68 | db: self.db, 69 | block_env: self.block_env, 70 | cfg_env: self.cfg_env, 71 | inspector, 72 | inspect: self.inspect, 73 | precompiles: self.precompiles, 74 | } 75 | } 76 | 77 | /// Sets a custom inspector and enables invoking it during transaction execution. 78 | pub fn activate_inspector(self, inspector: J) -> EthEvmBuilder { 79 | self.inspector(inspector).inspect() 80 | } 81 | 82 | /// Sets whether to invoke the inspector during transaction execution. 83 | pub const fn set_inspect(mut self, inspect: bool) -> Self { 84 | self.inspect = inspect; 85 | self 86 | } 87 | 88 | /// Enables invoking the inspector during transaction execution. 89 | pub const fn inspect(self) -> Self { 90 | self.set_inspect(true) 91 | } 92 | 93 | /// Overrides the precompiles map. If not provided, it will be derived from the `SpecId` in 94 | /// `CfgEnv`. 95 | pub fn precompiles(mut self, precompiles: PrecompilesMap) -> Self { 96 | self.precompiles = Some(precompiles); 97 | self 98 | } 99 | 100 | /// Builds the `EthEvm` instance. 101 | pub fn build(self) -> EthEvm 102 | where 103 | I: Inspector>, 104 | { 105 | let precompiles = match self.precompiles { 106 | Some(p) => p, 107 | None => PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id( 108 | self.cfg_env.spec, 109 | ))), 110 | }; 111 | 112 | let inner = Context::mainnet() 113 | .with_block(self.block_env) 114 | .with_cfg(self.cfg_env) 115 | .with_db(self.db) 116 | .build_mainnet_with_inspector(self.inspector) 117 | .with_precompiles(precompiles); 118 | 119 | EthEvm { inner, inspect: self.inspect } 120 | } 121 | } 122 | 123 | /// Ethereum EVM implementation. 124 | /// 125 | /// This is a wrapper type around the `revm` ethereum evm with optional [`Inspector`] (tracing) 126 | /// support. [`Inspector`] support is configurable at runtime because it's part of the underlying 127 | /// [`RevmEvm`] type. 128 | #[expect(missing_debug_implementations)] 129 | pub struct EthEvm { 130 | inner: RevmEvm< 131 | EthEvmContext, 132 | I, 133 | EthInstructions>, 134 | PRECOMPILE, 135 | EthFrame, 136 | >, 137 | inspect: bool, 138 | } 139 | 140 | impl EthEvm { 141 | /// Creates a new Ethereum EVM instance. 142 | /// 143 | /// The `inspect` argument determines whether the configured [`Inspector`] of the given 144 | /// [`RevmEvm`] should be invoked on [`Evm::transact`]. 145 | pub const fn new( 146 | evm: RevmEvm< 147 | EthEvmContext, 148 | I, 149 | EthInstructions>, 150 | PRECOMPILE, 151 | EthFrame, 152 | >, 153 | inspect: bool, 154 | ) -> Self { 155 | Self { inner: evm, inspect } 156 | } 157 | 158 | /// Consumes self and return the inner EVM instance. 159 | pub fn into_inner( 160 | self, 161 | ) -> RevmEvm< 162 | EthEvmContext, 163 | I, 164 | EthInstructions>, 165 | PRECOMPILE, 166 | EthFrame, 167 | > { 168 | self.inner 169 | } 170 | 171 | /// Provides a reference to the EVM context. 172 | pub const fn ctx(&self) -> &EthEvmContext { 173 | &self.inner.ctx 174 | } 175 | 176 | /// Provides a mutable reference to the EVM context. 177 | pub const fn ctx_mut(&mut self) -> &mut EthEvmContext { 178 | &mut self.inner.ctx 179 | } 180 | } 181 | 182 | impl Deref for EthEvm { 183 | type Target = EthEvmContext; 184 | 185 | #[inline] 186 | fn deref(&self) -> &Self::Target { 187 | self.ctx() 188 | } 189 | } 190 | 191 | impl DerefMut for EthEvm { 192 | #[inline] 193 | fn deref_mut(&mut self) -> &mut Self::Target { 194 | self.ctx_mut() 195 | } 196 | } 197 | 198 | impl Evm for EthEvm 199 | where 200 | DB: Database, 201 | I: Inspector>, 202 | PRECOMPILE: PrecompileProvider, Output = InterpreterResult>, 203 | { 204 | type DB = DB; 205 | type Tx = TxEnv; 206 | type Error = EVMError; 207 | type HaltReason = HaltReason; 208 | type Spec = SpecId; 209 | type BlockEnv = BlockEnv; 210 | type Precompiles = PRECOMPILE; 211 | type Inspector = I; 212 | 213 | fn block(&self) -> &BlockEnv { 214 | &self.block 215 | } 216 | 217 | fn chain_id(&self) -> u64 { 218 | self.cfg.chain_id 219 | } 220 | 221 | fn transact_raw( 222 | &mut self, 223 | tx: Self::Tx, 224 | ) -> Result, Self::Error> { 225 | if self.inspect { 226 | self.inner.inspect_tx(tx) 227 | } else { 228 | self.inner.transact(tx) 229 | } 230 | } 231 | 232 | fn transact_system_call( 233 | &mut self, 234 | caller: Address, 235 | contract: Address, 236 | data: Bytes, 237 | ) -> Result, Self::Error> { 238 | self.inner.system_call_with_caller(caller, contract, data) 239 | } 240 | 241 | fn finish(self) -> (Self::DB, EvmEnv) { 242 | let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.ctx; 243 | 244 | (journaled_state.database, EvmEnv { block_env, cfg_env }) 245 | } 246 | 247 | fn set_inspector_enabled(&mut self, enabled: bool) { 248 | self.inspect = enabled; 249 | } 250 | 251 | fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles) { 252 | (&self.inner.ctx.journaled_state.database, &self.inner.inspector, &self.inner.precompiles) 253 | } 254 | 255 | fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles) { 256 | ( 257 | &mut self.inner.ctx.journaled_state.database, 258 | &mut self.inner.inspector, 259 | &mut self.inner.precompiles, 260 | ) 261 | } 262 | } 263 | 264 | /// Factory producing [`EthEvm`]. 265 | #[derive(Debug, Default, Clone, Copy)] 266 | #[non_exhaustive] 267 | pub struct EthEvmFactory; 268 | 269 | impl EvmFactory for EthEvmFactory { 270 | type Evm>> = EthEvm; 271 | type Context = Context; 272 | type Tx = TxEnv; 273 | type Error = EVMError; 274 | type HaltReason = HaltReason; 275 | type Spec = SpecId; 276 | type BlockEnv = BlockEnv; 277 | type Precompiles = PrecompilesMap; 278 | 279 | fn create_evm(&self, db: DB, input: EvmEnv) -> Self::Evm { 280 | EthEvmBuilder::new(db, input).build() 281 | } 282 | 283 | fn create_evm_with_inspector>>( 284 | &self, 285 | db: DB, 286 | input: EvmEnv, 287 | inspector: I, 288 | ) -> Self::Evm { 289 | EthEvmBuilder::new(db, input).activate_inspector(inspector).build() 290 | } 291 | } 292 | 293 | #[cfg(test)] 294 | mod tests { 295 | use super::*; 296 | use alloy_primitives::address; 297 | use revm::{database_interface::EmptyDB, primitives::hardfork::SpecId}; 298 | 299 | #[test] 300 | fn test_precompiles_with_correct_spec() { 301 | // create tests where precompile should be available for later specs but not earlier ones 302 | let specs_to_test = [ 303 | // MODEXP (0x05) was added in Byzantium, should not exist in Frontier 304 | ( 305 | address!("0x0000000000000000000000000000000000000005"), 306 | SpecId::FRONTIER, // Early spec - should NOT have this precompile 307 | SpecId::BYZANTIUM, // Later spec - should have this precompile 308 | "MODEXP", 309 | ), 310 | // BLAKE2F (0x09) was added in Istanbul, should not exist in Byzantium 311 | ( 312 | address!("0x0000000000000000000000000000000000000009"), 313 | SpecId::BYZANTIUM, // Early spec - should NOT have this precompile 314 | SpecId::ISTANBUL, // Later spec - should have this precompile 315 | "BLAKE2F", 316 | ), 317 | ]; 318 | 319 | for (precompile_addr, early_spec, later_spec, name) in specs_to_test { 320 | let mut early_cfg_env = CfgEnv::default(); 321 | early_cfg_env.spec = early_spec; 322 | early_cfg_env.chain_id = 1; 323 | 324 | let early_env = EvmEnv { block_env: BlockEnv::default(), cfg_env: early_cfg_env }; 325 | let factory = EthEvmFactory; 326 | let mut early_evm = factory.create_evm(EmptyDB::default(), early_env); 327 | 328 | // precompile should NOT be available in early spec 329 | assert!( 330 | early_evm.precompiles_mut().get(&precompile_addr).is_none(), 331 | "{name} precompile at {precompile_addr:?} should NOT be available for early spec {early_spec:?}" 332 | ); 333 | 334 | let mut later_cfg_env = CfgEnv::default(); 335 | later_cfg_env.spec = later_spec; 336 | later_cfg_env.chain_id = 1; 337 | 338 | let later_env = EvmEnv { block_env: BlockEnv::default(), cfg_env: later_cfg_env }; 339 | let mut later_evm = factory.create_evm(EmptyDB::default(), later_env); 340 | 341 | // precompile should be available in later spec 342 | assert!( 343 | later_evm.precompiles_mut().get(&precompile_addr).is_some(), 344 | "{name} precompile at {precompile_addr:?} should be available for later spec {later_spec:?}" 345 | ); 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /crates/evm/src/evm.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over EVM. 2 | 3 | use crate::{env::BlockEnvironment, tracing::TxTracer, EvmEnv, EvmError, IntoTxEnv}; 4 | use alloy_consensus::transaction::TxHashRef; 5 | use alloy_primitives::{Address, Bytes, B256}; 6 | use core::{error::Error, fmt::Debug, hash::Hash}; 7 | use revm::{ 8 | context::result::ExecutionResult, 9 | context_interface::{ 10 | result::{HaltReasonTr, ResultAndState}, 11 | ContextTr, 12 | }, 13 | inspector::{JournalExt, NoOpInspector}, 14 | DatabaseCommit, Inspector, 15 | }; 16 | 17 | /// Helper trait to bound [`revm::Database::Error`] with common requirements. 18 | pub trait Database: revm::Database + Debug {} 19 | impl Database for T where T: revm::Database + Debug {} 20 | 21 | /// An instance of an ethereum virtual machine. 22 | /// 23 | /// An EVM is commonly initialized with the corresponding block context and state and it's only 24 | /// purpose is to execute transactions. 25 | /// 26 | /// Executing a transaction will return the outcome of the transaction. 27 | pub trait Evm { 28 | /// Database type held by the EVM. 29 | type DB; 30 | /// The transaction object that the EVM will execute. 31 | /// 32 | /// This type represents the transaction environment that the EVM operates on internally. 33 | /// Typically this is [`revm::context::TxEnv`], which contains all necessary transaction 34 | /// data like sender, gas limits, value, and calldata. 35 | /// 36 | /// The EVM accepts flexible transaction inputs through the [`IntoTxEnv`] trait. This means 37 | /// that while the EVM internally works with `Self::Tx` (usually `TxEnv`), users can pass 38 | /// various transaction formats to [`Evm::transact`], including: 39 | /// - Direct [`TxEnv`](revm::context::TxEnv) instances 40 | /// - [`Recovered`](alloy_consensus::transaction::Recovered) where `T` implements 41 | /// [`crate::FromRecoveredTx`] 42 | /// - [`WithEncoded>`](alloy_eips::eip2718::WithEncoded) where `T` implements 43 | /// [`crate::FromTxWithEncoded`] 44 | /// 45 | /// This design allows the EVM to accept recovered consensus transactions seamlessly. 46 | type Tx: IntoTxEnv; 47 | /// Error type returned by EVM. Contains either errors related to invalid transactions or 48 | /// internal irrecoverable execution errors. 49 | type Error: EvmError; 50 | /// Halt reason. Enum over all possible reasons for halting the execution. When execution halts, 51 | /// it means that transaction is valid, however, it's execution was interrupted (e.g because of 52 | /// running out of gas or overflowing stack). 53 | type HaltReason: HaltReasonTr + Send + Sync + 'static; 54 | /// Identifier of the EVM specification. EVM is expected to use this identifier to determine 55 | /// which features are enabled. 56 | type Spec: Debug + Copy + Hash + Eq + Send + Sync + Default + 'static; 57 | /// Block environment used by the EVM. 58 | type BlockEnv: BlockEnvironment; 59 | /// Precompiles used by the EVM. 60 | type Precompiles; 61 | /// Evm inspector. 62 | type Inspector; 63 | 64 | /// Reference to [`Evm::BlockEnv`]. 65 | fn block(&self) -> &Self::BlockEnv; 66 | 67 | /// Returns the chain ID of the environment. 68 | fn chain_id(&self) -> u64; 69 | 70 | /// Executes a transaction and returns the outcome. 71 | fn transact_raw( 72 | &mut self, 73 | tx: Self::Tx, 74 | ) -> Result, Self::Error>; 75 | 76 | /// Same as [`Evm::transact_raw`], but takes any type implementing [`IntoTxEnv`]. 77 | /// 78 | /// This is the primary method for executing transactions. It accepts flexible input types 79 | /// that can be converted to the EVM's transaction environment, including: 80 | /// - [`TxEnv`](revm::context::TxEnv) - Direct transaction environment 81 | /// - [`Recovered`](alloy_consensus::transaction::Recovered) - Consensus transaction with 82 | /// recovered sender 83 | /// - [`WithEncoded>`](alloy_eips::eip2718::WithEncoded) - Transaction with sender 84 | /// and encoded bytes 85 | /// 86 | /// The conversion happens automatically through the [`IntoTxEnv`] trait. 87 | fn transact( 88 | &mut self, 89 | tx: impl IntoTxEnv, 90 | ) -> Result, Self::Error> { 91 | self.transact_raw(tx.into_tx_env()) 92 | } 93 | 94 | /// Executes a system call. 95 | /// 96 | /// Note: this will only keep the target `contract` in the state. This is done because revm is 97 | /// loading [`revm::context::Block::beneficiary`] into state by default, and we need to avoid it 98 | /// by also covering edge cases when beneficiary is set to the system contract address. 99 | fn transact_system_call( 100 | &mut self, 101 | caller: Address, 102 | contract: Address, 103 | data: Bytes, 104 | ) -> Result, Self::Error>; 105 | 106 | /// Returns an immutable reference to the underlying database. 107 | fn db(&self) -> &Self::DB { 108 | self.components().0 109 | } 110 | 111 | /// Returns a mutable reference to the underlying database. 112 | fn db_mut(&mut self) -> &mut Self::DB { 113 | self.components_mut().0 114 | } 115 | 116 | /// Executes a transaction and commits the state changes to the underlying database. 117 | fn transact_commit( 118 | &mut self, 119 | tx: impl IntoTxEnv, 120 | ) -> Result, Self::Error> 121 | where 122 | Self::DB: DatabaseCommit, 123 | { 124 | let ResultAndState { result, state } = self.transact(tx)?; 125 | self.db_mut().commit(state); 126 | 127 | Ok(result) 128 | } 129 | 130 | /// Consumes the EVM and returns the inner [`EvmEnv`]. 131 | fn finish(self) -> (Self::DB, EvmEnv) 132 | where 133 | Self: Sized; 134 | 135 | /// Consumes the EVM and returns the inner database. 136 | fn into_db(self) -> Self::DB 137 | where 138 | Self: Sized, 139 | { 140 | self.finish().0 141 | } 142 | 143 | /// Consumes the EVM and returns the inner [`EvmEnv`]. 144 | fn into_env(self) -> EvmEnv 145 | where 146 | Self: Sized, 147 | { 148 | self.finish().1 149 | } 150 | 151 | /// Determines whether additional transactions should be inspected or not. 152 | /// 153 | /// See also [`EvmFactory::create_evm_with_inspector`]. 154 | fn set_inspector_enabled(&mut self, enabled: bool); 155 | 156 | /// Enables the configured inspector. 157 | /// 158 | /// All additional transactions will be inspected if enabled. 159 | fn enable_inspector(&mut self) { 160 | self.set_inspector_enabled(true) 161 | } 162 | 163 | /// Disables the configured inspector. 164 | /// 165 | /// Transactions will no longer be inspected. 166 | fn disable_inspector(&mut self) { 167 | self.set_inspector_enabled(false) 168 | } 169 | 170 | /// Getter of precompiles. 171 | fn precompiles(&self) -> &Self::Precompiles { 172 | self.components().2 173 | } 174 | 175 | /// Mutable getter of precompiles. 176 | fn precompiles_mut(&mut self) -> &mut Self::Precompiles { 177 | self.components_mut().2 178 | } 179 | 180 | /// Getter of inspector. 181 | fn inspector(&self) -> &Self::Inspector { 182 | self.components().1 183 | } 184 | 185 | /// Mutable getter of inspector. 186 | fn inspector_mut(&mut self) -> &mut Self::Inspector { 187 | self.components_mut().1 188 | } 189 | 190 | /// Provides immutable references to the database, inspector and precompiles. 191 | fn components(&self) -> (&Self::DB, &Self::Inspector, &Self::Precompiles); 192 | 193 | /// Provides mutable references to the database, inspector and precompiles. 194 | fn components_mut(&mut self) -> (&mut Self::DB, &mut Self::Inspector, &mut Self::Precompiles); 195 | } 196 | 197 | /// An extension trait for [`Evm`] providing additional functionality. 198 | pub trait EvmExt: Evm { 199 | /// Replays all the transactions until the target transaction is found. 200 | /// 201 | /// This stops before transacting the target hash and commits all previous changes. 202 | /// 203 | /// Returns the index of the target transaction in the iterator. 204 | fn replay_transactions_until( 205 | &mut self, 206 | transactions: I, 207 | target_tx_hash: B256, 208 | ) -> Result 209 | where 210 | Self::DB: DatabaseCommit, 211 | I: IntoIterator, 212 | T: IntoTxEnv + TxHashRef, 213 | { 214 | let mut index = 0; 215 | for tx in transactions { 216 | if *tx.tx_hash() == target_tx_hash { 217 | // reached the target transaction 218 | break; 219 | } 220 | self.transact_commit(tx)?; 221 | index += 1; 222 | } 223 | Ok(index) 224 | } 225 | 226 | /// Replays all the previous transactions and returns the [`ResultAndState`] of the target 227 | /// transaction. 228 | /// 229 | /// Returns `None` if the target transaction was not found. 230 | fn replay_transaction( 231 | &mut self, 232 | transactions: I, 233 | target_tx_hash: B256, 234 | ) -> Result>, Self::Error> 235 | where 236 | Self::DB: DatabaseCommit, 237 | I: IntoIterator, 238 | T: IntoTxEnv + TxHashRef, 239 | { 240 | for tx in transactions { 241 | if *tx.tx_hash() == target_tx_hash { 242 | // reached the target transaction 243 | return self.transact(tx).map(Some); 244 | } else { 245 | self.transact_commit(tx)?; 246 | } 247 | } 248 | Ok(None) 249 | } 250 | } 251 | 252 | /// Automatic implementation of [`EvmExt`] for all types that implement [`Evm`]. 253 | impl EvmExt for T {} 254 | 255 | /// A type responsible for creating instances of an ethereum virtual machine given a certain input. 256 | pub trait EvmFactory { 257 | /// The EVM type that this factory creates. 258 | type Evm>>: Evm< 259 | DB = DB, 260 | Tx = Self::Tx, 261 | HaltReason = Self::HaltReason, 262 | Error = Self::Error, 263 | Spec = Self::Spec, 264 | BlockEnv = Self::BlockEnv, 265 | Precompiles = Self::Precompiles, 266 | Inspector = I, 267 | >; 268 | 269 | /// The EVM context for inspectors 270 | type Context: ContextTr; 271 | /// Transaction environment. 272 | type Tx: IntoTxEnv; 273 | /// EVM error. See [`Evm::Error`]. 274 | type Error: EvmError; 275 | /// Halt reason. See [`Evm::HaltReason`]. 276 | type HaltReason: HaltReasonTr + Send + Sync + 'static; 277 | /// The EVM specification identifier, see [`Evm::Spec`]. 278 | type Spec: Debug + Copy + Hash + Eq + Send + Sync + Default + 'static; 279 | /// Block environment used by the EVM. See [`Evm::BlockEnv`]. 280 | type BlockEnv: BlockEnvironment; 281 | /// Precompiles used by the EVM. 282 | type Precompiles; 283 | 284 | /// Creates a new instance of an EVM. 285 | fn create_evm( 286 | &self, 287 | db: DB, 288 | evm_env: EvmEnv, 289 | ) -> Self::Evm; 290 | 291 | /// Creates a new instance of an EVM with an inspector. 292 | /// 293 | /// Note: It is expected that the [`Inspector`] is usually provided as `&mut Inspector` so that 294 | /// it remains owned by the call site when [`Evm::transact`] is invoked. 295 | fn create_evm_with_inspector>>( 296 | &self, 297 | db: DB, 298 | input: EvmEnv, 299 | inspector: I, 300 | ) -> Self::Evm; 301 | } 302 | 303 | /// An extension trait for [`EvmFactory`] providing useful non-overridable methods. 304 | pub trait EvmFactoryExt: EvmFactory { 305 | /// Creates a new [`TxTracer`] instance with the given database, input and fused inspector. 306 | fn create_tracer( 307 | &self, 308 | db: DB, 309 | input: EvmEnv, 310 | fused_inspector: I, 311 | ) -> TxTracer> 312 | where 313 | DB: Database + DatabaseCommit, 314 | I: Inspector> + Clone, 315 | { 316 | TxTracer::new(self.create_evm_with_inspector(db, input, fused_inspector)) 317 | } 318 | } 319 | 320 | impl EvmFactoryExt for T {} 321 | --------------------------------------------------------------------------------