├── .config ├── nextest.toml └── zepter.yaml ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── BUG-FORM.yml │ ├── FEATURE-FORM.yml │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md ├── scripts │ └── install_test_binaries.sh └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── cliff.toml ├── clippy.toml ├── crates ├── evm │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── block │ │ ├── calc.rs │ │ ├── error.rs │ │ ├── mod.rs │ │ ├── state_changes.rs │ │ ├── state_hook.rs │ │ └── system_calls │ │ │ ├── eip2935.rs │ │ │ ├── eip4788.rs │ │ │ ├── eip7002.rs │ │ │ ├── eip7251.rs │ │ │ └── mod.rs │ │ ├── either.rs │ │ ├── env.rs │ │ ├── error.rs │ │ ├── eth │ │ ├── block.rs │ │ ├── dao_fork.rs │ │ ├── eip6110.rs │ │ ├── mod.rs │ │ ├── receipt_builder.rs │ │ └── spec.rs │ │ ├── evm.rs │ │ ├── lib.rs │ │ ├── precompiles.rs │ │ └── tx.rs └── op-evm │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── block │ ├── canyon.rs │ ├── mod.rs │ └── receipt_builder.rs │ └── lib.rs ├── deny.toml ├── release.toml ├── rustfmt.toml └── scripts ├── changelog.sh └── check_no_std.sh /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @mattsse @klkvr 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/scripts/install_test_binaries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Installs Geth binary 3 | # Note: intended for use only with CI (x86_64 Ubuntu, MacOS or Windows) 4 | set -e 5 | 6 | GETH_BUILD=${GETH_BUILD:-"1.14.8-a9523b64"} 7 | RETH_BUILD=${RETH_BUILD:-"1.0.6"} 8 | 9 | BIN_DIR=${BIN_DIR:-"$HOME/bin"} 10 | 11 | PLATFORM="$(uname -s | awk '{print tolower($0)}')" 12 | 13 | main() { 14 | mkdir -p "$BIN_DIR" 15 | cd "$BIN_DIR" 16 | export PATH="$BIN_DIR:$PATH" 17 | if [ "$GITHUB_PATH" ]; then 18 | echo "$BIN_DIR" >> "$GITHUB_PATH" 19 | fi 20 | 21 | install_geth & 22 | install_reth & 23 | 24 | wait 25 | } 26 | 27 | # Installs geth from https://geth.ethereum.org/downloads 28 | install_geth() { 29 | case "$PLATFORM" in 30 | linux) 31 | NAME="geth-$PLATFORM-amd64-$GETH_BUILD" 32 | curl -sL "https://gethstore.blob.core.windows.net/builds/$NAME.tar.gz" | tar -xzf - 33 | mv -f "$NAME/geth" ./ 34 | rm -rf "$NAME" 35 | chmod +x geth 36 | ;; 37 | *) 38 | NAME="geth-windows-amd64-$GETH_BUILD" 39 | curl -so $NAME.zip "https://gethstore.blob.core.windows.net/builds/$NAME.zip" 40 | unzip $NAME.zip 41 | mv -f "$NAME/geth.exe" ./ 42 | rm -rf "$NAME" "$NAME.zip" 43 | ;; 44 | esac 45 | 46 | echo "" 47 | echo "Installed Geth:" 48 | geth version 49 | } 50 | 51 | # Install reth from https://github.com/paradigmxyz/reth/releases 52 | install_reth() { 53 | case "$PLATFORM" in 54 | linux) 55 | NAME="reth-v$RETH_BUILD-x86_64-unknown-linux-gnu" 56 | curl -sL "https://github.com/paradigmxyz/reth/releases/download/v$RETH_BUILD/$NAME.tar.gz" | tar -xzf - 57 | chmod +x reth 58 | 59 | echo "" 60 | echo "Installed Reth:" 61 | reth --version 62 | ;; 63 | esac 64 | } 65 | 66 | main -------------------------------------------------------------------------------- /.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.85" # 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.85" # MSRV 38 | flags: "--all-features" 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: dtolnay/rust-toolchain@master 42 | with: 43 | toolchain: ${{ matrix.rust }} 44 | - name: Install Anvil 45 | uses: foundry-rs/foundry-toolchain@v1 46 | with: 47 | version: nightly 48 | - name: Install test binaries 49 | shell: bash 50 | run: ./.github/scripts/install_test_binaries.sh 51 | - uses: Swatinem/rust-cache@v2 52 | with: 53 | cache-on-failure: true 54 | # Only run tests on latest stable and above 55 | - name: Install cargo-nextest 56 | if: ${{ matrix.rust != '1.85' }} # MSRV 57 | uses: taiki-e/install-action@nextest 58 | - name: build 59 | if: ${{ matrix.rust == '1.85' }} # MSRV 60 | run: cargo build --workspace ${{ matrix.flags }} 61 | - name: test 62 | shell: bash 63 | if: ${{ matrix.rust != '1.85' }} # MSRV 64 | run: cargo nextest run --workspace ${{ matrix.flags }} 65 | 66 | doctest: 67 | runs-on: ubuntu-latest 68 | timeout-minutes: 30 69 | steps: 70 | - uses: actions/checkout@v4 71 | - uses: dtolnay/rust-toolchain@stable 72 | - uses: Swatinem/rust-cache@v2 73 | with: 74 | cache-on-failure: true 75 | - run: cargo test --workspace --doc 76 | - run: cargo test --all-features --workspace --doc 77 | 78 | wasm-unknown: 79 | runs-on: ubuntu-latest 80 | timeout-minutes: 30 81 | steps: 82 | - uses: actions/checkout@v4 83 | - uses: dtolnay/rust-toolchain@stable 84 | with: 85 | target: wasm32-unknown-unknown 86 | - uses: taiki-e/install-action@cargo-hack 87 | - uses: Swatinem/rust-cache@v2 88 | with: 89 | cache-on-failure: true 90 | - name: cargo hack 91 | run: | 92 | cargo hack build --workspace --ignore-unknown-features --features ws --target wasm32-unknown-unknown --no-default-features 93 | 94 | wasm-wasi: 95 | runs-on: ubuntu-latest 96 | timeout-minutes: 30 97 | steps: 98 | - uses: actions/checkout@v4 99 | - uses: dtolnay/rust-toolchain@stable 100 | with: 101 | target: wasm32-wasip1 102 | - uses: taiki-e/install-action@cargo-hack 103 | - uses: Swatinem/rust-cache@v2 104 | with: 105 | cache-on-failure: true 106 | - name: cargo hack 107 | run: | 108 | cargo hack build --workspace --target wasm32-wasip1 109 | 110 | feature-checks: 111 | runs-on: ubuntu-latest 112 | timeout-minutes: 30 113 | steps: 114 | - uses: actions/checkout@v4 115 | - uses: dtolnay/rust-toolchain@stable 116 | - uses: taiki-e/install-action@cargo-hack 117 | - uses: Swatinem/rust-cache@v2 118 | with: 119 | cache-on-failure: true 120 | - name: cargo hack 121 | run: cargo hack check --feature-powerset --depth 1 122 | 123 | check-no-std: 124 | name: check no_std ${{ matrix.features }} 125 | runs-on: ubuntu-latest 126 | timeout-minutes: 30 127 | steps: 128 | - uses: actions/checkout@v4 129 | - uses: dtolnay/rust-toolchain@stable 130 | with: 131 | targets: riscv32imac-unknown-none-elf 132 | - uses: taiki-e/install-action@cargo-hack 133 | - uses: Swatinem/rust-cache@v2 134 | with: 135 | cache-on-failure: true 136 | - run: ./scripts/check_no_std.sh 137 | 138 | clippy: 139 | runs-on: ubuntu-latest 140 | timeout-minutes: 30 141 | steps: 142 | - uses: actions/checkout@v4 143 | - uses: dtolnay/rust-toolchain@master 144 | with: 145 | toolchain: nightly 146 | components: clippy 147 | - uses: Swatinem/rust-cache@v2 148 | with: 149 | cache-on-failure: true 150 | - run: cargo +nightly clippy --workspace --all-targets --all-features 151 | env: 152 | RUSTFLAGS: -Dwarnings 153 | 154 | docs: 155 | runs-on: ubuntu-latest 156 | timeout-minutes: 30 157 | steps: 158 | - uses: actions/checkout@v4 159 | - uses: dtolnay/rust-toolchain@nightly 160 | - uses: Swatinem/rust-cache@v2 161 | with: 162 | cache-on-failure: true 163 | - name: Build documentation 164 | run: cargo doc --workspace --all-features --no-deps --document-private-items 165 | env: 166 | RUSTDOCFLAGS: --cfg docsrs -D warnings --show-type-layout --generate-link-to-definition -Zunstable-options 167 | 168 | fmt: 169 | runs-on: ubuntu-latest 170 | timeout-minutes: 30 171 | steps: 172 | - uses: actions/checkout@v4 173 | - uses: dtolnay/rust-toolchain@nightly 174 | with: 175 | components: rustfmt 176 | - run: cargo fmt --all --check 177 | 178 | deny: 179 | uses: ithacaxyz/ci/.github/workflows/deny.yml@main 180 | 181 | ci-success: 182 | name: ci success 183 | runs-on: ubuntu-latest 184 | if: always() 185 | needs: 186 | - test 187 | - doctest 188 | - wasm-unknown 189 | - wasm-wasi 190 | - feature-checks 191 | - check-no-std 192 | - clippy 193 | - docs 194 | - fmt 195 | - deny 196 | steps: 197 | - name: Decide whether the needed jobs succeeded or failed 198 | uses: re-actors/alls-green@release/v1 199 | with: 200 | jobs: ${{ toJSON(needs) }} 201 | 202 | # Check crates correctly propagate features 203 | feature-propagation: 204 | runs-on: ubuntu-latest 205 | timeout-minutes: 20 206 | steps: 207 | - uses: actions/checkout@v4 208 | - name: run zepter 209 | run: | 210 | cargo install zepter -f --locked 211 | zepter --version 212 | time zepter run check -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .vscode 4 | .idea 5 | .env 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.10.0](https://github.com/alloy-rs/evm/releases/tag/v0.10.0) - 2025-05-23 9 | 10 | ### Dependencies 11 | 12 | - [`deps`] Bump revm to `24.0.0` and op-revm to `5.0.0` ([#98](https://github.com/alloy-rs/evm/issues/98)) 13 | 14 | ### Features 15 | 16 | - Implement from_recovered_tx for txDeposit nativel ([#96](https://github.com/alloy-rs/evm/issues/96)) 17 | 18 | ### Miscellaneous Tasks 19 | 20 | - Preparing for mint nonoptional in reth ([#91](https://github.com/alloy-rs/evm/issues/91)) 21 | 22 | ## [0.9.1](https://github.com/alloy-rs/evm/releases/tag/v0.9.1) - 2025-05-20 23 | 24 | ### Features 25 | 26 | - Implement `FromTxWithEncoded` and `FromRecoveredTx` from `OpTxEnvelope` for `TxEnv` ([#94](https://github.com/alloy-rs/evm/issues/94)) 27 | 28 | ### Miscellaneous Tasks 29 | 30 | - Release 0.9.1 31 | 32 | ## [0.9.0](https://github.com/alloy-rs/evm/releases/tag/v0.9.0) - 2025-05-20 33 | 34 | ### Features 35 | 36 | - Add non-mutable getters for `inspector` and `precompiles` ([#93](https://github.com/alloy-rs/evm/issues/93)) 37 | - `BlockExecutor::execute_transaction_with_commit_condition` ([#92](https://github.com/alloy-rs/evm/issues/92)) 38 | 39 | ### Miscellaneous Tasks 40 | 41 | - Release 0.9.0 42 | 43 | ## [0.8.1](https://github.com/alloy-rs/evm/releases/tag/v0.8.1) - 2025-05-16 44 | 45 | ### Features 46 | 47 | - Extend Evm::Spec bounds with Hash and PartialEq ([#88](https://github.com/alloy-rs/evm/issues/88)) 48 | 49 | ### Miscellaneous Tasks 50 | 51 | - Release 0.8.1 52 | 53 | ## [0.8.0](https://github.com/alloy-rs/evm/releases/tag/v0.8.0) - 2025-05-13 54 | 55 | ### Dependencies 56 | 57 | - Bump alloy 1.0.0 ([#87](https://github.com/alloy-rs/evm/issues/87)) 58 | 59 | ### Miscellaneous Tasks 60 | 61 | - Release 0.8.0 62 | 63 | ## [0.7.2](https://github.com/alloy-rs/evm/releases/tag/v0.7.2) - 2025-05-12 64 | 65 | ### Bug Fixes 66 | 67 | - `r.as_ref()` the trait `AsRef<[_; 0]>` is not implemented for `[u8]` ([#86](https://github.com/alloy-rs/evm/issues/86)) 68 | 69 | ### Miscellaneous Tasks 70 | 71 | - Release 0.7.2 72 | 73 | ### Styling 74 | 75 | - Impl Evm for Either ([#84](https://github.com/alloy-rs/evm/issues/84)) 76 | 77 | ## [0.7.1](https://github.com/alloy-rs/evm/releases/tag/v0.7.1) - 2025-05-09 78 | 79 | ### Dependencies 80 | 81 | - Bump op-revm ([#85](https://github.com/alloy-rs/evm/issues/85)) 82 | 83 | ### Miscellaneous Tasks 84 | 85 | - Release 0.7.1 86 | 87 | ## [0.7.0](https://github.com/alloy-rs/evm/releases/tag/v0.7.0) - 2025-05-08 88 | 89 | ### Bug Fixes 90 | 91 | - Use HashMap::with_capacity_and_hasher ([#83](https://github.com/alloy-rs/evm/issues/83)) 92 | 93 | ### Dependencies 94 | 95 | - Bump op-revm ([#79](https://github.com/alloy-rs/evm/issues/79)) 96 | 97 | ### Features 98 | 99 | - Expose Inspector on Evm ([#81](https://github.com/alloy-rs/evm/issues/81)) 100 | - [eip7702] Delegate signer recovery to `alloy-consensus::crypto` ([#82](https://github.com/alloy-rs/evm/issues/82)) 101 | - Bump revm ([#74](https://github.com/alloy-rs/evm/issues/74)) 102 | - Include Precompiles associated type in Evm trait ([#73](https://github.com/alloy-rs/evm/issues/73)) 103 | - Add SpecPrecompiles ([#71](https://github.com/alloy-rs/evm/issues/71)) 104 | 105 | ### Miscellaneous Tasks 106 | 107 | - Release 0.7.0 108 | - Use as_ref ([#80](https://github.com/alloy-rs/evm/issues/80)) 109 | 110 | ### Styling 111 | 112 | - Re-export revm & op-revm ([#77](https://github.com/alloy-rs/evm/issues/77)) 113 | 114 | ## [0.6.0](https://github.com/alloy-rs/evm/releases/tag/v0.6.0) - 2025-04-23 115 | 116 | ### Dependencies 117 | 118 | - Bump alloy 0.15 ([#72](https://github.com/alloy-rs/evm/issues/72)) 119 | 120 | ### Miscellaneous Tasks 121 | 122 | - Release 0.6.0 123 | 124 | ## [0.5.0](https://github.com/alloy-rs/evm/releases/tag/v0.5.0) - 2025-04-15 125 | 126 | ### Dependencies 127 | 128 | - Bump `op-alloy-consensus` ([#66](https://github.com/alloy-rs/evm/issues/66)) 129 | - Bump `op-revm` to `3.0.1` ([#65](https://github.com/alloy-rs/evm/issues/65)) 130 | 131 | ### Features 132 | 133 | - Added method to get chain id ([#62](https://github.com/alloy-rs/evm/issues/62)) 134 | 135 | ### Miscellaneous Tasks 136 | 137 | - Release 0.5.0 138 | 139 | ## [0.4.0](https://github.com/alloy-rs/evm/releases/tag/v0.4.0) - 2025-04-09 140 | 141 | ### Dependencies 142 | 143 | - Alloy 0.14 ([#63](https://github.com/alloy-rs/evm/issues/63)) 144 | 145 | ### Miscellaneous Tasks 146 | 147 | - Release 0.4.0 148 | 149 | ## [0.3.2](https://github.com/alloy-rs/evm/releases/tag/v0.3.2) - 2025-04-08 150 | 151 | ### Features 152 | 153 | - Add fn evm(&self) ([#60](https://github.com/alloy-rs/evm/issues/60)) 154 | 155 | ### Miscellaneous Tasks 156 | 157 | - Release 0.3.2 158 | 159 | ## [0.3.1](https://github.com/alloy-rs/evm/releases/tag/v0.3.1) - 2025-04-02 160 | 161 | ### Features 162 | 163 | - Add missing trait impls for ref types ([#58](https://github.com/alloy-rs/evm/issues/58)) 164 | 165 | ### Miscellaneous Tasks 166 | 167 | - Release 0.3.1 168 | 169 | ## [0.3.0](https://github.com/alloy-rs/evm/releases/tag/v0.3.0) - 2025-04-02 170 | 171 | ### Features 172 | 173 | - [tx] Add `FromTxWithEncoded` bound to `BlockExecutor` transaction ([#54](https://github.com/alloy-rs/evm/issues/54)) 174 | - [tx] Relax bounds on `TxEip4844` for `EthereumTxEnvelope` ([#57](https://github.com/alloy-rs/evm/issues/57)) 175 | - [tx] Implement `FromTxWithEncoded` and `FromRecoveredTx` for `EthereumTxEnvelope` ([#56](https://github.com/alloy-rs/evm/issues/56)) 176 | 177 | ### Miscellaneous Tasks 178 | 179 | - Release 0.3.0 180 | 181 | ### Other 182 | 183 | - Rm precise pin ([#55](https://github.com/alloy-rs/evm/issues/55)) 184 | - Added execute_block ([#50](https://github.com/alloy-rs/evm/issues/50)) 185 | 186 | ## [0.2.0](https://github.com/alloy-rs/evm/releases/tag/v0.2.0) - 2025-03-28 187 | 188 | ### Dependencies 189 | 190 | - Bump deps revm alloy ([#48](https://github.com/alloy-rs/evm/issues/48)) 191 | 192 | ### Features 193 | 194 | - Add helper trait for deriving `TxEnv` from `WithEncoded` ([#42](https://github.com/alloy-rs/evm/issues/42)) 195 | - [op-receipt-builder] Add Debug trait to OpReceiptBuilder. ([#47](https://github.com/alloy-rs/evm/issues/47)) 196 | 197 | ### Miscellaneous Tasks 198 | 199 | - Release 0.2.0 200 | 201 | 202 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Alloy 2 | 3 | :balloon: Thanks for your help improving the project! We are so happy to have 4 | you! 5 | 6 | There are opportunities to contribute to Alloy at any level. It doesn't 7 | matter if you are just getting started with Rust or are the most weathered 8 | expert, we can use your help. 9 | 10 | **No contribution is too small and all contributions are valued.** 11 | 12 | This guide will help you get started. **Do not let this guide intimidate you**. 13 | It should be considered a map to help you navigate the process. 14 | 15 | The [dev channel][dev] is available for any concerns not covered in this guide, 16 | please join us! 17 | 18 | [dev]: https://t.me/ethers_rs 19 | 20 | ## Conduct 21 | 22 | The Alloy project adheres to the [Rust Code of Conduct][coc]. This describes 23 | the _minimum_ behavior expected from all contributors. 24 | 25 | [coc]: https://www.rust-lang.org/policies/code-of-conduct 26 | 27 | ## Contributing in Issues 28 | 29 | For any issue, there are fundamentally three ways an individual can contribute: 30 | 31 | 1. By opening the issue for discussion: For instance, if you believe that you 32 | have uncovered a bug in Alloy, creating a new issue in the Alloy 33 | issue tracker is the way to report it. 34 | 35 | 2. By helping to triage the issue: This can be done by providing 36 | supporting details (a test case that demonstrates a bug), providing 37 | suggestions on how to address the issue, or ensuring that the issue is tagged 38 | correctly. 39 | 40 | 3. By helping to resolve the issue: Typically this is done either in the form of 41 | demonstrating that the issue reported is not a problem after all, or more 42 | often, by opening a Pull Request that changes some bit of something in 43 | Alloy in a concrete and reviewable manner. 44 | 45 | **Anybody can participate in any stage of contribution**. We urge you to 46 | participate in the discussion around bugs and participate in reviewing PRs. 47 | 48 | ### Asking for General Help 49 | 50 | If you have reviewed existing documentation and still have questions or are 51 | having problems, you can open an issue asking for help. 52 | 53 | In exchange for receiving help, we ask that you contribute back a documentation 54 | PR that helps others avoid the problems that you encountered. 55 | 56 | ### Submitting a Bug Report 57 | 58 | When opening a new issue in the Alloy issue tracker, users will be presented 59 | with a [basic template][template] that should be filled in. If you believe that 60 | you have uncovered a bug, please fill out this form, following the template to 61 | the best of your ability. Do not worry if you cannot answer every detail, just 62 | fill in what you can. 63 | 64 | The two most important pieces of information we need in order to properly 65 | evaluate the report is a description of the behavior you are seeing and a simple 66 | test case we can use to recreate the problem on our own. If we cannot recreate 67 | the issue, it becomes impossible for us to fix. 68 | 69 | In order to rule out the possibility of bugs introduced by userland code, test 70 | cases should be limited, as much as possible, to using only Alloy APIs. 71 | 72 | See [How to create a Minimal, Complete, and Verifiable example][mcve]. 73 | 74 | [mcve]: https://stackoverflow.com/help/mcve 75 | [template]: .github/PULL_REQUEST_TEMPLATE.md 76 | 77 | ### Triaging a Bug Report 78 | 79 | Once an issue has been opened, it is not uncommon for there to be discussion 80 | around it. Some contributors may have differing opinions about the issue, 81 | including whether the behavior being seen is a bug or a feature. This discussion 82 | is part of the process and should be kept focused, helpful, and professional. 83 | 84 | Short, clipped responses—that provide neither additional context nor supporting 85 | detail—are not helpful or professional. To many, such responses are simply 86 | annoying and unfriendly. 87 | 88 | Contributors are encouraged to help one another make forward progress as much as 89 | possible, empowering one another to solve issues collaboratively. If you choose 90 | to comment on an issue that you feel either is not a problem that needs to be 91 | fixed, or if you encounter information in an issue that you feel is incorrect, 92 | explain why you feel that way with additional supporting context, and be willing 93 | to be convinced that you may be wrong. By doing so, we can often reach the 94 | correct outcome much faster. 95 | 96 | ### Resolving a Bug Report 97 | 98 | In the majority of cases, issues are resolved by opening a Pull Request. The 99 | process for opening and reviewing a Pull Request is similar to that of opening 100 | and triaging issues, but carries with it a necessary review and approval 101 | workflow that ensures that the proposed changes meet the minimal quality and 102 | functional guidelines of the Alloy project. 103 | 104 | ## Pull Requests 105 | 106 | Pull Requests are the way concrete changes are made to the code, documentation, 107 | and dependencies in the Alloy repository. 108 | 109 | Even tiny pull requests (e.g., one character pull request fixing a typo in API 110 | documentation) are greatly appreciated. Before making a large change, it is 111 | usually a good idea to first open an issue describing the change to solicit 112 | feedback and guidance. This will increase the likelihood of the PR getting 113 | merged. 114 | 115 | When opening a PR **please select the "Allow Edits From Maintainers" option**. 116 | Alloy maintains strict standards for code quality and style, as well as 117 | commit signing. This option allows us to make small changes to your PR to bring 118 | it in line with these standards. It helps us get your PR in faster, and with 119 | less work from you. 120 | 121 | ### Contributions Related to Spelling and Grammar 122 | 123 | At this time, we will not be accepting contributions that only fix spelling or grammatical errors in documentation, code or 124 | elsewhere. 125 | 126 | ### Cargo Commands 127 | 128 | This section lists some commonly needed commands. 129 | 130 | ```sh 131 | cargo check --all-features 132 | cargo +nightly fmt --all 133 | cargo build --all-features 134 | cargo test --all-features 135 | cargo test --no-default-features 136 | cargo +nightly clippy --all-features 137 | ``` 138 | 139 | ### Tests 140 | 141 | If the change being proposed alters code (as opposed to only documentation for 142 | example), it is either adding new functionality to Alloy or it is fixing 143 | existing, broken functionality. In both of these cases, the pull request should 144 | include one or more tests to ensure that Alloy does not regress in the 145 | future. 146 | 147 | #### Unit Tests 148 | 149 | Functions which have very specific tasks should be unit tested. We encourage 150 | using table tests to cover a large number of cases in a succinct readable 151 | manner. 152 | 153 | #### Integration tests 154 | 155 | Integration tests go in the same crate as the code they are testing, in the 156 | `tests/` directory. 157 | 158 | The best strategy for writing a new integration test is to look at existing 159 | integration tests in the crate and follow the style. 160 | 161 | #### Documentation tests 162 | 163 | Ideally, every API has at least one [documentation test] that demonstrates how 164 | to use the API. Documentation tests are run with `cargo test --doc`. This 165 | ensures that the example is correct and provides additional test coverage. 166 | 167 | The trick to documentation tests is striking a balance between being succinct 168 | for a reader to understand and actually testing the API. 169 | 170 | Lines that start with `/// #` are removed when the documentation is generated. 171 | They are only there to get the test to run. Use this trick to keep your example 172 | succinct in the user-facing tests :) 173 | 174 | ### Commits 175 | 176 | It is a recommended best practice to keep your changes as logically grouped as 177 | possible within individual commits. There is no limit to the number of commits 178 | any single Pull Request may have, and many contributors find it easier to review 179 | changes that are split across multiple commits. 180 | 181 | That said, if you have a number of commits that are "checkpoints" and don't 182 | represent a single logical change, please squash those together. 183 | 184 | Note that multiple commits often get squashed when they are landed (see the 185 | notes about [commit squashing](#commit-squashing)). 186 | 187 | #### Commit message guidelines 188 | 189 | Commit messages should follow the 190 | [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 191 | specification. 192 | 193 | Here's a few examples from the master branch's commit log: 194 | 195 | - feat(abigen): support empty events 196 | - chore: bump crypto deps 197 | - test: simplify test cleanup 198 | - fmt: run rustfmt 199 | 200 | ### Opening the Pull Request 201 | 202 | From within GitHub, opening a new Pull Request will present you with a 203 | [template] that should be filled out. Please try to do your best at filling out 204 | the details, but feel free to skip parts if you're not sure what to put. 205 | 206 | [template]: .github/PULL_REQUEST_TEMPLATE.md 207 | 208 | ### Discuss and update 209 | 210 | You will probably get feedback or requests for changes to your Pull Request. 211 | This is a big part of the submission process so don't be discouraged! Some 212 | contributors may sign off on the Pull Request right away, others may have 213 | more detailed comments or feedback. This is a necessary part of the process 214 | in order to evaluate whether the changes are correct and necessary. 215 | 216 | **Any community member can review a PR and you might get conflicting feedback**. 217 | Keep an eye out for comments from code owners to provide guidance on conflicting 218 | feedback. 219 | 220 | **Once the PR is open, do not rebase the commits**. See 221 | [Commit Squashing](#commit-squashing) for more details. 222 | 223 | ### Commit Squashing 224 | 225 | In most cases, **do not squash commits that you add to your Pull Request during 226 | the review process**. When the commits in your Pull Request land, they may be 227 | squashed into one commit per logical change. Metadata will be added to the 228 | commit message (including links to the Pull Request, links to relevant issues, 229 | and the names of the reviewers). The commit history of your Pull Request, 230 | however, will stay intact on the Pull Request page. 231 | 232 | ## Reviewing Pull Requests 233 | 234 | **Any Alloy community member is welcome to review any pull request**. 235 | 236 | All Alloy contributors who choose to review and provide feedback on Pull 237 | Requests have a responsibility to both the project and the individual making the 238 | contribution. Reviews and feedback must be helpful, insightful, and geared 239 | towards improving the contribution as opposed to simply blocking it. If there 240 | are reasons why you feel the PR should not land, explain what those are. Do not 241 | expect to be able to block a Pull Request from advancing simply because you say 242 | "No" without giving an explanation. Be open to having your mind changed. Be open 243 | to working with the contributor to make the Pull Request better. 244 | 245 | Reviews that are dismissive or disrespectful of the contributor or any other 246 | reviewers are strictly counter to the Code of Conduct. 247 | 248 | When reviewing a Pull Request, the primary goals are for the codebase to improve 249 | and for the person submitting the request to succeed. **Even if a Pull Request 250 | does not land, the submitters should come away from the experience feeling like 251 | their effort was not wasted or unappreciated**. Every Pull Request from a new 252 | contributor is an opportunity to grow the community. 253 | 254 | ### Review a bit at a time. 255 | 256 | Do not overwhelm new contributors. 257 | 258 | It is tempting to micro-optimize and make everything about relative performance, 259 | perfect grammar, or exact style matches. Do not succumb to that temptation. 260 | 261 | Focus first on the most significant aspects of the change: 262 | 263 | 1. Does this change make sense for Alloy? 264 | 2. Does this change make Alloy better, even if only incrementally? 265 | 3. Are there clear bugs or larger scale issues that need attending to? 266 | 4. Is the commit message readable and correct? If it contains a breaking change 267 | is it clear enough? 268 | 269 | Note that only **incremental** improvement is needed to land a PR. This means 270 | that the PR does not need to be perfect, only better than the status quo. Follow 271 | up PRs may be opened to continue iterating. 272 | 273 | When changes are necessary, _request_ them, do not _demand_ them, and **do not 274 | assume that the submitter already knows how to add a test or run a benchmark**. 275 | 276 | Specific performance optimization techniques, coding styles and conventions 277 | change over time. The first impression you give to a new contributor never does. 278 | 279 | Nits (requests for small changes that are not essential) are fine, but try to 280 | avoid stalling the Pull Request. Most nits can typically be fixed by the 281 | Alloy Collaborator landing the Pull Request but they can also be an 282 | opportunity for the contributor to learn a bit more about the project. 283 | 284 | It is always good to clearly indicate nits when you comment: e.g. 285 | `Nit: change foo() to bar(). But this is not blocking.` 286 | 287 | If your comments were addressed but were not folded automatically after new 288 | commits or if they proved to be mistaken, please, [hide them][hiding-a-comment] 289 | with the appropriate reason to keep the conversation flow concise and relevant. 290 | 291 | ### Be aware of the person behind the code 292 | 293 | Be aware that _how_ you communicate requests and reviews in your feedback can 294 | have a significant impact on the success of the Pull Request. Yes, we may land 295 | a particular change that makes Alloy better, but the individual might just 296 | not want to have anything to do with Alloy ever again. The goal is not just 297 | having good code. 298 | 299 | ### Abandoned or Stalled Pull Requests 300 | 301 | If a Pull Request appears to be abandoned or stalled, it is polite to first 302 | check with the contributor to see if they intend to continue the work before 303 | checking if they would mind if you took it over (especially if it just has nits 304 | left). When doing so, it is courteous to give the original contributor credit 305 | for the work they started (either by preserving their name and email address in 306 | the commit log, or by using an `Author: ` meta-data tag in the commit. 307 | 308 | _Adapted from the [Tokio contributing guide]_. 309 | 310 | [hiding-a-comment]: https://help.github.com/articles/managing-disruptive-comments/#hiding-a-comment 311 | [documentation test]: https://doc.rust-lang.org/rustdoc/documentation-tests.html 312 | [Tokio contributing guide]: https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md 313 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | version = "0.10.0" 7 | edition = "2021" 8 | rust-version = "1.85" 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 = "allow" # TODO: https://github.com/rust-lang/rust-clippy/issues/14020 29 | use-self = "warn" 30 | option-if-let-else = "warn" 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.10.0", path = "crates/evm", default-features = false } 39 | alloy-op-evm = { version = "0.10.0", 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", default-features = false } 45 | alloy-consensus = { version = "1.0.0", 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.2" } 49 | 50 | # op-alloy 51 | alloy-op-hardforks = { version = "0.2" } 52 | op-alloy-consensus = { version = "0.17", default-features = false } 53 | 54 | # revm 55 | revm = { version = "24.0.0", default-features = false } 56 | op-revm = { version = "5.0.0", default-features = false } 57 | 58 | # misc 59 | auto_impl = "1" 60 | derive_more = { version = "2", default-features = false, features = ["full"] } 61 | serde = { version = "1", default-features = false, features = ["derive"] } 62 | thiserror = { version = "2.0.0", default-features = false } 63 | serde_json = "1" 64 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.85" 2 | -------------------------------------------------------------------------------- /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 | 23 | revm.workspace = true 24 | op-revm = { workspace = true, optional = true } 25 | op-alloy-consensus = { workspace = true, optional = true } 26 | 27 | auto_impl.workspace = true 28 | derive_more.workspace = true 29 | thiserror.workspace = true 30 | 31 | [dev-dependencies] 32 | alloy-primitives = { workspace = true, features = ["serde"] } 33 | serde_json.workspace = true 34 | 35 | [features] 36 | default = ["std"] 37 | secp256k1 = [ 38 | "std", 39 | "alloy-consensus/secp256k1", 40 | ] 41 | std = [ 42 | "alloy-primitives/std", 43 | "revm/std", 44 | "alloy-consensus/std", 45 | "alloy-eips/std", 46 | "alloy-sol-types/std", 47 | "derive_more/std", 48 | "op-revm?/std", 49 | "thiserror/std", 50 | "op-alloy-consensus?/std" 51 | ] 52 | op = ["op-revm", "op-alloy-consensus"] 53 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | } 83 | 84 | /// `BlockExecutor` Errors 85 | #[derive(Debug, thiserror::Error)] 86 | pub enum BlockExecutionError { 87 | /// Validation error, transparently wrapping [`BlockValidationError`] 88 | #[error(transparent)] 89 | Validation(#[from] BlockValidationError), 90 | /// Internal, i.e. non consensus or validation related Block Executor Errors 91 | #[error(transparent)] 92 | Internal(#[from] InternalBlockExecutionError), 93 | } 94 | 95 | impl BlockExecutionError { 96 | /// Create a new [`BlockExecutionError::Internal`] variant, containing a 97 | /// [`InternalBlockExecutionError::Other`] error. 98 | pub fn other(error: E) -> Self 99 | where 100 | E: core::error::Error + Send + Sync + 'static, 101 | { 102 | Self::Internal(InternalBlockExecutionError::other(error)) 103 | } 104 | 105 | /// Create a new [`BlockExecutionError::Internal`] variant, containing a 106 | /// [`InternalBlockExecutionError::Other`] error with the given message. 107 | pub fn msg(msg: impl core::fmt::Display) -> Self { 108 | Self::Internal(InternalBlockExecutionError::msg(msg)) 109 | } 110 | 111 | /// Returns the inner `BlockValidationError` if the error is a validation error. 112 | pub const fn as_validation(&self) -> Option<&BlockValidationError> { 113 | match self { 114 | Self::Validation(err) => Some(err), 115 | _ => None, 116 | } 117 | } 118 | 119 | /// Handles an EVM error occurred when executing a transaction. 120 | /// 121 | /// If an error matches [`EvmError::InvalidTransaction`], it will be wrapped into 122 | /// [`BlockValidationError::InvalidTx`], otherwise into [`InternalBlockExecutionError::EVM`]. 123 | pub fn evm(error: E, hash: B256) -> Self { 124 | match error.try_into_invalid_tx_err() { 125 | Ok(err) => { 126 | Self::Validation(BlockValidationError::InvalidTx { hash, error: Box::new(err) }) 127 | } 128 | Err(err) => { 129 | Self::Internal(InternalBlockExecutionError::EVM { hash, error: Box::new(err) }) 130 | } 131 | } 132 | } 133 | } 134 | 135 | /// Internal (i.e., not validation or consensus related) `BlockExecutor` Errors 136 | #[derive(Debug, thiserror::Error)] 137 | pub enum InternalBlockExecutionError { 138 | /// EVM error occurred when executing transaction. This is different from 139 | /// [`BlockValidationError::InvalidTx`] because it will only contain EVM errors which are not 140 | /// transaction validation errors and are assumed to be fatal. 141 | #[error("internal EVM error occurred when executing transaction {hash}: {error}")] 142 | EVM { 143 | /// The hash of the transaction 144 | hash: B256, 145 | /// The EVM error. 146 | error: Box, 147 | }, 148 | /// Arbitrary Block Executor Errors 149 | #[error(transparent)] 150 | Other(Box), 151 | } 152 | 153 | impl InternalBlockExecutionError { 154 | /// Create a new [`InternalBlockExecutionError::Other`] variant. 155 | pub fn other(error: E) -> Self 156 | where 157 | E: core::error::Error + Send + Sync + 'static, 158 | { 159 | Self::Other(Box::new(error)) 160 | } 161 | 162 | /// Create a new [`InternalBlockExecutionError::Other`] from a given message. 163 | pub fn msg(msg: impl core::fmt::Display) -> Self { 164 | Self::Other(msg.to_string().into()) 165 | } 166 | 167 | /// Returns the arbitrary error if it is [`InternalBlockExecutionError::Other`] 168 | pub fn as_other(&self) -> Option<&(dyn core::error::Error + Send + Sync + 'static)> { 169 | match self { 170 | Self::Other(err) => Some(&**err), 171 | _ => None, 172 | } 173 | } 174 | 175 | /// Attempts to downcast the [`InternalBlockExecutionError::Other`] variant to a concrete type 176 | pub fn downcast(self) -> Result, Self> { 177 | match self { 178 | Self::Other(err) => err.downcast().map_err(Self::Other), 179 | err => Err(err), 180 | } 181 | } 182 | 183 | /// Returns a reference to the [`InternalBlockExecutionError::Other`] value if this type is a 184 | /// [`InternalBlockExecutionError::Other`] of that type. Returns None otherwise. 185 | pub fn downcast_other(&self) -> Option<&T> { 186 | let other = self.as_other()?; 187 | other.downcast_ref() 188 | } 189 | 190 | /// Returns true if the this type is a [`InternalBlockExecutionError::Other`] of that error 191 | /// type. Returns false otherwise. 192 | pub fn is_other(&self) -> bool { 193 | self.as_other().map(|err| err.is::()).unwrap_or(false) 194 | } 195 | } 196 | 197 | #[cfg(test)] 198 | mod tests { 199 | use super::*; 200 | 201 | #[derive(thiserror::Error, Debug)] 202 | #[error("err")] 203 | struct E; 204 | 205 | #[test] 206 | fn other_downcast() { 207 | let err = InternalBlockExecutionError::other(E); 208 | assert!(err.is_other::()); 209 | 210 | assert!(err.downcast_other::().is_some()); 211 | assert!(err.downcast::().is_ok()); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /crates/evm/src/block/mod.rs: -------------------------------------------------------------------------------- 1 | //! Block execution abstraction. 2 | 3 | use crate::{ 4 | Database, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded, IntoTxEnv, RecoveredTx, 5 | }; 6 | use alloc::{boxed::Box, vec::Vec}; 7 | use alloy_eips::eip7685::Requests; 8 | use revm::{ 9 | context::result::ExecutionResult, database::State, inspector::NoOpInspector, Inspector, 10 | }; 11 | 12 | mod error; 13 | pub use error::*; 14 | 15 | mod state_hook; 16 | pub use state_hook::*; 17 | 18 | pub mod system_calls; 19 | pub use system_calls::*; 20 | 21 | pub mod state_changes; 22 | 23 | pub mod calc; 24 | 25 | /// The result of executing a block. 26 | #[derive(Debug, Clone, Default, PartialEq, Eq)] 27 | pub struct BlockExecutionResult { 28 | /// All the receipts of the transactions in the block. 29 | pub receipts: Vec, 30 | /// All the EIP-7685 requests in the block. 31 | pub requests: Requests, 32 | /// The total gas used by the block. 33 | pub gas_used: u64, 34 | } 35 | 36 | /// Helper trait to encapsulate requirements for a type to be used as input for [`BlockExecutor`]. 37 | pub trait ExecutableTx: 38 | IntoTxEnv<::Tx> + RecoveredTx + Copy 39 | { 40 | } 41 | impl ExecutableTx for T where 42 | T: IntoTxEnv<::Tx> + RecoveredTx + Copy 43 | { 44 | } 45 | 46 | /// Marks whether transaction should be commited into block executor's state. 47 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 48 | #[must_use] 49 | pub enum CommitChanges { 50 | /// Transaction should be commited into block executor's state. 51 | Yes, 52 | /// Transaction should not be commited. 53 | No, 54 | } 55 | 56 | impl CommitChanges { 57 | /// Returns `true` if transaction should be commited into block executor's state. 58 | pub fn should_commit(self) -> bool { 59 | matches!(self, Self::Yes) 60 | } 61 | } 62 | 63 | /// A type that knows how to execute a single block. 64 | /// 65 | /// The current abstraction assumes that block execution consists of the following steps: 66 | /// 1. Apply pre-execution changes. Those might include system calls, irregular state transitions 67 | /// (DAO fork), etc. 68 | /// 2. Apply block transactions to the state. 69 | /// 3. Apply post-execution changes and finalize the state. This might include other system calls, 70 | /// block rewards, etc. 71 | /// 72 | /// The output of [`BlockExecutor::finish`] is a [`BlockExecutionResult`] which contains all 73 | /// relevant information about the block execution. 74 | pub trait BlockExecutor { 75 | /// Input transaction type. 76 | type Transaction; 77 | /// Receipt type this executor produces. 78 | type Receipt; 79 | /// EVM used by the executor. 80 | type Evm: Evm + FromTxWithEncoded>; 81 | 82 | /// Applies any necessary changes before executing the block's transactions. 83 | fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError>; 84 | 85 | /// Executes a single transaction and applies execution result to internal state. 86 | /// 87 | /// Returns the gas used by the transaction. 88 | fn execute_transaction( 89 | &mut self, 90 | tx: impl ExecutableTx, 91 | ) -> Result { 92 | self.execute_transaction_with_result_closure(tx, |_| ()) 93 | } 94 | 95 | /// Executes a single transaction and applies execution result to internal state. Invokes the 96 | /// given closure with an internal [`ExecutionResult`] produced by the EVM. 97 | fn execute_transaction_with_result_closure( 98 | &mut self, 99 | tx: impl ExecutableTx, 100 | f: impl FnOnce(&ExecutionResult<::HaltReason>), 101 | ) -> Result { 102 | self.execute_transaction_with_commit_condition(tx, |res| { 103 | f(res); 104 | CommitChanges::Yes 105 | }) 106 | .map(Option::unwrap_or_default) 107 | } 108 | 109 | /// Executes a single transaction and applies execution result to internal state. Invokes the 110 | /// given closure with an internal [`ExecutionResult`] produced by the EVM, and commits the 111 | /// transaction to the state on [`CommitChanges::Yes`]. 112 | /// 113 | /// Returns [`None`] if transaction was skipped via [`CommitChanges::No`]. 114 | fn execute_transaction_with_commit_condition( 115 | &mut self, 116 | tx: impl ExecutableTx, 117 | f: impl FnOnce(&ExecutionResult<::HaltReason>) -> CommitChanges, 118 | ) -> Result, BlockExecutionError>; 119 | 120 | /// Applies any necessary changes after executing the block's transactions, completes execution 121 | /// and returns the underlying EVM along with execution result. 122 | fn finish( 123 | self, 124 | ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError>; 125 | 126 | /// A helper to invoke [`BlockExecutor::finish`] returning only the [`BlockExecutionResult`]. 127 | fn apply_post_execution_changes( 128 | self, 129 | ) -> Result, BlockExecutionError> 130 | where 131 | Self: Sized, 132 | { 133 | self.finish().map(|(_, result)| result) 134 | } 135 | 136 | /// Sets a hook to be called after each state change during execution. 137 | fn set_state_hook(&mut self, hook: Option>); 138 | 139 | /// A builder-style helper to invoke [`BlockExecutor::set_state_hook`]. 140 | #[must_use] 141 | fn with_state_hook(mut self, hook: Option>) -> Self 142 | where 143 | Self: Sized, 144 | { 145 | self.set_state_hook(hook); 146 | self 147 | } 148 | 149 | /// Exposes mutable reference to EVM. 150 | fn evm_mut(&mut self) -> &mut Self::Evm; 151 | 152 | /// Exposes immutable reference to EVM. 153 | fn evm(&self) -> &Self::Evm; 154 | 155 | /// Executes all transactions in a block, applying pre and post execution changes. 156 | fn execute_block( 157 | mut self, 158 | transactions: impl IntoIterator>, 159 | ) -> Result, BlockExecutionError> 160 | where 161 | Self: Sized, 162 | { 163 | self.apply_pre_execution_changes()?; 164 | 165 | for tx in transactions { 166 | self.execute_transaction(tx)?; 167 | } 168 | 169 | self.apply_post_execution_changes() 170 | } 171 | } 172 | 173 | /// A helper trait encapsulating the constraints on [`BlockExecutor`] produced by the 174 | /// [`BlockExecutorFactory`] to avoid duplicating them in every implementation. 175 | pub trait BlockExecutorFor<'a, F: BlockExecutorFactory + ?Sized, DB, I = NoOpInspector> 176 | where 177 | Self: BlockExecutor< 178 | Evm = ::Evm<&'a mut State, I>, 179 | Transaction = F::Transaction, 180 | Receipt = F::Receipt, 181 | >, 182 | DB: Database + 'a, 183 | I: Inspector<::Context<&'a mut State>> + 'a, 184 | { 185 | } 186 | 187 | impl<'a, F, DB, I, T> BlockExecutorFor<'a, F, DB, I> for T 188 | where 189 | F: BlockExecutorFactory, 190 | DB: Database + 'a, 191 | I: Inspector<::Context<&'a mut State>> + 'a, 192 | T: BlockExecutor< 193 | Evm = ::Evm<&'a mut State, I>, 194 | Transaction = F::Transaction, 195 | Receipt = F::Receipt, 196 | >, 197 | { 198 | } 199 | 200 | /// A factory that can create [`BlockExecutor`]s. 201 | /// 202 | /// This trait extends [`crate::EvmFactory`] and provides a way to construct a [`BlockExecutor`]. 203 | /// Executor is expected to derive most of the context for block execution from the EVM (which 204 | /// includes [`revm::context::BlockEnv`]), and any additional context should be contained in 205 | /// configured [`ExecutionCtx`]. 206 | /// 207 | /// Every block executor factory is expected to contain and expose an [`EvmFactory`] instance. 208 | /// 209 | /// For more context on the executor design, see the documentation for [`BlockExecutor`]. 210 | /// 211 | /// [`ExecutionCtx`]: BlockExecutorFactory::ExecutionCtx 212 | #[auto_impl::auto_impl(Arc)] 213 | pub trait BlockExecutorFactory: 'static { 214 | /// The EVM factory used by the executor. 215 | type EvmFactory: EvmFactory; 216 | 217 | /// Context required for block execution. 218 | /// 219 | /// This is similar to [`crate::EvmEnv`], but only contains context unrelated to EVM and 220 | /// required for execution of an entire block. 221 | type ExecutionCtx<'a>: Clone; 222 | 223 | /// Transaction type used by the executor, see [`BlockExecutor::Transaction`]. 224 | type Transaction; 225 | 226 | /// Receipt type produced by the executor, see [`BlockExecutor::Receipt`]. 227 | type Receipt; 228 | 229 | /// Reference to EVM factory used by the executor. 230 | fn evm_factory(&self) -> &Self::EvmFactory; 231 | 232 | /// Creates an executor with given EVM and execution context. 233 | fn create_executor<'a, DB, I>( 234 | &'a self, 235 | evm: ::Evm<&'a mut State, I>, 236 | ctx: Self::ExecutionCtx<'a>, 237 | ) -> impl BlockExecutorFor<'a, Self, DB, I> 238 | where 239 | DB: Database + 'a, 240 | I: Inspector<::Context<&'a mut State>> + 'a; 241 | } 242 | -------------------------------------------------------------------------------- /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::BlockEnv, 10 | database::State, 11 | state::{Account, AccountStatus, EvmState}, 12 | Database, 13 | }; 14 | 15 | /// Collect all balance changes at the end of the block. 16 | /// 17 | /// Balance changes might include the block reward, uncle rewards, withdrawals, or irregular 18 | /// state changes (DAO fork). 19 | #[inline] 20 | pub fn post_block_balance_increments( 21 | spec: impl EthereumHardforks, 22 | block_env: &BlockEnv, 23 | ommers: &[H], 24 | withdrawals: Option<&Withdrawals>, 25 | ) -> HashMap 26 | where 27 | H: BlockHeader, 28 | { 29 | let mut balance_increments = HashMap::default(); 30 | 31 | // Add block rewards if they are enabled. 32 | if let Some(base_block_reward) = calc::base_block_reward(&spec, block_env.number) { 33 | // Ommer rewards 34 | for ommer in ommers { 35 | *balance_increments.entry(ommer.beneficiary()).or_default() += 36 | calc::ommer_reward(base_block_reward, block_env.number, ommer.number()); 37 | } 38 | 39 | // Full block reward 40 | *balance_increments.entry(block_env.beneficiary).or_default() += 41 | calc::block_reward(base_block_reward, ommers.len()); 42 | } 43 | 44 | // process withdrawals 45 | insert_post_block_withdrawals_balance_increments( 46 | spec, 47 | block_env.timestamp, 48 | withdrawals.map(|w| w.as_slice()), 49 | &mut balance_increments, 50 | ); 51 | 52 | balance_increments 53 | } 54 | 55 | /// Returns a map of addresses to their balance increments if the Shanghai hardfork is active at the 56 | /// given timestamp. 57 | /// 58 | /// Zero-valued withdrawals are filtered out. 59 | #[inline] 60 | pub fn post_block_withdrawals_balance_increments( 61 | spec: impl EthereumHardforks, 62 | block_timestamp: u64, 63 | withdrawals: &[Withdrawal], 64 | ) -> HashMap { 65 | let mut balance_increments = 66 | HashMap::with_capacity_and_hasher(withdrawals.len(), Default::default()); 67 | insert_post_block_withdrawals_balance_increments( 68 | spec, 69 | block_timestamp, 70 | Some(withdrawals), 71 | &mut balance_increments, 72 | ); 73 | balance_increments 74 | } 75 | 76 | /// Applies all withdrawal balance increments if shanghai is active at the given timestamp to the 77 | /// given `balance_increments` map. 78 | /// 79 | /// Zero-valued withdrawals are filtered out. 80 | #[inline] 81 | pub fn insert_post_block_withdrawals_balance_increments( 82 | spec: impl EthereumHardforks, 83 | block_timestamp: u64, 84 | withdrawals: Option<&[Withdrawal]>, 85 | balance_increments: &mut HashMap, 86 | ) { 87 | // Process withdrawals 88 | if spec.is_shanghai_active_at_timestamp(block_timestamp) { 89 | if let Some(withdrawals) = withdrawals { 90 | for withdrawal in withdrawals { 91 | if withdrawal.amount > 0 { 92 | *balance_increments.entry(withdrawal.address).or_default() += 93 | withdrawal.amount_wei().to::(); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | /// Creates an `EvmState` from a map of balance increments and the current state 101 | /// to load accounts from. No balance increment is done in the function. 102 | /// Zero balance increments are ignored and won't create state entries. 103 | pub fn balance_increment_state( 104 | balance_increments: &HashMap, 105 | state: &mut State, 106 | ) -> Result 107 | where 108 | DB: Database, 109 | { 110 | let mut load_account = |address: &Address| -> Result<(Address, Account), BlockExecutionError> { 111 | let cache_account = state.load_cache_account(*address).map_err(|_| { 112 | BlockExecutionError::msg("could not load account for balance increment") 113 | })?; 114 | 115 | let account = cache_account.account.as_ref().ok_or_else(|| { 116 | BlockExecutionError::msg("could not load account for balance increment") 117 | })?; 118 | 119 | Ok(( 120 | *address, 121 | Account { 122 | info: account.info.clone(), 123 | storage: Default::default(), 124 | status: AccountStatus::Touched, 125 | }, 126 | )) 127 | }; 128 | 129 | balance_increments 130 | .iter() 131 | .filter(|(_, &balance)| balance != 0) 132 | .map(|(addr, _)| load_account(addr)) 133 | .collect::>() 134 | } 135 | -------------------------------------------------------------------------------- /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/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_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 transact 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) { 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 == 0 { 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 | -------------------------------------------------------------------------------- /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_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 transact 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) { 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 == 0 { 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/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 transact 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 message data. 23 | // 24 | // This 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 | -------------------------------------------------------------------------------- /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 transact 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 message data. 24 | // 25 | // This 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 | // clienst software MUST [..] call the contract as `SYSTEM_ADDRESS` and empty input data to 31 | // trigger the system subroutine execute. 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/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 request 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/either.rs: -------------------------------------------------------------------------------- 1 | use crate::{Evm, EvmEnv}; 2 | use alloy_primitives::{Address, Bytes}; 3 | use revm::context::{either, BlockEnv}; 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 | Precompiles = L::Precompiles, 15 | Inspector = L::Inspector, 16 | >, 17 | { 18 | type DB = L::DB; 19 | type Tx = L::Tx; 20 | type Error = L::Error; 21 | type HaltReason = L::HaltReason; 22 | type Spec = L::Spec; 23 | type Precompiles = L::Precompiles; 24 | type Inspector = L::Inspector; 25 | 26 | fn block(&self) -> &BlockEnv { 27 | either::for_both!(self, evm => evm.block()) 28 | } 29 | 30 | fn chain_id(&self) -> u64 { 31 | either::for_both!(self, evm => evm.chain_id()) 32 | } 33 | 34 | fn transact_raw( 35 | &mut self, 36 | tx: Self::Tx, 37 | ) -> Result, Self::Error> { 38 | either::for_both!(self, evm => evm.transact_raw(tx)) 39 | } 40 | 41 | fn transact( 42 | &mut self, 43 | tx: impl crate::IntoTxEnv, 44 | ) -> Result, Self::Error> { 45 | either::for_both!(self, evm => evm.transact(tx)) 46 | } 47 | 48 | fn transact_system_call( 49 | &mut self, 50 | caller: Address, 51 | contract: Address, 52 | data: Bytes, 53 | ) -> Result, Self::Error> { 54 | either::for_both!(self, evm => evm.transact_system_call(caller, contract, data)) 55 | } 56 | 57 | fn db_mut(&mut self) -> &mut Self::DB { 58 | either::for_both!(self, evm => evm.db_mut()) 59 | } 60 | 61 | fn transact_commit( 62 | &mut self, 63 | tx: impl crate::IntoTxEnv, 64 | ) -> Result, Self::Error> 65 | where 66 | Self::DB: revm::DatabaseCommit, 67 | { 68 | either::for_both!(self, evm => evm.transact_commit(tx)) 69 | } 70 | 71 | fn finish(self) -> (Self::DB, EvmEnv) 72 | where 73 | Self: Sized, 74 | { 75 | either::for_both!(self, evm => evm.finish()) 76 | } 77 | 78 | fn into_db(self) -> Self::DB 79 | where 80 | Self: Sized, 81 | { 82 | either::for_both!(self, evm => evm.into_db()) 83 | } 84 | 85 | fn into_env(self) -> EvmEnv 86 | where 87 | Self: Sized, 88 | { 89 | either::for_both!(self, evm => evm.into_env()) 90 | } 91 | 92 | fn set_inspector_enabled(&mut self, enabled: bool) { 93 | either::for_both!(self, evm => evm.set_inspector_enabled(enabled)) 94 | } 95 | 96 | fn enable_inspector(&mut self) { 97 | either::for_both!(self, evm => evm.enable_inspector()) 98 | } 99 | 100 | fn disable_inspector(&mut self) { 101 | either::for_both!(self, evm => evm.disable_inspector()) 102 | } 103 | 104 | fn precompiles(&self) -> &Self::Precompiles { 105 | either::for_both!(self, evm => evm.precompiles()) 106 | } 107 | 108 | fn precompiles_mut(&mut self) -> &mut Self::Precompiles { 109 | either::for_both!(self, evm => evm.precompiles_mut()) 110 | } 111 | 112 | fn inspector(&self) -> &Self::Inspector { 113 | either::for_both!(self, evm => evm.inspector()) 114 | } 115 | 116 | fn inspector_mut(&mut self) -> &mut Self::Inspector { 117 | either::for_both!(self, evm => evm.inspector_mut()) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /crates/evm/src/env.rs: -------------------------------------------------------------------------------- 1 | //! Configuration types for EVM environment. 2 | 3 | use revm::{ 4 | context::{BlockEnv, CfgEnv}, 5 | primitives::hardfork::SpecId, 6 | }; 7 | 8 | /// Container type that holds both the configuration and block environment for EVM execution. 9 | #[derive(Debug, Clone, Default)] 10 | pub struct EvmEnv { 11 | /// The configuration environment with handler settings 12 | pub cfg_env: CfgEnv, 13 | /// The block environment containing block-specific data 14 | pub block_env: BlockEnv, 15 | } 16 | 17 | impl EvmEnv { 18 | /// Create a new `EvmEnv` from its components. 19 | /// 20 | /// # Arguments 21 | /// 22 | /// * `cfg_env_with_handler_cfg` - The configuration environment with handler settings 23 | /// * `block` - The block environment containing block-specific data 24 | pub const fn new(cfg_env: CfgEnv, block_env: BlockEnv) -> Self { 25 | Self { cfg_env, block_env } 26 | } 27 | 28 | /// Returns a reference to the block environment. 29 | pub const fn block_env(&self) -> &BlockEnv { 30 | &self.block_env 31 | } 32 | 33 | /// Returns a reference to the configuration environment. 34 | pub const fn cfg_env(&self) -> &CfgEnv { 35 | &self.cfg_env 36 | } 37 | 38 | /// Returns the chain ID of the environment. 39 | pub const fn chainid(&self) -> u64 { 40 | self.cfg_env.chain_id 41 | } 42 | 43 | /// Returns the spec id of the chain 44 | pub const fn spec_id(&self) -> &Spec { 45 | &self.cfg_env.spec 46 | } 47 | } 48 | 49 | impl From<(CfgEnv, BlockEnv)> for EvmEnv { 50 | fn from((cfg_env, block_env): (CfgEnv, BlockEnv)) -> Self { 51 | Self { cfg_env, block_env } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /crates/evm/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over EVM errors. 2 | 3 | use core::error::Error; 4 | use revm::context_interface::result::{EVMError, InvalidTransaction}; 5 | 6 | /// Abstraction over transaction validation error. 7 | pub trait InvalidTxError: Error + Send + Sync + 'static { 8 | /// Returns whether the error cause by transaction having a nonce lower than expected. 9 | fn is_nonce_too_low(&self) -> bool; 10 | } 11 | 12 | impl InvalidTxError for InvalidTransaction { 13 | fn is_nonce_too_low(&self) -> bool { 14 | matches!(self, Self::NonceTooLow { .. }) 15 | } 16 | } 17 | 18 | /// Abstraction over errors that can occur during EVM execution. 19 | /// 20 | /// It's assumed that errors can occur either because of an invalid transaction, meaning that other 21 | /// transaction might still result in successful execution, or because of a general EVM 22 | /// misconfiguration. 23 | /// 24 | /// If caller occurs a error different from [`EvmError::InvalidTransaction`], it should most likely 25 | /// be treated as fatal error flagging some EVM misconfiguration. 26 | pub trait EvmError: Sized + Error + Send + Sync + 'static { 27 | /// Errors which might occur as a result of an invalid transaction. i.e unrelated to general EVM 28 | /// configuration. 29 | type InvalidTransaction: InvalidTxError; 30 | 31 | /// Returns the [`EvmError::InvalidTransaction`] if the error is an invalid transaction error. 32 | fn as_invalid_tx_err(&self) -> Option<&Self::InvalidTransaction>; 33 | 34 | /// Attempts to convert the error into [`EvmError::InvalidTransaction`]. 35 | fn try_into_invalid_tx_err(self) -> Result; 36 | 37 | /// Returns `true` if the error is an invalid transaction error. 38 | fn is_invalid_tx_err(&self) -> bool { 39 | self.as_invalid_tx_err().is_some() 40 | } 41 | } 42 | 43 | impl EvmError for EVMError 44 | where 45 | DBError: Error + Send + Sync + 'static, 46 | TxError: InvalidTxError, 47 | { 48 | type InvalidTransaction = TxError; 49 | 50 | fn as_invalid_tx_err(&self) -> Option<&Self::InvalidTransaction> { 51 | match self { 52 | Self::Transaction(err) => Some(err), 53 | _ => None, 54 | } 55 | } 56 | 57 | fn try_into_invalid_tx_err(self) -> Result { 58 | match self { 59 | Self::Transaction(err) => Ok(err), 60 | err => Err(err), 61 | } 62 | } 63 | } 64 | 65 | #[cfg(feature = "op")] 66 | impl InvalidTxError for op_revm::OpTransactionError { 67 | fn is_nonce_too_low(&self) -> bool { 68 | matches!(self, Self::Base(tx) if tx.is_nonce_too_low()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /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, CommitChanges, 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::{Log, B256}; 23 | use revm::{ 24 | context::result::ExecutionResult, context_interface::result::ResultAndState, database::State, 25 | DatabaseCommit, 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 | } 40 | 41 | /// Block executor for Ethereum. 42 | #[derive(Debug)] 43 | pub struct EthBlockExecutor<'a, Evm, Spec, R: ReceiptBuilder> { 44 | /// Reference to the specification object. 45 | spec: Spec, 46 | 47 | /// Context for block execution. 48 | pub ctx: EthBlockExecutionCtx<'a>, 49 | /// Inner EVM. 50 | evm: Evm, 51 | /// Utility to call system smart contracts. 52 | system_caller: SystemCaller, 53 | /// Receipt builder. 54 | receipt_builder: R, 55 | 56 | /// Receipts of executed transactions. 57 | receipts: Vec, 58 | /// Total gas used by transactions in this block. 59 | gas_used: u64, 60 | } 61 | 62 | impl<'a, Evm, Spec, R> EthBlockExecutor<'a, Evm, Spec, R> 63 | where 64 | Spec: Clone, 65 | R: ReceiptBuilder, 66 | { 67 | /// Creates a new [`EthBlockExecutor`] 68 | pub fn new(evm: Evm, ctx: EthBlockExecutionCtx<'a>, spec: Spec, receipt_builder: R) -> Self { 69 | Self { 70 | evm, 71 | ctx, 72 | receipts: Vec::new(), 73 | gas_used: 0, 74 | system_caller: SystemCaller::new(spec.clone()), 75 | spec, 76 | receipt_builder, 77 | } 78 | } 79 | } 80 | 81 | impl<'db, DB, E, Spec, R> BlockExecutor for EthBlockExecutor<'_, E, Spec, R> 82 | where 83 | DB: Database + 'db, 84 | E: Evm< 85 | DB = &'db mut State, 86 | Tx: FromRecoveredTx + FromTxWithEncoded, 87 | >, 88 | Spec: EthExecutorSpec, 89 | R: ReceiptBuilder>, 90 | { 91 | type Transaction = R::Transaction; 92 | type Receipt = R::Receipt; 93 | type Evm = E; 94 | 95 | fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { 96 | // Set state clear flag if the block is after the Spurious Dragon hardfork. 97 | let state_clear_flag = 98 | self.spec.is_spurious_dragon_active_at_block(self.evm.block().number); 99 | self.evm.db_mut().set_state_clear_flag(state_clear_flag); 100 | 101 | self.system_caller.apply_blockhashes_contract_call(self.ctx.parent_hash, &mut self.evm)?; 102 | self.system_caller 103 | .apply_beacon_root_contract_call(self.ctx.parent_beacon_block_root, &mut self.evm)?; 104 | 105 | Ok(()) 106 | } 107 | 108 | fn execute_transaction_with_commit_condition( 109 | &mut self, 110 | tx: impl ExecutableTx, 111 | f: impl FnOnce(&ExecutionResult<::HaltReason>) -> CommitChanges, 112 | ) -> Result, BlockExecutionError> { 113 | // The sum of the transaction's gas limit, Tg, and the gas utilized in this block prior, 114 | // must be no greater than the block's gasLimit. 115 | let block_available_gas = self.evm.block().gas_limit - self.gas_used; 116 | 117 | if tx.tx().gas_limit() > block_available_gas { 118 | return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { 119 | transaction_gas_limit: tx.tx().gas_limit(), 120 | block_available_gas, 121 | } 122 | .into()); 123 | } 124 | 125 | // Execute transaction. 126 | let ResultAndState { result, state } = self 127 | .evm 128 | .transact(tx) 129 | .map_err(|err| BlockExecutionError::evm(err, tx.tx().trie_hash()))?; 130 | 131 | if !f(&result).should_commit() { 132 | return Ok(None); 133 | } 134 | 135 | self.system_caller.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); 136 | 137 | let gas_used = result.gas_used(); 138 | 139 | // append gas used 140 | self.gas_used += gas_used; 141 | 142 | // Push transaction changeset and calculate header bloom filter for receipt. 143 | self.receipts.push(self.receipt_builder.build_receipt(ReceiptBuilderCtx { 144 | tx: tx.tx(), 145 | evm: &self.evm, 146 | result, 147 | state: &state, 148 | cumulative_gas_used: self.gas_used, 149 | })); 150 | 151 | // Commit the state changes. 152 | self.evm.db_mut().commit(state); 153 | 154 | Ok(Some(gas_used)) 155 | } 156 | 157 | fn finish( 158 | mut self, 159 | ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { 160 | let requests = if self.spec.is_prague_active_at_timestamp(self.evm.block().timestamp) { 161 | // Collect all EIP-6110 deposits 162 | let deposit_requests = 163 | eip6110::parse_deposits_from_receipts(&self.spec, &self.receipts)?; 164 | 165 | let mut requests = Requests::default(); 166 | 167 | if !deposit_requests.is_empty() { 168 | requests.push_request_with_type(eip6110::DEPOSIT_REQUEST_TYPE, deposit_requests); 169 | } 170 | 171 | requests.extend(self.system_caller.apply_post_execution_changes(&mut self.evm)?); 172 | requests 173 | } else { 174 | Requests::default() 175 | }; 176 | 177 | let mut balance_increments = post_block_balance_increments( 178 | &self.spec, 179 | self.evm.block(), 180 | self.ctx.ommers, 181 | self.ctx.withdrawals.as_deref(), 182 | ); 183 | 184 | // Irregular state change at Ethereum DAO hardfork 185 | if self 186 | .spec 187 | .ethereum_fork_activation(EthereumHardfork::Dao) 188 | .transitions_at_block(self.evm.block().number) 189 | { 190 | // drain balances from hardcoded addresses. 191 | let drained_balance: u128 = self 192 | .evm 193 | .db_mut() 194 | .drain_balances(dao_fork::DAO_HARDFORK_ACCOUNTS) 195 | .map_err(|_| BlockValidationError::IncrementBalanceFailed)? 196 | .into_iter() 197 | .sum(); 198 | 199 | // return balance to DAO beneficiary. 200 | *balance_increments.entry(dao_fork::DAO_HARDFORK_BENEFICIARY).or_default() += 201 | drained_balance; 202 | } 203 | // increment balances 204 | self.evm 205 | .db_mut() 206 | .increment_balances(balance_increments.clone()) 207 | .map_err(|_| BlockValidationError::IncrementBalanceFailed)?; 208 | 209 | // call state hook with changes due to balance increments. 210 | self.system_caller.try_on_state_with(|| { 211 | balance_increment_state(&balance_increments, self.evm.db_mut()).map(|state| { 212 | ( 213 | StateChangeSource::PostBlock(StateChangePostBlockSource::BalanceIncrements), 214 | Cow::Owned(state), 215 | ) 216 | }) 217 | })?; 218 | 219 | Ok(( 220 | self.evm, 221 | BlockExecutionResult { receipts: self.receipts, requests, gas_used: self.gas_used }, 222 | )) 223 | } 224 | 225 | fn set_state_hook(&mut self, hook: Option>) { 226 | self.system_caller.with_state_hook(hook); 227 | } 228 | 229 | fn evm_mut(&mut self) -> &mut Self::Evm { 230 | &mut self.evm 231 | } 232 | 233 | fn evm(&self) -> &Self::Evm { 234 | &self.evm 235 | } 236 | } 237 | 238 | /// Ethereum block executor factory. 239 | #[derive(Debug, Clone, Default, Copy)] 240 | pub struct EthBlockExecutorFactory< 241 | R = AlloyReceiptBuilder, 242 | Spec = EthSpec, 243 | EvmFactory = EthEvmFactory, 244 | > { 245 | /// Receipt builder. 246 | receipt_builder: R, 247 | /// Chain specification. 248 | spec: Spec, 249 | /// EVM factory. 250 | evm_factory: EvmFactory, 251 | } 252 | 253 | impl EthBlockExecutorFactory { 254 | /// Creates a new [`EthBlockExecutorFactory`] with the given spec, [`EvmFactory`], and 255 | /// [`ReceiptBuilder`]. 256 | pub const fn new(receipt_builder: R, spec: Spec, evm_factory: EvmFactory) -> Self { 257 | Self { receipt_builder, spec, evm_factory } 258 | } 259 | 260 | /// Exposes the receipt builder. 261 | pub const fn receipt_builder(&self) -> &R { 262 | &self.receipt_builder 263 | } 264 | 265 | /// Exposes the chain specification. 266 | pub const fn spec(&self) -> &Spec { 267 | &self.spec 268 | } 269 | 270 | /// Exposes the EVM factory. 271 | pub const fn evm_factory(&self) -> &EvmFactory { 272 | &self.evm_factory 273 | } 274 | } 275 | 276 | impl BlockExecutorFactory for EthBlockExecutorFactory 277 | where 278 | R: ReceiptBuilder>, 279 | Spec: EthExecutorSpec, 280 | EvmF: EvmFactory + FromTxWithEncoded>, 281 | Self: 'static, 282 | { 283 | type EvmFactory = EvmF; 284 | type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>; 285 | type Transaction = R::Transaction; 286 | type Receipt = R::Receipt; 287 | 288 | fn evm_factory(&self) -> &Self::EvmFactory { 289 | &self.evm_factory 290 | } 291 | 292 | fn create_executor<'a, DB, I>( 293 | &'a self, 294 | evm: EvmF::Evm<&'a mut State, I>, 295 | ctx: Self::ExecutionCtx<'a>, 296 | ) -> impl BlockExecutorFor<'a, Self, DB, I> 297 | where 298 | DB: Database + 'a, 299 | I: Inspector>> + 'a, 300 | { 301 | EthBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /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/eip6110.rs: -------------------------------------------------------------------------------- 1 | //! EIP-6110 deposit requests parsing 2 | 3 | use super::spec::EthExecutorSpec; 4 | use crate::block::BlockValidationError; 5 | use alloc::{string::ToString, vec::Vec}; 6 | use alloy_consensus::TxReceipt; 7 | use alloy_primitives::{Address, Bytes, Log}; 8 | use alloy_sol_types::{sol, SolEvent}; 9 | 10 | pub use alloy_eips::eip6110::*; 11 | 12 | /// The size of a deposit request in bytes. While the event fields emit 13 | /// bytestrings, those bytestrings are fixed size. The fields are: 48-byte 14 | /// pubkey, 32-byte withdrawal credentials, 8-byte amount, 96-byte signature, 15 | /// and 8-byte index. 16 | const DEPOSIT_BYTES_SIZE: usize = 48 + 32 + 8 + 96 + 8; 17 | 18 | sol! { 19 | #[allow(missing_docs)] 20 | event DepositEvent( 21 | bytes pubkey, 22 | bytes withdrawal_credentials, 23 | bytes amount, 24 | bytes signature, 25 | bytes index 26 | ); 27 | } 28 | 29 | /// Accumulate a deposit request from a log. containing a [`DepositEvent`]. 30 | pub fn accumulate_deposit_from_log(log: &Log, out: &mut Vec) { 31 | out.reserve(DEPOSIT_BYTES_SIZE); 32 | out.extend_from_slice(log.pubkey.as_ref()); 33 | out.extend_from_slice(log.withdrawal_credentials.as_ref()); 34 | out.extend_from_slice(log.amount.as_ref()); 35 | out.extend_from_slice(log.signature.as_ref()); 36 | out.extend_from_slice(log.index.as_ref()); 37 | } 38 | 39 | /// Accumulate deposits from an iterator of logs. 40 | pub fn accumulate_deposits_from_logs<'a>( 41 | address: Address, 42 | logs: impl IntoIterator, 43 | out: &mut Vec, 44 | ) -> Result<(), BlockValidationError> { 45 | logs.into_iter() 46 | // filter logs by address 47 | .filter(|log| log.address == address) 48 | // explicitly filter logs by the DepositEvent's signature hash (first topic) 49 | .filter(|log| { 50 | // 0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5 51 | log.topics().first() == Some(&DepositEvent::SIGNATURE_HASH) 52 | }) 53 | .try_for_each(|log| { 54 | // We assume that the log is valid because it was emitted by the 55 | // deposit contract. 56 | let decoded_log = 57 | DepositEvent::decode_log(log).map_err(|err: alloy_sol_types::Error| { 58 | BlockValidationError::DepositRequestDecode(err.to_string()) 59 | })?; 60 | accumulate_deposit_from_log(&decoded_log, out); 61 | Ok(()) 62 | }) 63 | } 64 | 65 | /// Accumulate deposits from a receipt. Iterates over the logs in the receipt 66 | /// and accumulates the deposit request bytestrings. 67 | pub fn accumulate_deposits_from_receipt( 68 | address: Address, 69 | receipt: impl TxReceipt, 70 | out: &mut Vec, 71 | ) -> Result<(), BlockValidationError> { 72 | accumulate_deposits_from_logs(address, receipt.logs(), out) 73 | } 74 | 75 | /// Accumulate deposits from a list of receipts. Iterates over the logs in the 76 | /// receipts and accumulates the deposit request bytestrings. 77 | pub fn accumulate_deposits_from_receipts<'a, I, R>( 78 | address: Address, 79 | receipts: I, 80 | out: &mut Vec, 81 | ) -> Result<(), BlockValidationError> 82 | where 83 | I: IntoIterator, 84 | R: TxReceipt + 'a, 85 | { 86 | receipts 87 | .into_iter() 88 | .try_for_each(|receipt| accumulate_deposits_from_receipt(address, receipt, out)) 89 | } 90 | 91 | /// Find deposit logs in a list of receipts, and return the concatenated 92 | /// deposit request bytestring. 93 | /// 94 | /// The address of the deposit contract is taken from the chain spec, and 95 | /// defaults to [`MAINNET_DEPOSIT_CONTRACT_ADDRESS`] if not specified in 96 | /// the chain spec. 97 | pub fn parse_deposits_from_receipts<'a, I, R>( 98 | spec: impl EthExecutorSpec, 99 | receipts: I, 100 | ) -> Result 101 | where 102 | I: IntoIterator, 103 | R: TxReceipt + 'a, 104 | { 105 | let mut out = Vec::new(); 106 | accumulate_deposits_from_receipts( 107 | spec.deposit_contract_address().unwrap_or(MAINNET_DEPOSIT_CONTRACT_ADDRESS), 108 | receipts, 109 | &mut out, 110 | )?; 111 | Ok(out.into()) 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use super::*; 117 | use crate::eth::spec::EthSpec; 118 | use alloc::vec; 119 | use alloy_consensus::Receipt; 120 | use alloy_primitives::{b256, bytes}; 121 | 122 | #[test] 123 | fn check_deposit_sig() { 124 | let expected = b256!("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"); 125 | assert_eq!(expected, DepositEvent::SIGNATURE_HASH); 126 | } 127 | 128 | #[test] 129 | fn test_parse_deposit_from_log() { 130 | let receipts = vec![ 131 | // https://etherscan.io/tx/0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9 132 | Receipt { 133 | status: true.into(), 134 | cumulative_gas_used: 0, 135 | logs: serde_json::from_str( 136 | r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f3900000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddec0000000000000000000000000000000000000000000000000000000000000008e474160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9","transactionIndex":"0xc4","logIndex":"0x18f","removed":false}]"# 137 | ).unwrap(), 138 | }, 139 | // https://etherscan.io/tx/0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338 140 | Receipt { 141 | status: true.into(), 142 | cumulative_gas_used: 0, 143 | logs: serde_json::from_str( 144 | r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f6000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba0390000000000000000000000000000000000000000000000000000000000000008e374160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338","transactionIndex":"0x7c","logIndex":"0xe2","removed":false}]"#, 145 | ).unwrap(), 146 | }, 147 | ]; 148 | 149 | let request_data = parse_deposits_from_receipts(EthSpec::mainnet(), &receipts).unwrap(); 150 | assert_eq!( 151 | request_data, 152 | bytes!( 153 | "998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396201000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f39004059730700000098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddece474160000000000a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f60040597307000000ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba039e374160000000000" 154 | ) 155 | ); 156 | } 157 | 158 | #[test] 159 | fn test_parse_deposit_from_log_extra() { 160 | let receipts = vec![ 161 | Receipt { 162 | status: true.into(), 163 | cumulative_gas_used: 0, 164 | // Transfer + Deposit 165 | logs: serde_json::from_str( 166 | r#"[ 167 | { 168 | "address": "0x00000000219ab540356cbb839cbe05303d7705fa", 169 | "topics": [ 170 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 171 | "0x0000000000000000000000008b0c2c4c8eb078bc6c01f48523764c8942c0c6c4", 172 | "0x0000000000000000000000000000000000000000000000000000000000000000" 173 | ], 174 | "data": "0x0000000000000000000000000000000000000000000000000000000000000001", 175 | "blockNumber": "0x158c89", 176 | "transactionHash": "0xfcd133f57d5bab8cea211bab0361379456b9115a66bd242d62582aab0bb5fe71", 177 | "transactionIndex": "0x0", 178 | "blockHash": "0xe826da725061cdd2ab7638d4c4b5f25a07491f131d05b89628c4613cad07a246", 179 | "logIndex": "0x0", 180 | "removed": false 181 | }, 182 | {"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f3900000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddec0000000000000000000000000000000000000000000000000000000000000008e474160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9","transactionIndex":"0xc4","logIndex":"0x18f","removed":false}]"# 183 | ).unwrap(), 184 | }, 185 | // https://etherscan.io/tx/0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338 186 | Receipt { 187 | status: true.into(), 188 | cumulative_gas_used: 0, 189 | logs: serde_json::from_str( 190 | r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f6000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba0390000000000000000000000000000000000000000000000000000000000000008e374160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338","transactionIndex":"0x7c","logIndex":"0xe2","removed":false}]"#, 191 | ).unwrap(), 192 | }, 193 | ]; 194 | 195 | let request_data = parse_deposits_from_receipts(EthSpec::mainnet(), &receipts).unwrap(); 196 | assert_eq!( 197 | request_data, 198 | bytes!( 199 | "998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396201000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f39004059730700000098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddece474160000000000a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f60040597307000000ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba039e374160000000000" 200 | ) 201 | ); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /crates/evm/src/eth/mod.rs: -------------------------------------------------------------------------------- 1 | //! Ethereum EVM implementation. 2 | 3 | use crate::{env::EvmEnv, evm::EvmFactory, precompiles::PrecompilesMap, Database, Evm}; 4 | use alloc::vec::Vec; 5 | use alloy_primitives::{Address, Bytes, TxKind, U256}; 6 | use core::{ 7 | fmt::Debug, 8 | ops::{Deref, DerefMut}, 9 | }; 10 | use revm::{ 11 | context::{BlockEnv, CfgEnv, Evm as RevmEvm, TxEnv}, 12 | context_interface::result::{EVMError, HaltReason, ResultAndState}, 13 | handler::{instructions::EthInstructions, EthPrecompiles, PrecompileProvider}, 14 | inspector::NoOpInspector, 15 | interpreter::{interpreter::EthInterpreter, InterpreterResult}, 16 | precompile::{PrecompileSpecId, Precompiles}, 17 | primitives::hardfork::SpecId, 18 | Context, ExecuteEvm, InspectEvm, Inspector, MainBuilder, MainContext, 19 | }; 20 | 21 | mod block; 22 | pub use block::*; 23 | 24 | pub mod dao_fork; 25 | pub mod eip6110; 26 | pub mod receipt_builder; 27 | pub mod spec; 28 | 29 | /// The Ethereum EVM context type. 30 | pub type EthEvmContext = Context; 31 | 32 | /// Ethereum EVM implementation. 33 | /// 34 | /// This is a wrapper type around the `revm` ethereum evm with optional [`Inspector`] (tracing) 35 | /// support. [`Inspector`] support is configurable at runtime because it's part of the underlying 36 | /// [`RevmEvm`] type. 37 | #[expect(missing_debug_implementations)] 38 | pub struct EthEvm { 39 | inner: RevmEvm< 40 | EthEvmContext, 41 | I, 42 | EthInstructions>, 43 | PRECOMPILE, 44 | >, 45 | inspect: bool, 46 | } 47 | 48 | impl EthEvm { 49 | /// Creates a new Ethereum EVM instance. 50 | /// 51 | /// The `inspect` argument determines whether the configured [`Inspector`] of the given 52 | /// [`RevmEvm`] should be invoked on [`Evm::transact`]. 53 | pub const fn new( 54 | evm: RevmEvm< 55 | EthEvmContext, 56 | I, 57 | EthInstructions>, 58 | PRECOMPILE, 59 | >, 60 | inspect: bool, 61 | ) -> Self { 62 | Self { inner: evm, inspect } 63 | } 64 | 65 | /// Consumes self and return the inner EVM instance. 66 | pub fn into_inner( 67 | self, 68 | ) -> RevmEvm, I, EthInstructions>, PRECOMPILE> 69 | { 70 | self.inner 71 | } 72 | 73 | /// Provides a reference to the EVM context. 74 | pub const fn ctx(&self) -> &EthEvmContext { 75 | &self.inner.ctx 76 | } 77 | 78 | /// Provides a mutable reference to the EVM context. 79 | pub fn ctx_mut(&mut self) -> &mut EthEvmContext { 80 | &mut self.inner.ctx 81 | } 82 | } 83 | 84 | impl Deref for EthEvm { 85 | type Target = EthEvmContext; 86 | 87 | #[inline] 88 | fn deref(&self) -> &Self::Target { 89 | self.ctx() 90 | } 91 | } 92 | 93 | impl DerefMut for EthEvm { 94 | #[inline] 95 | fn deref_mut(&mut self) -> &mut Self::Target { 96 | self.ctx_mut() 97 | } 98 | } 99 | 100 | impl Evm for EthEvm 101 | where 102 | DB: Database, 103 | I: Inspector>, 104 | PRECOMPILE: PrecompileProvider, Output = InterpreterResult>, 105 | { 106 | type DB = DB; 107 | type Tx = TxEnv; 108 | type Error = EVMError; 109 | type HaltReason = HaltReason; 110 | type Spec = SpecId; 111 | type Precompiles = PRECOMPILE; 112 | type Inspector = I; 113 | 114 | fn block(&self) -> &BlockEnv { 115 | &self.block 116 | } 117 | 118 | fn chain_id(&self) -> u64 { 119 | self.cfg.chain_id 120 | } 121 | 122 | fn transact_raw(&mut self, tx: Self::Tx) -> Result { 123 | if self.inspect { 124 | self.inner.set_tx(tx); 125 | self.inner.inspect_replay() 126 | } else { 127 | self.inner.transact(tx) 128 | } 129 | } 130 | 131 | fn transact_system_call( 132 | &mut self, 133 | caller: Address, 134 | contract: Address, 135 | data: Bytes, 136 | ) -> Result { 137 | let tx = TxEnv { 138 | caller, 139 | kind: TxKind::Call(contract), 140 | // Explicitly set nonce to 0 so revm does not do any nonce checks 141 | nonce: 0, 142 | gas_limit: 30_000_000, 143 | value: U256::ZERO, 144 | data, 145 | // Setting the gas price to zero enforces that no value is transferred as part of the 146 | // call, and that the call will not count against the block's gas limit 147 | gas_price: 0, 148 | // The chain ID check is not relevant here and is disabled if set to None 149 | chain_id: None, 150 | // Setting the gas priority fee to None ensures the effective gas price is derived from 151 | // the `gas_price` field, which we need to be zero 152 | gas_priority_fee: None, 153 | access_list: Default::default(), 154 | // blob fields can be None for this tx 155 | blob_hashes: Vec::new(), 156 | max_fee_per_blob_gas: 0, 157 | tx_type: 0, 158 | authorization_list: Default::default(), 159 | }; 160 | 161 | let mut gas_limit = tx.gas_limit; 162 | let mut basefee = 0; 163 | let mut disable_nonce_check = true; 164 | 165 | // ensure the block gas limit is >= the tx 166 | core::mem::swap(&mut self.block.gas_limit, &mut gas_limit); 167 | // disable the base fee check for this call by setting the base fee to zero 168 | core::mem::swap(&mut self.block.basefee, &mut basefee); 169 | // disable the nonce check 170 | core::mem::swap(&mut self.cfg.disable_nonce_check, &mut disable_nonce_check); 171 | 172 | let mut res = self.transact(tx); 173 | 174 | // swap back to the previous gas limit 175 | core::mem::swap(&mut self.block.gas_limit, &mut gas_limit); 176 | // swap back to the previous base fee 177 | core::mem::swap(&mut self.block.basefee, &mut basefee); 178 | // swap back to the previous nonce check flag 179 | core::mem::swap(&mut self.cfg.disable_nonce_check, &mut disable_nonce_check); 180 | 181 | // NOTE: We assume that only the contract storage is modified. Revm currently marks the 182 | // caller and block beneficiary accounts as "touched" when we do the above transact calls, 183 | // and includes them in the result. 184 | // 185 | // We're doing this state cleanup to make sure that changeset only includes the changed 186 | // contract storage. 187 | if let Ok(res) = &mut res { 188 | res.state.retain(|addr, _| *addr == contract); 189 | } 190 | 191 | res 192 | } 193 | 194 | fn db_mut(&mut self) -> &mut Self::DB { 195 | &mut self.journaled_state.database 196 | } 197 | 198 | fn finish(self) -> (Self::DB, EvmEnv) { 199 | let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.ctx; 200 | 201 | (journaled_state.database, EvmEnv { block_env, cfg_env }) 202 | } 203 | 204 | fn set_inspector_enabled(&mut self, enabled: bool) { 205 | self.inspect = enabled; 206 | } 207 | 208 | fn precompiles(&self) -> &Self::Precompiles { 209 | &self.inner.precompiles 210 | } 211 | 212 | fn precompiles_mut(&mut self) -> &mut Self::Precompiles { 213 | &mut self.inner.precompiles 214 | } 215 | 216 | fn inspector(&self) -> &Self::Inspector { 217 | &self.inner.inspector 218 | } 219 | 220 | fn inspector_mut(&mut self) -> &mut Self::Inspector { 221 | &mut self.inner.inspector 222 | } 223 | } 224 | 225 | /// Factory producing [`EthEvm`]. 226 | #[derive(Debug, Default, Clone, Copy)] 227 | #[non_exhaustive] 228 | pub struct EthEvmFactory; 229 | 230 | impl EvmFactory for EthEvmFactory { 231 | type Evm>> = EthEvm; 232 | type Context = Context; 233 | type Tx = TxEnv; 234 | type Error = EVMError; 235 | type HaltReason = HaltReason; 236 | type Spec = SpecId; 237 | type Precompiles = PrecompilesMap; 238 | 239 | fn create_evm(&self, db: DB, input: EvmEnv) -> Self::Evm { 240 | let spec_id = input.cfg_env.spec; 241 | EthEvm { 242 | inner: Context::mainnet() 243 | .with_block(input.block_env) 244 | .with_cfg(input.cfg_env) 245 | .with_db(db) 246 | .build_mainnet_with_inspector(NoOpInspector {}) 247 | .with_precompiles(PrecompilesMap::from_static(Precompiles::new( 248 | PrecompileSpecId::from_spec_id(spec_id), 249 | ))), 250 | inspect: false, 251 | } 252 | } 253 | 254 | fn create_evm_with_inspector>>( 255 | &self, 256 | db: DB, 257 | input: EvmEnv, 258 | inspector: I, 259 | ) -> Self::Evm { 260 | let spec_id = input.cfg_env.spec; 261 | EthEvm { 262 | inner: Context::mainnet() 263 | .with_block(input.block_env) 264 | .with_cfg(input.cfg_env) 265 | .with_db(db) 266 | .build_mainnet_with_inspector(inspector) 267 | .with_precompiles(PrecompilesMap::from_static(Precompiles::new( 268 | PrecompileSpecId::from_spec_id(spec_id), 269 | ))), 270 | inspect: true, 271 | } 272 | } 273 | } 274 | 275 | #[cfg(test)] 276 | mod tests { 277 | use super::*; 278 | use alloy_primitives::address; 279 | use revm::{database_interface::EmptyDB, primitives::hardfork::SpecId}; 280 | 281 | #[test] 282 | fn test_precompiles_with_correct_spec() { 283 | // create tests where precompile should be available for later specs but not earlier ones 284 | let specs_to_test = [ 285 | // MODEXP (0x05) was added in Byzantium, should not exist in Frontier 286 | ( 287 | address!("0x0000000000000000000000000000000000000005"), 288 | SpecId::FRONTIER, // Early spec - should NOT have this precompile 289 | SpecId::BYZANTIUM, // Later spec - should have this precompile 290 | "MODEXP", 291 | ), 292 | // BLAKE2F (0x09) was added in Istanbul, should not exist in Byzantium 293 | ( 294 | address!("0x0000000000000000000000000000000000000009"), 295 | SpecId::BYZANTIUM, // Early spec - should NOT have this precompile 296 | SpecId::ISTANBUL, // Later spec - should have this precompile 297 | "BLAKE2F", 298 | ), 299 | ]; 300 | 301 | for (precompile_addr, early_spec, later_spec, name) in specs_to_test { 302 | let mut early_cfg_env = CfgEnv::default(); 303 | early_cfg_env.spec = early_spec; 304 | early_cfg_env.chain_id = 1; 305 | 306 | let early_env = EvmEnv { block_env: BlockEnv::default(), cfg_env: early_cfg_env }; 307 | let factory = EthEvmFactory; 308 | let mut early_evm = factory.create_evm(EmptyDB::default(), early_env); 309 | 310 | // precompile should NOT be available in early spec 311 | assert!( 312 | early_evm.precompiles_mut().get(&precompile_addr).is_none(), 313 | "{name} precompile at {precompile_addr:?} should NOT be available for early spec {early_spec:?}" 314 | ); 315 | 316 | let mut later_cfg_env = CfgEnv::default(); 317 | later_cfg_env.spec = later_spec; 318 | later_cfg_env.chain_id = 1; 319 | 320 | let later_env = EvmEnv { block_env: BlockEnv::default(), cfg_env: later_cfg_env }; 321 | let mut later_evm = factory.create_evm(EmptyDB::default(), later_env); 322 | 323 | // precompile should be available in later spec 324 | assert!( 325 | later_evm.precompiles_mut().get(&precompile_addr).is_some(), 326 | "{name} precompile at {precompile_addr:?} should be available for later spec {later_spec:?}" 327 | ); 328 | } 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /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/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/evm.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction over EVM. 2 | 3 | use crate::{EvmEnv, EvmError, IntoTxEnv}; 4 | use alloy_primitives::{Address, Bytes}; 5 | use core::{error::Error, fmt::Debug, hash::Hash}; 6 | use revm::{ 7 | context::{result::ExecutionResult, BlockEnv}, 8 | context_interface::{ 9 | result::{HaltReasonTr, ResultAndState}, 10 | ContextTr, 11 | }, 12 | inspector::{JournalExt, NoOpInspector}, 13 | DatabaseCommit, Inspector, 14 | }; 15 | 16 | /// Helper trait to bound [`revm::Database::Error`] with common requirements. 17 | pub trait Database: revm::Database {} 18 | impl Database for T where T: revm::Database {} 19 | 20 | /// An instance of an ethereum virtual machine. 21 | /// 22 | /// An EVM is commonly initialized with the corresponding block context and state and it's only 23 | /// purpose is to execute transactions. 24 | /// 25 | /// Executing a transaction will return the outcome of the transaction. 26 | pub trait Evm { 27 | /// Database type held by the EVM. 28 | type DB; 29 | /// The transaction object that the EVM will execute. 30 | /// 31 | /// Implementations are expected to rely on a single entrypoint for transaction execution such 32 | /// as [`revm::context::TxEnv`]. The actual set of valid inputs is not limited by allowing to 33 | /// provide any [`IntoTxEnv`] implementation for [`Evm::transact`] method. 34 | type Tx: IntoTxEnv; 35 | /// Error type returned by EVM. Contains either errors related to invalid transactions or 36 | /// internal irrecoverable execution errors. 37 | type Error: EvmError; 38 | /// Halt reason. Enum over all possible reasons for halting the execution. When execution halts, 39 | /// it means that transaction is valid, however, it's execution was interrupted (e.g because of 40 | /// running out of gas or overflowing stack). 41 | type HaltReason: HaltReasonTr + Send + Sync + 'static; 42 | /// Identifier of the EVM specification. EVM is expected to use this identifier to determine 43 | /// which features are enabled. 44 | type Spec: Debug + Copy + Hash + Eq + Send + Sync + Default + 'static; 45 | /// Precompiles used by the EVM. 46 | type Precompiles; 47 | /// Evm inspector. 48 | type Inspector; 49 | 50 | /// Reference to [`BlockEnv`]. 51 | fn block(&self) -> &BlockEnv; 52 | 53 | /// Returns the chain ID of the environment. 54 | fn chain_id(&self) -> u64; 55 | 56 | /// Executes a transaction and returns the outcome. 57 | fn transact_raw( 58 | &mut self, 59 | tx: Self::Tx, 60 | ) -> Result, Self::Error>; 61 | 62 | /// Same as [`Evm::transact_raw`], but takes a [`IntoTxEnv`] implementation, thus allowing to 63 | /// support transacting with an external type. 64 | fn transact( 65 | &mut self, 66 | tx: impl IntoTxEnv, 67 | ) -> Result, Self::Error> { 68 | self.transact_raw(tx.into_tx_env()) 69 | } 70 | 71 | /// Executes a system call. 72 | /// 73 | /// Note: this will only keep the target `contract` in the state. This is done because revm is 74 | /// loading [`BlockEnv::beneficiary`] into state by default, and we need to avoid it by also 75 | /// covering edge cases when beneficiary is set to the system contract address. 76 | fn transact_system_call( 77 | &mut self, 78 | caller: Address, 79 | contract: Address, 80 | data: Bytes, 81 | ) -> Result, Self::Error>; 82 | 83 | /// Returns a mutable reference to the underlying database. 84 | fn db_mut(&mut self) -> &mut Self::DB; 85 | 86 | /// Executes a transaction and commits the state changes to the underlying database. 87 | fn transact_commit( 88 | &mut self, 89 | tx: impl IntoTxEnv, 90 | ) -> Result, Self::Error> 91 | where 92 | Self::DB: DatabaseCommit, 93 | { 94 | let ResultAndState { result, state } = self.transact(tx)?; 95 | self.db_mut().commit(state); 96 | 97 | Ok(result) 98 | } 99 | 100 | /// Consumes the EVM and returns the inner [`EvmEnv`]. 101 | fn finish(self) -> (Self::DB, EvmEnv) 102 | where 103 | Self: Sized; 104 | 105 | /// Consumes the EVM and returns the inner database. 106 | fn into_db(self) -> Self::DB 107 | where 108 | Self: Sized, 109 | { 110 | self.finish().0 111 | } 112 | 113 | /// Consumes the EVM and returns the inner [`EvmEnv`]. 114 | fn into_env(self) -> EvmEnv 115 | where 116 | Self: Sized, 117 | { 118 | self.finish().1 119 | } 120 | 121 | /// Determines whether additional transactions should be inspected or not. 122 | /// 123 | /// See also [`EvmFactory::create_evm_with_inspector`]. 124 | fn set_inspector_enabled(&mut self, enabled: bool); 125 | 126 | /// Enables the configured inspector. 127 | /// 128 | /// All additional transactions will be inspected if enabled. 129 | fn enable_inspector(&mut self) { 130 | self.set_inspector_enabled(true) 131 | } 132 | 133 | /// Disables the configured inspector. 134 | /// 135 | /// Transactions will no longer be inspected. 136 | fn disable_inspector(&mut self) { 137 | self.set_inspector_enabled(false) 138 | } 139 | 140 | /// Getter of precompiles. 141 | fn precompiles(&self) -> &Self::Precompiles; 142 | 143 | /// Mutable getter of precompiles. 144 | fn precompiles_mut(&mut self) -> &mut Self::Precompiles; 145 | 146 | /// Getter of inspector. 147 | fn inspector(&self) -> &Self::Inspector; 148 | 149 | /// Mutable getter of inspector. 150 | fn inspector_mut(&mut self) -> &mut Self::Inspector; 151 | } 152 | 153 | /// A type responsible for creating instances of an ethereum virtual machine given a certain input. 154 | pub trait EvmFactory { 155 | /// The EVM type that this factory creates. 156 | type Evm>>: Evm< 157 | DB = DB, 158 | Tx = Self::Tx, 159 | HaltReason = Self::HaltReason, 160 | Error = Self::Error, 161 | Spec = Self::Spec, 162 | Precompiles = Self::Precompiles, 163 | Inspector = I, 164 | >; 165 | 166 | /// The EVM context for inspectors 167 | type Context: ContextTr; 168 | /// Transaction environment. 169 | type Tx: IntoTxEnv; 170 | /// EVM error. See [`Evm::Error`]. 171 | type Error: EvmError; 172 | /// Halt reason. See [`Evm::HaltReason`]. 173 | type HaltReason: HaltReasonTr + Send + Sync + 'static; 174 | /// The EVM specification identifier, see [`Evm::Spec`]. 175 | type Spec: Debug + Copy + Hash + Eq + Send + Sync + Default + 'static; 176 | /// Precompiles used by the EVM. 177 | type Precompiles; 178 | 179 | /// Creates a new instance of an EVM. 180 | fn create_evm( 181 | &self, 182 | db: DB, 183 | evm_env: EvmEnv, 184 | ) -> Self::Evm; 185 | 186 | /// Creates a new instance of an EVM with an inspector. 187 | /// 188 | /// Note: It is expected that the [`Inspector`] is usually provided as `&mut Inspector` so that 189 | /// it remains owned by the call site when [`Evm::transact`] is invoked. 190 | fn create_evm_with_inspector>>( 191 | &self, 192 | db: DB, 193 | input: EvmEnv, 194 | inspector: I, 195 | ) -> Self::Evm; 196 | } 197 | -------------------------------------------------------------------------------- /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, doc_auto_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 precompiles; 24 | 25 | mod either; 26 | 27 | // re-export revm and op-revm 28 | #[cfg(feature = "op")] 29 | pub use op_revm; 30 | pub use revm; 31 | -------------------------------------------------------------------------------- /crates/evm/src/precompiles.rs: -------------------------------------------------------------------------------- 1 | //! Helpers for dealing with Precompiles. 2 | 3 | use alloc::{borrow::Cow, boxed::Box, string::String, sync::Arc}; 4 | use alloy_consensus::transaction::Either; 5 | use alloy_primitives::{ 6 | map::{HashMap, HashSet}, 7 | Address, Bytes, 8 | }; 9 | use revm::{ 10 | context::{Cfg, ContextTr, LocalContextTr}, 11 | handler::{EthPrecompiles, PrecompileProvider}, 12 | interpreter::{CallInput, Gas, InputsImpl, InstructionResult, InterpreterResult}, 13 | precompile::{PrecompileError, PrecompileResult, Precompiles}, 14 | }; 15 | 16 | /// A mapping of precompile contracts that can be either static (builtin) or dynamic. 17 | /// 18 | /// This is an optimization that allows us to keep using the static precompiles 19 | /// until we need to modify them, at which point we convert to the dynamic representation. 20 | #[derive(Clone)] 21 | pub enum PrecompilesMap { 22 | /// Static builtin precompiles. 23 | Builtin(Cow<'static, Precompiles>), 24 | /// Dynamic precompiles that can be modified at runtime. 25 | Dynamic(DynPrecompiles), 26 | } 27 | 28 | impl PrecompilesMap { 29 | /// Creates the [`PrecompilesMap`] from a static reference. 30 | pub fn from_static(precompiles: &'static Precompiles) -> Self { 31 | Self::new(Cow::Borrowed(precompiles)) 32 | } 33 | 34 | /// Creates a new set of precompiles for a spec. 35 | pub fn new(precompiles: Cow<'static, Precompiles>) -> Self { 36 | Self::Builtin(precompiles) 37 | } 38 | 39 | /// Maps a precompile at the given address using the provided function. 40 | pub fn map_precompile(&mut self, address: &Address, f: F) 41 | where 42 | F: FnOnce(DynPrecompile) -> DynPrecompile + Send + Sync + 'static, 43 | { 44 | let dyn_precompiles = self.ensure_dynamic_precompiles(); 45 | 46 | // get the current precompile at the address 47 | if let Some(dyn_precompile) = dyn_precompiles.inner.remove(address) { 48 | // apply the transformation function 49 | let transformed = f(dyn_precompile); 50 | 51 | // update the precompile at the address 52 | dyn_precompiles.inner.insert(*address, transformed); 53 | } 54 | } 55 | 56 | /// Maps all precompiles using the provided function. 57 | pub fn map_precompiles(&mut self, mut f: F) 58 | where 59 | F: FnMut(&Address, DynPrecompile) -> DynPrecompile, 60 | { 61 | let dyn_precompiles = self.ensure_dynamic_precompiles(); 62 | 63 | // apply the transformation to each precompile 64 | let entries = dyn_precompiles.inner.drain(); 65 | let mut new_map = 66 | HashMap::with_capacity_and_hasher(entries.size_hint().0, Default::default()); 67 | for (addr, precompile) in entries { 68 | let transformed = f(&addr, precompile); 69 | new_map.insert(addr, transformed); 70 | } 71 | 72 | dyn_precompiles.inner = new_map; 73 | } 74 | 75 | /// Applies a new precompile at the given address. 76 | pub fn apply_precompile(&mut self, address: &Address, f: F) 77 | where 78 | F: FnOnce(Option) -> Option, 79 | { 80 | let dyn_precompiles = self.ensure_dynamic_precompiles(); 81 | let current = dyn_precompiles.inner.get(address).cloned(); 82 | 83 | // apply the transformation function 84 | let result = f(current); 85 | 86 | match result { 87 | Some(transformed) => { 88 | // insert the transformed precompile 89 | dyn_precompiles.inner.insert(*address, transformed); 90 | dyn_precompiles.addresses.insert(*address); 91 | } 92 | None => { 93 | // remove the precompile if the transformation returned None 94 | dyn_precompiles.inner.remove(address); 95 | dyn_precompiles.addresses.remove(address); 96 | } 97 | } 98 | } 99 | 100 | /// Ensures that precompiles are in their dynamic representation. 101 | /// If they are already dynamic, this is a no-op. 102 | /// Returns a mutable reference to the dynamic precompiles. 103 | pub fn ensure_dynamic_precompiles(&mut self) -> &mut DynPrecompiles { 104 | if let Self::Builtin(ref precompiles_cow) = self { 105 | let mut dynamic = DynPrecompiles::default(); 106 | 107 | let static_precompiles = match precompiles_cow { 108 | Cow::Borrowed(static_ref) => static_ref, 109 | Cow::Owned(owned) => owned, 110 | }; 111 | 112 | for (addr, precompile_fn) in static_precompiles.inner() { 113 | let dyn_precompile: DynPrecompile = (*precompile_fn).into(); 114 | dynamic.inner.insert(*addr, dyn_precompile); 115 | dynamic.addresses.insert(*addr); 116 | } 117 | 118 | *self = Self::Dynamic(dynamic); 119 | } 120 | 121 | match self { 122 | Self::Dynamic(dynamic) => dynamic, 123 | _ => unreachable!("We just ensured that this is a Dynamic variant"), 124 | } 125 | } 126 | 127 | /// Returns an iterator over references to precompile addresses. 128 | pub fn addresses(&self) -> impl Iterator { 129 | match self { 130 | Self::Builtin(precompiles) => Either::Left(precompiles.addresses()), 131 | Self::Dynamic(dyn_precompiles) => Either::Right(dyn_precompiles.addresses.iter()), 132 | } 133 | } 134 | 135 | /// Gets a reference to the precompile at the given address. 136 | pub fn get(&self, address: &Address) -> Option { 137 | match self { 138 | Self::Builtin(precompiles) => precompiles.get(address).map(Either::Left), 139 | Self::Dynamic(dyn_precompiles) => dyn_precompiles.inner.get(address).map(Either::Right), 140 | } 141 | } 142 | } 143 | 144 | impl From for PrecompilesMap { 145 | fn from(value: EthPrecompiles) -> Self { 146 | Self::from_static(value.precompiles) 147 | } 148 | } 149 | 150 | impl core::fmt::Debug for PrecompilesMap { 151 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 152 | match self { 153 | Self::Builtin(_) => f.debug_struct("PrecompilesMap::Builtin").finish(), 154 | Self::Dynamic(precompiles) => f 155 | .debug_struct("PrecompilesMap::Dynamic") 156 | .field("addresses", &precompiles.addresses) 157 | .finish(), 158 | } 159 | } 160 | } 161 | 162 | impl PrecompileProvider for PrecompilesMap { 163 | type Output = InterpreterResult; 164 | 165 | fn set_spec(&mut self, _spec: ::Spec) -> bool { 166 | false 167 | } 168 | 169 | fn run( 170 | &mut self, 171 | context: &mut CTX, 172 | address: &Address, 173 | inputs: &InputsImpl, 174 | _is_static: bool, 175 | gas_limit: u64, 176 | ) -> Result, String> { 177 | // Get the precompile at the address 178 | let precompile = self.get(address); 179 | 180 | if precompile.is_none() { 181 | return Ok(None); 182 | } 183 | 184 | let mut result = InterpreterResult { 185 | result: InstructionResult::Return, 186 | gas: Gas::new(gas_limit), 187 | output: Bytes::new(), 188 | }; 189 | 190 | // Execute the precompile 191 | let r; 192 | let input_bytes = match &inputs.input { 193 | CallInput::SharedBuffer(range) => { 194 | match context.local().shared_memory_buffer_slice(range.clone()) { 195 | Some(slice) => { 196 | r = slice; 197 | &*r 198 | } 199 | None => &[], 200 | } 201 | } 202 | CallInput::Bytes(bytes) => bytes.as_ref(), 203 | }; 204 | 205 | let precompile_result = 206 | precompile.expect("None case already handled").call(input_bytes, gas_limit); 207 | 208 | match precompile_result { 209 | Ok(output) => { 210 | let underflow = result.gas.record_cost(output.gas_used); 211 | assert!(underflow, "Gas underflow is not possible"); 212 | result.result = InstructionResult::Return; 213 | result.output = output.bytes; 214 | } 215 | Err(PrecompileError::Fatal(e)) => return Err(e), 216 | Err(e) => { 217 | result.result = if e.is_oog() { 218 | InstructionResult::PrecompileOOG 219 | } else { 220 | InstructionResult::PrecompileError 221 | }; 222 | } 223 | }; 224 | 225 | Ok(Some(result)) 226 | } 227 | 228 | fn warm_addresses(&self) -> Box> { 229 | Box::new(self.addresses().copied()) 230 | } 231 | 232 | fn contains(&self, address: &Address) -> bool { 233 | self.get(address).is_some() 234 | } 235 | } 236 | 237 | /// A dynamic precompile implementation that can be modified at runtime. 238 | #[derive(Clone)] 239 | pub struct DynPrecompile(pub(crate) Arc); 240 | 241 | impl core::fmt::Debug for DynPrecompile { 242 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 243 | f.debug_struct("DynPrecompile").finish() 244 | } 245 | } 246 | 247 | impl<'a, F> From for DynPrecompile 248 | where 249 | F: FnOnce(&'a [u8], u64) -> PrecompileResult + Precompile + Send + Sync + 'static, 250 | { 251 | fn from(f: F) -> Self { 252 | Self(Arc::new(f)) 253 | } 254 | } 255 | 256 | /// A mutable representation of precompiles that allows for runtime modification. 257 | /// 258 | /// This structure stores dynamic precompiles that can be modified at runtime, 259 | /// unlike the static `Precompiles` struct from revm. 260 | #[derive(Clone, Default)] 261 | pub struct DynPrecompiles { 262 | /// Precompiles 263 | inner: HashMap, 264 | /// Addresses of precompile 265 | addresses: HashSet
, 266 | } 267 | 268 | impl core::fmt::Debug for DynPrecompiles { 269 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 270 | f.debug_struct("DynPrecompiles").field("addresses", &self.addresses).finish() 271 | } 272 | } 273 | 274 | /// Trait for implementing precompiled contracts. 275 | pub trait Precompile { 276 | /// Execute the precompile with the given input data and gas limit. 277 | fn call(&self, data: &[u8], gas: u64) -> PrecompileResult; 278 | } 279 | 280 | impl Precompile for F 281 | where 282 | F: Fn(&[u8], u64) -> PrecompileResult + Send + Sync, 283 | { 284 | fn call(&self, data: &[u8], gas: u64) -> PrecompileResult { 285 | self(data, gas) 286 | } 287 | } 288 | 289 | impl Precompile for DynPrecompile { 290 | fn call(&self, data: &[u8], gas: u64) -> PrecompileResult { 291 | self.0.call(data, gas) 292 | } 293 | } 294 | 295 | impl Precompile for &DynPrecompile { 296 | fn call(&self, data: &[u8], gas: u64) -> PrecompileResult { 297 | self.0.call(data, gas) 298 | } 299 | } 300 | 301 | impl Precompile for Either { 302 | fn call(&self, data: &[u8], gas: u64) -> PrecompileResult { 303 | match self { 304 | Self::Left(p) => p.call(data, gas), 305 | Self::Right(p) => p.call(data, gas), 306 | } 307 | } 308 | } 309 | 310 | #[cfg(test)] 311 | mod tests { 312 | use super::*; 313 | use alloy_primitives::{address, Bytes}; 314 | use revm::precompile::PrecompileOutput; 315 | 316 | #[test] 317 | fn test_map_precompile() { 318 | let eth_precompiles = EthPrecompiles::default(); 319 | let mut spec_precompiles = PrecompilesMap::from(eth_precompiles); 320 | 321 | // create a test input for the precompile (identity precompile) 322 | let identity_address = address!("0x0000000000000000000000000000000000000004"); 323 | let test_input = Bytes::from_static(b"test data"); 324 | let gas_limit = 1000; 325 | 326 | // Ensure we're using dynamic precompiles 327 | spec_precompiles.ensure_dynamic_precompiles(); 328 | 329 | // using the dynamic precompiles interface 330 | let dyn_precompile = match &spec_precompiles { 331 | PrecompilesMap::Dynamic(dyn_precompiles) => { 332 | dyn_precompiles.inner.get(&identity_address).unwrap() 333 | } 334 | _ => panic!("Expected dynamic precompiles"), 335 | }; 336 | 337 | let result = dyn_precompile.call(&test_input, gas_limit).unwrap(); 338 | assert_eq!(result.bytes, test_input, "Identity precompile should return the input data"); 339 | 340 | // define a function to modify the precompile 341 | // this will change the identity precompile to always return a fixed value 342 | let constant_bytes = Bytes::from_static(b"constant value"); 343 | 344 | // define a function to modify the precompile to always return a constant value 345 | spec_precompiles.map_precompile(&identity_address, move |_original_dyn| { 346 | // create a new DynPrecompile that always returns our constant 347 | |_data: &[u8], _gas: u64| -> PrecompileResult { 348 | Ok(PrecompileOutput { gas_used: 10, bytes: Bytes::from_static(b"constant value") }) 349 | } 350 | .into() 351 | }); 352 | 353 | // get the modified precompile and check it 354 | let dyn_precompile = match &spec_precompiles { 355 | PrecompilesMap::Dynamic(dyn_precompiles) => { 356 | dyn_precompiles.inner.get(&identity_address).unwrap() 357 | } 358 | _ => panic!("Expected dynamic precompiles"), 359 | }; 360 | 361 | let result = dyn_precompile.call(&test_input, gas_limit).unwrap(); 362 | assert_eq!( 363 | result.bytes, constant_bytes, 364 | "Modified precompile should return the constant value" 365 | ); 366 | } 367 | 368 | #[test] 369 | fn test_closure_precompile() { 370 | let test_input = Bytes::from_static(b"test data"); 371 | let expected_output = Bytes::from_static(b"processed: test data"); 372 | let gas_limit = 1000; 373 | 374 | // define a closure that implements the precompile functionality 375 | let closure_precompile = |data: &[u8], _gas: u64| -> PrecompileResult { 376 | let mut output = b"processed: ".to_vec(); 377 | output.extend_from_slice(data.as_ref()); 378 | Ok(PrecompileOutput { gas_used: 15, bytes: Bytes::from(output) }) 379 | }; 380 | 381 | let dyn_precompile: DynPrecompile = closure_precompile.into(); 382 | 383 | let result = dyn_precompile.call(&test_input, gas_limit).unwrap(); 384 | assert_eq!(result.gas_used, 15); 385 | assert_eq!(result.bytes, expected_output); 386 | } 387 | 388 | #[test] 389 | fn test_get_precompile() { 390 | let eth_precompiles = EthPrecompiles::default(); 391 | let spec_precompiles = PrecompilesMap::from(eth_precompiles); 392 | 393 | let identity_address = address!("0x0000000000000000000000000000000000000004"); 394 | let test_input = Bytes::from_static(b"test data"); 395 | let gas_limit = 1000; 396 | 397 | let precompile = spec_precompiles.get(&identity_address); 398 | assert!(precompile.is_some(), "Identity precompile should exist"); 399 | 400 | let result = precompile.unwrap().call(&test_input, gas_limit).unwrap(); 401 | assert_eq!(result.bytes, test_input, "Identity precompile should return the input data"); 402 | 403 | let nonexistent_address = address!("0x0000000000000000000000000000000000000099"); 404 | assert!( 405 | spec_precompiles.get(&nonexistent_address).is_none(), 406 | "Non-existent precompile should not be found" 407 | ); 408 | 409 | let mut dynamic_precompiles = spec_precompiles; 410 | dynamic_precompiles.ensure_dynamic_precompiles(); 411 | 412 | let dyn_precompile = dynamic_precompiles.get(&identity_address); 413 | assert!( 414 | dyn_precompile.is_some(), 415 | "Identity precompile should exist after conversion to dynamic" 416 | ); 417 | 418 | let result = dyn_precompile.unwrap().call(&test_input, gas_limit).unwrap(); 419 | assert_eq!( 420 | result.bytes, test_input, 421 | "Identity precompile should return the input data after conversion to dynamic" 422 | ); 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /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-consensus.workspace = true 25 | 26 | revm.workspace = true 27 | op-revm.workspace = true 28 | 29 | auto_impl.workspace = true 30 | 31 | [features] 32 | default = ["std"] 33 | std = [ 34 | "alloy-primitives/std", 35 | "revm/std", 36 | "alloy-evm/std", 37 | "op-revm/std", 38 | "alloy-consensus/std", 39 | "alloy-eips/std", 40 | "op-alloy-consensus/std" 41 | ] 42 | -------------------------------------------------------------------------------- /crates/op-evm/README.md: -------------------------------------------------------------------------------- 1 | # alloy-evm 2 | 3 | OP EVM implementation. 4 | -------------------------------------------------------------------------------- /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::{database::State, 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 State, 23 | ) -> Result<(), DB::Error> 24 | where 25 | DB: Database, 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 acc = db.load_cache_account(CREATE_2_DEPLOYER_ADDR)?; 35 | 36 | // Update the account info with the create2 deployer codehash and bytecode. 37 | let mut acc_info = acc.account_info().unwrap_or_default(); 38 | acc_info.code_hash = CREATE_2_DEPLOYER_CODEHASH; 39 | acc_info.code = Some(Bytecode::new_raw(Bytes::from_static(&CREATE_2_DEPLOYER_BYTECODE))); 40 | 41 | // Convert the cache account back into a revm account and mark it as touched. 42 | let mut revm_acc: revm::state::Account = acc_info.into(); 43 | revm_acc.mark_touch(); 44 | 45 | // Commit the create2 deployer account to the database. 46 | db.commit(HashMap::from_iter([(CREATE_2_DEPLOYER_ADDR, revm_acc)])); 47 | return Ok(()); 48 | } 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /crates/op-evm/src/block/mod.rs: -------------------------------------------------------------------------------- 1 | //! Block executor for Optimism. 2 | 3 | use crate::OpEvmFactory; 4 | use alloc::{borrow::Cow, boxed::Box, vec::Vec}; 5 | use alloy_consensus::{Eip658Value, Header, Transaction, TxReceipt}; 6 | use alloy_eips::{Encodable2718, Typed2718}; 7 | use alloy_evm::{ 8 | block::{ 9 | state_changes::{balance_increment_state, post_block_balance_increments}, 10 | BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, 11 | BlockExecutorFor, BlockValidationError, CommitChanges, ExecutableTx, OnStateHook, 12 | StateChangePostBlockSource, StateChangeSource, SystemCaller, 13 | }, 14 | eth::receipt_builder::ReceiptBuilderCtx, 15 | Database, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded, 16 | }; 17 | use alloy_op_hardforks::{OpChainHardforks, OpHardforks}; 18 | use alloy_primitives::{Bytes, B256}; 19 | use canyon::ensure_create2_deployer; 20 | use op_alloy_consensus::OpDepositReceipt; 21 | use op_revm::transaction::deposit::DEPOSIT_TRANSACTION_TYPE; 22 | pub use receipt_builder::OpAlloyReceiptBuilder; 23 | use receipt_builder::OpReceiptBuilder; 24 | use revm::{ 25 | context::result::{ExecutionResult, ResultAndState}, 26 | database::State, 27 | DatabaseCommit, Inspector, 28 | }; 29 | 30 | mod canyon; 31 | pub mod receipt_builder; 32 | 33 | /// Context for OP block execution. 34 | #[derive(Debug, Default, Clone)] 35 | pub struct OpBlockExecutionCtx { 36 | /// Parent block hash. 37 | pub parent_hash: B256, 38 | /// Parent beacon block root. 39 | pub parent_beacon_block_root: Option, 40 | /// The block's extra data. 41 | pub extra_data: Bytes, 42 | } 43 | 44 | /// Block executor for Optimism. 45 | #[derive(Debug)] 46 | pub struct OpBlockExecutor { 47 | /// Spec. 48 | spec: Spec, 49 | /// Receipt builder. 50 | receipt_builder: R, 51 | 52 | /// Context for block execution. 53 | ctx: OpBlockExecutionCtx, 54 | /// The EVM used by executor. 55 | evm: Evm, 56 | /// Receipts of executed transactions. 57 | receipts: Vec, 58 | /// Total gas used by executed transactions. 59 | gas_used: u64, 60 | /// Whether Regolith hardfork is active. 61 | is_regolith: bool, 62 | /// Utility to call system smart contracts. 63 | system_caller: SystemCaller, 64 | } 65 | 66 | impl OpBlockExecutor 67 | where 68 | E: Evm, 69 | R: OpReceiptBuilder, 70 | Spec: OpHardforks + Clone, 71 | { 72 | /// Creates a new [`OpBlockExecutor`]. 73 | pub fn new(evm: E, ctx: OpBlockExecutionCtx, spec: Spec, receipt_builder: R) -> Self { 74 | Self { 75 | is_regolith: spec.is_regolith_active_at_timestamp(evm.block().timestamp), 76 | evm, 77 | system_caller: SystemCaller::new(spec.clone()), 78 | spec, 79 | receipt_builder, 80 | receipts: Vec::new(), 81 | gas_used: 0, 82 | ctx, 83 | } 84 | } 85 | } 86 | 87 | impl<'db, DB, E, R, Spec> BlockExecutor for OpBlockExecutor 88 | where 89 | DB: Database + 'db, 90 | E: Evm< 91 | DB = &'db mut State, 92 | Tx: FromRecoveredTx + FromTxWithEncoded, 93 | >, 94 | R: OpReceiptBuilder, 95 | Spec: OpHardforks, 96 | { 97 | type Transaction = R::Transaction; 98 | type Receipt = R::Receipt; 99 | type Evm = E; 100 | 101 | fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { 102 | // Set state clear flag if the block is after the Spurious Dragon hardfork. 103 | let state_clear_flag = 104 | self.spec.is_spurious_dragon_active_at_block(self.evm.block().number); 105 | self.evm.db_mut().set_state_clear_flag(state_clear_flag); 106 | 107 | self.system_caller.apply_blockhashes_contract_call(self.ctx.parent_hash, &mut self.evm)?; 108 | self.system_caller 109 | .apply_beacon_root_contract_call(self.ctx.parent_beacon_block_root, &mut self.evm)?; 110 | 111 | // Ensure that the create2deployer is force-deployed at the canyon transition. Optimism 112 | // blocks will always have at least a single transaction in them (the L1 info transaction), 113 | // so we can safely assume that this will always be triggered upon the transition and that 114 | // the above check for empty blocks will never be hit on OP chains. 115 | ensure_create2_deployer(&self.spec, self.evm.block().timestamp, self.evm.db_mut()) 116 | .map_err(BlockExecutionError::other)?; 117 | 118 | Ok(()) 119 | } 120 | 121 | fn execute_transaction_with_commit_condition( 122 | &mut self, 123 | tx: impl ExecutableTx, 124 | f: impl FnOnce(&ExecutionResult<::HaltReason>) -> CommitChanges, 125 | ) -> Result, BlockExecutionError> { 126 | let is_deposit = tx.tx().ty() == DEPOSIT_TRANSACTION_TYPE; 127 | 128 | // The sum of the transaction’s gas limit, Tg, and the gas utilized in this block prior, 129 | // must be no greater than the block’s gasLimit. 130 | let block_available_gas = self.evm.block().gas_limit - self.gas_used; 131 | if tx.tx().gas_limit() > block_available_gas && (self.is_regolith || !is_deposit) { 132 | return Err(BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas { 133 | transaction_gas_limit: tx.tx().gas_limit(), 134 | block_available_gas, 135 | } 136 | .into()); 137 | } 138 | 139 | // Cache the depositor account prior to the state transition for the deposit nonce. 140 | // 141 | // Note that this *only* needs to be done post-regolith hardfork, as deposit nonces 142 | // were not introduced in Bedrock. In addition, regular transactions don't have deposit 143 | // nonces, so we don't need to touch the DB for those. 144 | let depositor = (self.is_regolith && is_deposit) 145 | .then(|| { 146 | self.evm 147 | .db_mut() 148 | .load_cache_account(*tx.signer()) 149 | .map(|acc| acc.account_info().unwrap_or_default()) 150 | }) 151 | .transpose() 152 | .map_err(BlockExecutionError::other)?; 153 | 154 | let hash = tx.tx().trie_hash(); 155 | 156 | // Execute transaction. 157 | let ResultAndState { result, state } = 158 | self.evm.transact(tx).map_err(move |err| BlockExecutionError::evm(err, hash))?; 159 | 160 | if !f(&result).should_commit() { 161 | return Ok(None); 162 | } 163 | 164 | self.system_caller.on_state(StateChangeSource::Transaction(self.receipts.len()), &state); 165 | 166 | let gas_used = result.gas_used(); 167 | 168 | // append gas used 169 | self.gas_used += gas_used; 170 | 171 | self.receipts.push( 172 | match self.receipt_builder.build_receipt(ReceiptBuilderCtx { 173 | tx: tx.tx(), 174 | result, 175 | cumulative_gas_used: self.gas_used, 176 | evm: &self.evm, 177 | state: &state, 178 | }) { 179 | Ok(receipt) => receipt, 180 | Err(ctx) => { 181 | let receipt = alloy_consensus::Receipt { 182 | // Success flag was added in `EIP-658: Embedding transaction status code 183 | // in receipts`. 184 | status: Eip658Value::Eip658(ctx.result.is_success()), 185 | cumulative_gas_used: self.gas_used, 186 | logs: ctx.result.into_logs(), 187 | }; 188 | 189 | self.receipt_builder.build_deposit_receipt(OpDepositReceipt { 190 | inner: receipt, 191 | deposit_nonce: depositor.map(|account| account.nonce), 192 | // The deposit receipt version was introduced in Canyon to indicate an 193 | // update to how receipt hashes should be computed 194 | // when set. The state transition process ensures 195 | // this is only set for post-Canyon deposit 196 | // transactions. 197 | deposit_receipt_version: (is_deposit 198 | && self.spec.is_canyon_active_at_timestamp(self.evm.block().timestamp)) 199 | .then_some(1), 200 | }) 201 | } 202 | }, 203 | ); 204 | 205 | self.evm.db_mut().commit(state); 206 | 207 | Ok(Some(gas_used)) 208 | } 209 | 210 | fn finish( 211 | mut self, 212 | ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { 213 | let balance_increments = 214 | post_block_balance_increments::
(&self.spec, self.evm.block(), &[], None); 215 | // increment balances 216 | self.evm 217 | .db_mut() 218 | .increment_balances(balance_increments.clone()) 219 | .map_err(|_| BlockValidationError::IncrementBalanceFailed)?; 220 | // call state hook with changes due to balance increments. 221 | self.system_caller.try_on_state_with(|| { 222 | balance_increment_state(&balance_increments, self.evm.db_mut()).map(|state| { 223 | ( 224 | StateChangeSource::PostBlock(StateChangePostBlockSource::BalanceIncrements), 225 | Cow::Owned(state), 226 | ) 227 | }) 228 | })?; 229 | 230 | let gas_used = self.receipts.last().map(|r| r.cumulative_gas_used()).unwrap_or_default(); 231 | Ok(( 232 | self.evm, 233 | BlockExecutionResult { 234 | receipts: self.receipts, 235 | requests: Default::default(), 236 | gas_used, 237 | }, 238 | )) 239 | } 240 | 241 | fn set_state_hook(&mut self, hook: Option>) { 242 | self.system_caller.with_state_hook(hook); 243 | } 244 | 245 | fn evm_mut(&mut self) -> &mut Self::Evm { 246 | &mut self.evm 247 | } 248 | 249 | fn evm(&self) -> &Self::Evm { 250 | &self.evm 251 | } 252 | } 253 | 254 | /// Ethereum block executor factory. 255 | #[derive(Debug, Clone, Default, Copy)] 256 | pub struct OpBlockExecutorFactory< 257 | R = OpAlloyReceiptBuilder, 258 | Spec = OpChainHardforks, 259 | EvmFactory = OpEvmFactory, 260 | > { 261 | /// Receipt builder. 262 | receipt_builder: R, 263 | /// Chain specification. 264 | spec: Spec, 265 | /// EVM factory. 266 | evm_factory: EvmFactory, 267 | } 268 | 269 | impl OpBlockExecutorFactory { 270 | /// Creates a new [`OpBlockExecutorFactory`] with the given spec, [`EvmFactory`], and 271 | /// [`OpReceiptBuilder`]. 272 | pub const fn new(receipt_builder: R, spec: Spec, evm_factory: EvmFactory) -> Self { 273 | Self { receipt_builder, spec, evm_factory } 274 | } 275 | 276 | /// Exposes the receipt builder. 277 | pub const fn receipt_builder(&self) -> &R { 278 | &self.receipt_builder 279 | } 280 | 281 | /// Exposes the chain specification. 282 | pub const fn spec(&self) -> &Spec { 283 | &self.spec 284 | } 285 | 286 | /// Exposes the EVM factory. 287 | pub const fn evm_factory(&self) -> &EvmFactory { 288 | &self.evm_factory 289 | } 290 | } 291 | 292 | impl BlockExecutorFactory for OpBlockExecutorFactory 293 | where 294 | R: OpReceiptBuilder, 295 | Spec: OpHardforks, 296 | EvmF: EvmFactory + FromTxWithEncoded>, 297 | Self: 'static, 298 | { 299 | type EvmFactory = EvmF; 300 | type ExecutionCtx<'a> = OpBlockExecutionCtx; 301 | type Transaction = R::Transaction; 302 | type Receipt = R::Receipt; 303 | 304 | fn evm_factory(&self) -> &Self::EvmFactory { 305 | &self.evm_factory 306 | } 307 | 308 | fn create_executor<'a, DB, I>( 309 | &'a self, 310 | evm: EvmF::Evm<&'a mut State, I>, 311 | ctx: Self::ExecutionCtx<'a>, 312 | ) -> impl BlockExecutorFor<'a, Self, DB, I> 313 | where 314 | DB: Database + 'a, 315 | I: Inspector>> + 'a, 316 | { 317 | OpBlockExecutor::new(evm, ctx, &self.spec, &self.receipt_builder) 318 | } 319 | } 320 | 321 | #[cfg(test)] 322 | mod tests { 323 | use alloy_consensus::{transaction::Recovered, SignableTransaction, TxLegacy}; 324 | use alloy_eips::eip2718::WithEncoded; 325 | use alloy_evm::EvmEnv; 326 | use alloy_primitives::{Address, Signature}; 327 | use op_alloy_consensus::OpTxEnvelope; 328 | use revm::database::{CacheDB, EmptyDB}; 329 | 330 | use super::*; 331 | 332 | #[test] 333 | fn test_with_encoded() { 334 | let executor_factory = OpBlockExecutorFactory::new( 335 | OpAlloyReceiptBuilder::default(), 336 | OpChainHardforks::op_mainnet(), 337 | OpEvmFactory::default(), 338 | ); 339 | let mut db = State::builder().with_database(CacheDB::::default()).build(); 340 | let evm = executor_factory.evm_factory.create_evm(&mut db, EvmEnv::default()); 341 | let mut executor = executor_factory.create_executor(evm, OpBlockExecutionCtx::default()); 342 | let tx = Recovered::new_unchecked( 343 | OpTxEnvelope::Legacy(TxLegacy::default().into_signed(Signature::new( 344 | Default::default(), 345 | Default::default(), 346 | Default::default(), 347 | ))), 348 | Address::ZERO, 349 | ); 350 | let tx_with_encoded = WithEncoded::new(tx.encoded_2718().into(), tx.clone()); 351 | 352 | // make sure we can use both `WithEncoded` and transaction itself as inputs. 353 | let _ = executor.execute_transaction(&tx); 354 | let _ = executor.execute_transaction(&tx_with_encoded); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /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/op-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, doc_auto_cfg))] 8 | #![cfg_attr(not(feature = "std"), no_std)] 9 | 10 | extern crate alloc; 11 | 12 | use alloc::vec::Vec; 13 | use alloy_evm::{precompiles::PrecompilesMap, Database, Evm, EvmEnv, EvmFactory}; 14 | use alloy_primitives::{Address, Bytes, TxKind, U256}; 15 | use core::{ 16 | fmt::Debug, 17 | ops::{Deref, DerefMut}, 18 | }; 19 | use op_alloy_consensus::OpTxType; 20 | use op_revm::{ 21 | precompiles::OpPrecompiles, DefaultOp, OpBuilder, OpContext, OpHaltReason, OpSpecId, 22 | OpTransaction, OpTransactionError, 23 | }; 24 | use revm::{ 25 | context::{BlockEnv, TxEnv}, 26 | context_interface::result::{EVMError, ResultAndState}, 27 | handler::{instructions::EthInstructions, PrecompileProvider}, 28 | inspector::NoOpInspector, 29 | interpreter::{interpreter::EthInterpreter, InterpreterResult}, 30 | Context, ExecuteEvm, InspectEvm, Inspector, 31 | }; 32 | 33 | pub mod block; 34 | pub use block::{OpBlockExecutionCtx, OpBlockExecutor, OpBlockExecutorFactory}; 35 | 36 | /// OP EVM implementation. 37 | /// 38 | /// This is a wrapper type around the `revm` evm with optional [`Inspector`] (tracing) 39 | /// support. [`Inspector`] support is configurable at runtime because it's part of the underlying 40 | /// [`OpEvm`](op_revm::OpEvm) type. 41 | #[allow(missing_debug_implementations)] // missing revm::OpContext Debug impl 42 | pub struct OpEvm { 43 | inner: op_revm::OpEvm, I, EthInstructions>, P>, 44 | inspect: bool, 45 | } 46 | 47 | impl OpEvm { 48 | /// Provides a reference to the EVM context. 49 | pub const fn ctx(&self) -> &OpContext { 50 | &self.inner.0.ctx 51 | } 52 | 53 | /// Provides a mutable reference to the EVM context. 54 | pub fn ctx_mut(&mut self) -> &mut OpContext { 55 | &mut self.inner.0.ctx 56 | } 57 | } 58 | 59 | impl OpEvm { 60 | /// Creates a new OP EVM instance. 61 | /// 62 | /// The `inspect` argument determines whether the configured [`Inspector`] of the given 63 | /// [`OpEvm`](op_revm::OpEvm) should be invoked on [`Evm::transact`]. 64 | pub const fn new( 65 | evm: op_revm::OpEvm, I, EthInstructions>, P>, 66 | inspect: bool, 67 | ) -> Self { 68 | Self { inner: evm, inspect } 69 | } 70 | } 71 | 72 | impl Deref for OpEvm { 73 | type Target = OpContext; 74 | 75 | #[inline] 76 | fn deref(&self) -> &Self::Target { 77 | self.ctx() 78 | } 79 | } 80 | 81 | impl DerefMut for OpEvm { 82 | #[inline] 83 | fn deref_mut(&mut self) -> &mut Self::Target { 84 | self.ctx_mut() 85 | } 86 | } 87 | 88 | impl Evm for OpEvm 89 | where 90 | DB: Database, 91 | I: Inspector>, 92 | P: PrecompileProvider, Output = InterpreterResult>, 93 | { 94 | type DB = DB; 95 | type Tx = OpTransaction; 96 | type Error = EVMError; 97 | type HaltReason = OpHaltReason; 98 | type Spec = OpSpecId; 99 | type Precompiles = P; 100 | type Inspector = I; 101 | 102 | fn block(&self) -> &BlockEnv { 103 | &self.block 104 | } 105 | 106 | fn chain_id(&self) -> u64 { 107 | self.cfg.chain_id 108 | } 109 | 110 | fn transact_raw( 111 | &mut self, 112 | tx: Self::Tx, 113 | ) -> Result, Self::Error> { 114 | if self.inspect { 115 | self.inner.set_tx(tx); 116 | self.inner.inspect_replay() 117 | } else { 118 | self.inner.transact(tx) 119 | } 120 | } 121 | 122 | fn transact_system_call( 123 | &mut self, 124 | caller: Address, 125 | contract: Address, 126 | data: Bytes, 127 | ) -> Result, Self::Error> { 128 | let tx = OpTransaction { 129 | base: TxEnv { 130 | caller, 131 | kind: TxKind::Call(contract), 132 | // Explicitly set nonce to 0 so revm does not do any nonce checks 133 | nonce: 0, 134 | gas_limit: 30_000_000, 135 | value: U256::ZERO, 136 | data, 137 | // Setting the gas price to zero enforces that no value is transferred as part of 138 | // the call, and that the call will not count against the block's 139 | // gas limit 140 | gas_price: 0, 141 | // The chain ID check is not relevant here and is disabled if set to None 142 | chain_id: None, 143 | // Setting the gas priority fee to None ensures the effective gas price is derived 144 | // from the `gas_price` field, which we need to be zero 145 | gas_priority_fee: None, 146 | access_list: Default::default(), 147 | // blob fields can be None for this tx 148 | blob_hashes: Vec::new(), 149 | max_fee_per_blob_gas: 0, 150 | tx_type: OpTxType::Deposit as u8, 151 | authorization_list: Default::default(), 152 | }, 153 | // The L1 fee is not charged for the EIP-4788 transaction, submit zero bytes for the 154 | // enveloped tx size. 155 | enveloped_tx: Some(Bytes::default()), 156 | deposit: Default::default(), 157 | }; 158 | 159 | let mut gas_limit = tx.base.gas_limit; 160 | let mut basefee = 0; 161 | let mut disable_nonce_check = true; 162 | 163 | // ensure the block gas limit is >= the tx 164 | core::mem::swap(&mut self.block.gas_limit, &mut gas_limit); 165 | // disable the base fee check for this call by setting the base fee to zero 166 | core::mem::swap(&mut self.block.basefee, &mut basefee); 167 | // disable the nonce check 168 | core::mem::swap(&mut self.cfg.disable_nonce_check, &mut disable_nonce_check); 169 | 170 | let mut res = self.transact(tx); 171 | 172 | // swap back to the previous gas limit 173 | core::mem::swap(&mut self.block.gas_limit, &mut gas_limit); 174 | // swap back to the previous base fee 175 | core::mem::swap(&mut self.block.basefee, &mut basefee); 176 | // swap back to the previous nonce check flag 177 | core::mem::swap(&mut self.cfg.disable_nonce_check, &mut disable_nonce_check); 178 | 179 | // NOTE: We assume that only the contract storage is modified. Revm currently marks the 180 | // caller and block beneficiary accounts as "touched" when we do the above transact calls, 181 | // and includes them in the result. 182 | // 183 | // We're doing this state cleanup to make sure that changeset only includes the changed 184 | // contract storage. 185 | if let Ok(res) = &mut res { 186 | res.state.retain(|addr, _| *addr == contract); 187 | } 188 | 189 | res 190 | } 191 | 192 | fn db_mut(&mut self) -> &mut Self::DB { 193 | &mut self.journaled_state.database 194 | } 195 | 196 | fn finish(self) -> (Self::DB, EvmEnv) { 197 | let Context { block: block_env, cfg: cfg_env, journaled_state, .. } = self.inner.0.ctx; 198 | 199 | (journaled_state.database, EvmEnv { block_env, cfg_env }) 200 | } 201 | 202 | fn set_inspector_enabled(&mut self, enabled: bool) { 203 | self.inspect = enabled; 204 | } 205 | 206 | fn precompiles(&self) -> &Self::Precompiles { 207 | &self.inner.0.precompiles 208 | } 209 | 210 | fn precompiles_mut(&mut self) -> &mut Self::Precompiles { 211 | &mut self.inner.0.precompiles 212 | } 213 | 214 | fn inspector(&self) -> &Self::Inspector { 215 | &self.inner.0.inspector 216 | } 217 | 218 | fn inspector_mut(&mut self) -> &mut Self::Inspector { 219 | &mut self.inner.0.inspector 220 | } 221 | } 222 | 223 | /// Factory producing [`OpEvm`]s. 224 | #[derive(Debug, Default, Clone, Copy)] 225 | #[non_exhaustive] 226 | pub struct OpEvmFactory; 227 | 228 | impl EvmFactory for OpEvmFactory { 229 | type Evm>> = OpEvm; 230 | type Context = OpContext; 231 | type Tx = OpTransaction; 232 | type Error = 233 | EVMError; 234 | type HaltReason = OpHaltReason; 235 | type Spec = OpSpecId; 236 | type Precompiles = PrecompilesMap; 237 | 238 | fn create_evm( 239 | &self, 240 | db: DB, 241 | input: EvmEnv, 242 | ) -> Self::Evm { 243 | let spec_id = input.cfg_env.spec; 244 | OpEvm { 245 | inner: Context::op() 246 | .with_db(db) 247 | .with_block(input.block_env) 248 | .with_cfg(input.cfg_env) 249 | .build_op_with_inspector(NoOpInspector {}) 250 | .with_precompiles(PrecompilesMap::from_static( 251 | OpPrecompiles::new_with_spec(spec_id).precompiles(), 252 | )), 253 | inspect: false, 254 | } 255 | } 256 | 257 | fn create_evm_with_inspector>>( 258 | &self, 259 | db: DB, 260 | input: EvmEnv, 261 | inspector: I, 262 | ) -> Self::Evm { 263 | let spec_id = input.cfg_env.spec; 264 | OpEvm { 265 | inner: Context::op() 266 | .with_db(db) 267 | .with_block(input.block_env) 268 | .with_cfg(input.cfg_env) 269 | .build_op_with_inspector(inspector) 270 | .with_precompiles(PrecompilesMap::from_static( 271 | OpPrecompiles::new_with_spec(spec_id).precompiles(), 272 | )), 273 | inspect: true, 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /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-3-Clause", 25 | "Unicode-3.0", 26 | "Unlicense", 27 | "Zlib", 28 | "CC0-1.0", 29 | ] 30 | 31 | [[licenses.clarify]] 32 | name = "ring" 33 | expression = "LicenseRef-ring" 34 | license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] 35 | 36 | [[licenses.clarify]] 37 | name = "webpki" 38 | expression = "LicenseRef-webpki" 39 | license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] 40 | 41 | [sources] 42 | unknown-registry = "deny" 43 | unknown-git = "deny" 44 | allow-git = [ 45 | "https://github.com/bluealloy/revm", 46 | "https://github.com/alloy-rs/hardforks", 47 | ] 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------