Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: init4tech/trevm
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.17.0
Choose a base ref
...
head repository: init4tech/trevm
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Jan 7, 2025

  1. v0.18.0 (#72)

    Evalir authored Jan 7, 2025
    Copy the full SHA
    c4cc25a View commit details

Commits on Jan 8, 2025

  1. chore: move Block impl for ZenithCallBundle to trevm (#73)

    As we moved out these types to zenith-types, we need to move this type impl here.
    
    Closes ENG-728
    Evalir authored Jan 8, 2025
    Copy the full SHA
    f771d1d View commit details

Commits on Jan 27, 2025

  1. fix: small docs improvements (#74)

    * fix: small docs improvements
    
    * fix: grammar
    anna-carroll authored Jan 27, 2025
    Copy the full SHA
    e86f6c2 View commit details
  2. v0.19.0 - revm 0.19.0

    prestwich committed Jan 27, 2025
    Copy the full SHA
    7b63461 View commit details

Commits on Jan 31, 2025

  1. feat: impl tx for transactionrequest (#75)

    * feat: impl tx for transactionrequest
    
    * feat: block overrides
    
    * feat: apply block and state overrides
    
    * chore: bump version
    prestwich authored Jan 31, 2025
    Copy the full SHA
    f5f7fcc View commit details
  2. Copy the full SHA
    a29c7f9 View commit details

Commits on Feb 4, 2025

  1. feat: estimation (#77)

    * feat: estimation
    
    * fix: use a max
    
    * feat: shortcut for estimate_tx_gas
    
    * fix: callee_account
    
    * fix: callee_account
    
    * nit: gas allowance
    
    * lint: clippy
    
    * doc: fixlink
    
    * nit: remove
    
    * chore: doc algorithm
    
    * refactor: clean up and add a macro
    
    * nit: search
    
    * refactor: search range
    
    * docs: add one
    
    * refactor: more cleaning
    
    * nit: add more comments
    
    * feat: blanket impl Block, Tx, Cfg for Fn() types
    
    * chore: bump version
    
    * refactor: use maybeuninit to save a tiny amount of resources
    
    * nits: clean up some naming
    prestwich authored Feb 4, 2025
    Copy the full SHA
    74cb161 View commit details

Commits on Feb 5, 2025

  1. Copy the full SHA
    19d8bca View commit details

Commits on Feb 20, 2025

  1. fix: gas estimation for simple transfers (#78)

    * fix: gas estimation for simple transfers
    
    * fix: concurrent state link
    
    * Update evm.rs
    
    Co-authored-by: evalir <e@evalir.xyz>
    
    * Update evm.rs
    
    Co-authored-by: evalir <e@evalir.xyz>
    
    * fix: calu
    
    ---------
    
    Co-authored-by: evalir <e@evalir.xyz>
    prestwich and Evalir authored Feb 20, 2025
    Copy the full SHA
    ea840af View commit details
  2. Always set randao in alloy block filler (#79)

    * fix: always set prevrandao
    
    * chore: bump version and note
    prestwich authored Feb 20, 2025
    Copy the full SHA
    9085282 View commit details

Commits on Feb 23, 2025

  1. Copy the full SHA
    96605c9 View commit details

Commits on Feb 24, 2025

  1. Allowance interface tweaks (#80)

    * feat: more allowance features
    
    * chore: version and readme
    
    * fix: clean up no-default and all-features
    
    * fix: docs
    
    * feat: cap gas on tx
    
    * fix: estimation midpoint
    
    * fix: estimation result
    
    * feat: misc tracing
    prestwich authored Feb 24, 2025
    Copy the full SHA
    591208c View commit details
  2. fix: optional tracing

    prestwich committed Feb 24, 2025
    Copy the full SHA
    57fbf51 View commit details

Commits on Feb 26, 2025

  1. feat(ci): feature checks (#82)

    * feat(ci): feature checks
    
    Closes ENG-833. Adds feature checks to the CI.
    
    * chore: use actions-homed action
    
    * chore: repushing don't mind me
    Evalir authored Feb 26, 2025
    Copy the full SHA
    26459ff View commit details
  2. chore: bump alloy and zenith deps (#83)

    * chore: bump alloy and zenith deps
    
    * fix: in test too
    prestwich authored Feb 26, 2025
    Copy the full SHA
    4b8add3 View commit details

Commits on Feb 27, 2025

  1. Trim commit bounds (#84)

    * refactor: remove DatabaseCommit bounds wherever possible
    
    * nit: import
    prestwich authored Feb 27, 2025
    Copy the full SHA
    f83eed9 View commit details
  2. feat: traits for things that behave like State (#85)

    * feat: traits for things that behave like State
    
    * chore: version
    
    * doc: add note
    prestwich authored Feb 27, 2025
    Copy the full SHA
    21f33a7 View commit details

Commits on Mar 4, 2025

  1. Copy the full SHA
    ad3965b View commit details
  2. feat: child db for concurrent state (#87)

    * fix: restore commit bounds to drivers
    
    * feat: child db for concurrent state
    
    * tests: write em
    
    * test: even more stuff
    
    * chore: bump version
    
    * chore: lints and doclinks
    prestwich authored Mar 4, 2025
    Copy the full SHA
    121563a View commit details
  3. fix: Unswap block overrides methods (#88)

    * fix: why were these swapped?
    
    * chore: bump version
    prestwich authored Mar 4, 2025
    Copy the full SHA
    747c725 View commit details

Commits on Mar 11, 2025

  1. Copy the full SHA
    905b947 View commit details

Commits on Mar 17, 2025

  1. Copy the full SHA
    2b4e88e View commit details

Commits on Mar 18, 2025

  1. refactor: trevm_try (#92)

    prestwich authored Mar 18, 2025
    Copy the full SHA
    b58a5b4 View commit details

Commits on Mar 27, 2025

  1. Dep: update revm to 20 (#94)

    * wip
    
    * wip
    
    * wip
    
    * fixing
    
    * chore: fix doctests
    
    * fix: doclinks
    
    * lint: clippy
    
    * chore: depend on stable
    
    * nit:
    
    * docs: slight cleanup
    
    * chore: update example
    
    * chore: fix examples
    
    * nit: remove note
    
    * chore: clean up syscalls
    
    * docs: slight cleanup
    
    * Update src/builder.rs
    
    Co-authored-by: evalir <e@evalir.xyz>
    
    ---------
    
    Co-authored-by: evalir <e@evalir.xyz>
    prestwich and Evalir authored Mar 27, 2025
    Copy the full SHA
    11f6ab9 View commit details
  2. fix: remove wildcard

    prestwich committed Mar 27, 2025
    Copy the full SHA
    6928dcb View commit details
  3. feat: cow (#93)

    * feat: cow
    
    * lint: clippy
    
    * chore: update for revm@20
    
    * fix: doclinks
    
    * fix: copyright notice
    prestwich authored Mar 27, 2025
    Copy the full SHA
    247e156 View commit details
  4. fix: remove dbgs

    prestwich committed Mar 27, 2025
    Copy the full SHA
    67050c7 View commit details
  5. Set chain id in simulation fillers (#95)

    * fix: set chain id in gas estimation and call
    
    * fix: import
    
    * chore: version
    prestwich authored Mar 27, 2025
    Copy the full SHA
    f82e063 View commit details
  6. feat: disable nonce checks

    prestwich committed Mar 27, 2025
    Copy the full SHA
    3366506 View commit details
  7. fix: add inspector bounds and always inspect (#96)

    * fix: add inspector bounds and always inspect
    
    * chore: version bump
    
    * fix: doclink
    prestwich authored Mar 27, 2025
    Copy the full SHA
    9a79e24 View commit details

Commits on Apr 3, 2025

  1. feature: utility inspectors (#98)

    * feat: a couple utility inspectors
    
    * feat: inspector stack
    
    * lint: clippy
    
    * feat: stack vs layered
    
    * fix: allow instantiating layered
    
    * fix: trace syscalls and change fail values
    
    * lint: clippy
    
    * chore: better docs
    prestwich authored Apr 3, 2025
    Copy the full SHA
    293eaef View commit details

Commits on Apr 4, 2025

  1. Copy the full SHA
    f7252a1 View commit details
  2. fix: db in driver traits again (#100)

    * fix: db in driver traits again
    
    * chore: clippy
    
    * chore: bump version to 0.20.6
    prestwich authored Apr 4, 2025
    Copy the full SHA
    15aff7a View commit details

Commits on Apr 6, 2025

  1. chore: fix trevm aliases to use noop by default (#101)

    * chore: fix trevm aliases to use noop by default
    
    * fix: default to noop in driver traits
    
    * refactor: reorder generics in aliases
    prestwich authored Apr 6, 2025
    Copy the full SHA
    e1a5fa7 View commit details

Commits on Apr 10, 2025

  1. feat: cachingdb traits (#102)

    * feat: cachingdb traits
    
    * chore: bump version
    
    * lint: clippy
    prestwich authored Apr 10, 2025
    Copy the full SHA
    7840c2a View commit details
  2. feat: extend and extend_ref (#103)

    * feat: extend and extend_ref
    
    * lint: clippy
    
    * lint: fmt
    prestwich authored Apr 10, 2025
    Copy the full SHA
    5fcdf50 View commit details

Commits on Apr 14, 2025

  1. Copy the full SHA
    3dba391 View commit details

Commits on May 14, 2025

  1. Adds filler impls for the things that are filled (#105)

    * feat: impl traits for envs
    
    * chore: version
    prestwich authored May 14, 2025
    Copy the full SHA
    0f21f79 View commit details

Commits on May 22, 2025

  1. chore(deps): upgrade to revm23/alloy1.0.5 (#104)

    * chore(deps): upgrade to revm22/alloy0.14
    
    * chore: alloy1.0.5/revm23 fixes
    
    * chore: more fixes
    
    * chore: keep optional validation
    
    * chore: nicer auth list wrangling
    
    * fix: proper selector parsing
    
    * chore: no underscore
    
    * chore: validate on example
    
    * chore: keep selector as option
    
    * feat: version bump
    
    * chore: vendor alloydb, fix example
    
    * chore: clippy
    
    * chore: docs
    
    * chore: improve code style
    
    * chore: backticks
    
    * chore: more docs
    
    * chore: do not use SAFETY disclaimers, as its not unsafe code
    Evalir authored May 22, 2025
    Copy the full SHA
    873b506 View commit details
  2. feat: disable prevrandao (#106)

    * feat: disable prevrandao
    
    * doc: more of em
    
    * refactor: generalize
    
    * fix: forbid handler
    
    * feat: set precompiles
    
    * Update src/helpers.rs
    
    Co-authored-by: evalir <e@evalir.xyz>
    
    ---------
    
    Co-authored-by: evalir <e@evalir.xyz>
    prestwich and Evalir authored May 22, 2025
    Copy the full SHA
    2e1ead2 View commit details
  3. chore: version bump

    prestwich committed May 22, 2025
    Copy the full SHA
    a086074 View commit details
  4. feat: add precompiles to builder (#107)

    * feat: add precompiles to builder
    
    * doc: more doc
    
    * chore: version
    prestwich authored May 22, 2025
    Copy the full SHA
    91e5f4f View commit details

Commits on May 28, 2025

  1. refactor: improve timeout error by not hijacking stack too deep (#108)

    * refactor: improve timeout error by not hijacking stack too deep
    
    * fix: error message
    
    * chore: version bump
    
    * fix: docs
    
    * fix: test
    prestwich authored May 28, 2025
    Copy the full SHA
    a74ef27 View commit details
  2. Copy the full SHA
    6e2d337 View commit details

Commits on Jun 11, 2025

  1. Copy the full SHA
    6be8a0a View commit details
  2. Copy the full SHA
    f9f5dd1 View commit details

Commits on Jun 18, 2025

  1. fix: estimation min set to gas_used - 1 rather than to estimate - 1 (#…

    …112)
    
    * fix: estimation min set to gas_used - 1 rather than to estimate - 1
    
    * chore: version bump
    
    * fix: rename to limit
    prestwich authored Jun 18, 2025
    Copy the full SHA
    fc58b46 View commit details

Commits on Jun 19, 2025

  1. fix: gas estimation does not return revert when a previous iteration …

    …succeeded (#113)
    
    * fix: gas estimation does not return revert when a previous iteration succeeded
    
    * chore: bump version
    prestwich authored Jun 19, 2025
    Copy the full SHA
    a32d151 View commit details

Commits on Jul 1, 2025

  1. feat(deps): bump revm (#114)

    Bumps to 27.0.1, with the necessary version bump
    Evalir authored Jul 1, 2025
    Copy the full SHA
    11c5d4a View commit details

Commits on Aug 12, 2025

  1. feat: more journal (#115)

    * feat: more journal
    
    * lint: clippy
    
    * fix: type inference
    prestwich authored Aug 12, 2025
    Copy the full SHA
    1da3f30 View commit details
Showing with 5,099 additions and 1,417 deletions.
  1. +2 −3 .github/workflows/rust-ci.yml
  2. +41 −0 CONTRIBUTING.md
  3. +38 −24 Cargo.toml
  4. +15 −0 README.md
  5. +19 −14 examples/basic_transact.rs
  6. +17 −20 examples/{fork_ref_transact.rs.bak → fork_ref_transact.rs}
  7. +91 −0 src/builder.rs
  8. +30 −29 src/connect.rs
  9. +154 −0 src/db/alloy.rs
  10. +289 −0 src/db/cow/mod.rs
  11. +12 −40 src/db/mod.rs
  12. +4 −4 src/db/{ → sync}/builder.rs
  13. +20 −6 src/db/{cache_state.rs → sync/cache.rs}
  14. +1 −0 src/db/sync/child.rs
  15. +25 −0 src/db/sync/error.rs
  16. +37 −0 src/db/sync/mod.rs
  17. +229 −104 src/db/{sync_state.rs → sync/state.rs}
  18. +272 −0 src/db/traits.rs
  19. +124 −114 src/driver/alloy.rs
  20. +16 −17 src/driver/block.rs
  21. +14 −14 src/driver/bundle.rs
  22. +17 −13 src/driver/chain.rs
  23. +298 −0 src/est.rs
  24. +1,413 −271 src/evm.rs
  25. +13 −8 src/ext.rs
  26. +234 −116 src/fill/alloy.rs
  27. +125 −0 src/fill/fillers.rs
  28. +4 −56 src/fill/mod.rs
  29. +1 −1 src/fill/noop.rs
  30. +133 −21 src/fill/traits.rs
  31. +0 −81 src/fill/zenith.rs
  32. +27 −0 src/helpers.rs
  33. +129 −0 src/inspectors/layer.rs
  34. +48 −0 src/inspectors/mod.rs
  35. +130 −0 src/inspectors/set.rs
  36. +207 −0 src/inspectors/spanning.rs
  37. +110 −0 src/inspectors/timeout.rs
  38. +70 −0 src/inspectors/tracing.rs
  39. +49 −0 src/inspectors/with_output.rs
  40. +50 −84 src/journal/coder.rs
  41. +13 −9 src/journal/index.rs
  42. +8 −5 src/journal/mod.rs
  43. +97 −0 src/journal/update.rs
  44. +76 −74 src/lib.rs
  45. +8 −4 src/lifecycle/output.rs
  46. +1 −1 src/lifecycle/postflight.rs
  47. +3 −3 src/lifecycle/receipt.rs
  48. +37 −0 src/macros.rs
  49. +36 −133 src/states.rs
  50. +29 −19 src/system/eip2935.rs
  51. +16 −10 src/system/eip4788.rs
  52. +16 −11 src/system/eip4895.rs
  53. +13 −9 src/system/eip6110.rs
  54. +17 −14 src/system/eip7002.rs
  55. +19 −15 src/system/eip7251.rs
  56. +20 −17 src/system/fill.rs
  57. +56 −23 src/system/mod.rs
  58. +126 −30 src/test_utils.rs
5 changes: 2 additions & 3 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
@@ -5,10 +5,9 @@ on:
push:
branches: [main]
pull_request:

env:
CARGO_TERM_COLOR: always

jobs:
rust-base:
uses: init4tech/actions/.github/workflows/rust-base.yml@main
rust-library-base:
uses: init4tech/actions/.github/workflows/rust-library-base.yml@main
41 changes: 41 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Contributing to Trevm

:balloon: Thanks for your help improving the project! We are so happy to have
you!

## Conduct

This repo adheres to the [Rust Code of Conduct][coc]. This describes
the _minimum_ behavior expected from all contributors. Failure to maintain civil
behavior will result in a ban.

[coc]: https://www.rust-lang.org/policies/code-of-conduct

## Pull Requests

Before making a large change, it is usually a good idea to first open an issue
describing the change to solicit feedback and guidance. This will increase the
likelihood of the PR getting merged.

When opening a PR **please select the "Allow Edits From Maintainers" option**.
We maintain code quality and style standards, and require commit signing. This
option allows us to make small changes to your PR to bring it in line with
these standards. It helps us get your PR in faster, and with less work from you.

## Development Basics

Before submitting a PR we recommend you run the following commands to ensure
your code is properly formatted and passes all tests:

```sh
cargo +nightly fmt --all
cargo clippy --all-features
cargo test --all-features
cargo test --no-default-features
```

### Contributions Related to Spelling, Grammar, and other trivial changes

At this time, we will not be accepting contributions that only fix spelling or
grammatical errors in documentation, code or
elsewhere.
62 changes: 38 additions & 24 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "trevm"
version = "0.17.0"
rust-version = "1.82.0"
version = "0.27.8"
rust-version = "1.83.0"
edition = "2021"
authors = ["init4"]
homepage = "https://github.com/init4tech/trevm"
@@ -26,28 +26,38 @@ use-self = "warn"
option-if-let-else = "warn"
redundant-clone = "warn"

[dependencies]
alloy-rlp = { version = "0.3.10", default-features = false, features = ["std"]}

alloy-primitives = { version = "0.8.11", default-features = false, features = ["std"]}
alloy-sol-types = { version = "0.8.11", default-features = false, features = ["std"]}
[[example]]
name = "basic_transact"

alloy = { version = "=0.7.3", default-features = false, features = ["consensus", "rpc-types-mev", "eips", "k256", "std"] }
[[example]]
name = "fork_ref_transact"
required-features = ["alloy-db"]

revm = { version = "18.0.0", default-features = false, features = ["std"] }
[dependencies]
alloy = { version = "1.0.25", default-features = false, features = [
"consensus",
"rpc-types-mev",
"eips",
"k256",
"std",
"rlp",
"sol-types",
] }

zenith-types = { version = "0.11" }
revm = { version = "27.1", default-features = false }
revm-inspectors = { version = "0.27.1", optional = true }

dashmap = { version = "6.1.0", optional = true }
tracing = { version = "0.1.41", optional = true }
thiserror = "2.0.11"

tokio = { version = "1.44", optional = true }

[dev-dependencies]
alloy-rlp = { version = "0.3", default-features = false }
revm = { version = "18.0.0", features = [
"test-utils",
"serde-json",
"std",
"alloydb",
] }
revm = { version = "27.0.1", features = ["serde-json", "std", "alloydb"] }
trevm = { path = ".", features = ["test-utils"] }

alloy = { version = "1.0.13", features = ["providers", "transports"] }

# misc
eyre = "0.6"
@@ -57,17 +67,26 @@ tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] }

[features]
default = [
"call",
"concurrent-db",
"estimate_gas",
"tracing-inspectors",
"revm/std",
"revm/c-kzg",
"revm/blst",
"revm/portable",
"revm/secp256k1",
]

alloy-db = ["dep:tokio", "alloy/providers"]

call = ["optional_eip3607", "optional_no_base_fee"]

concurrent-db = ["dep:dashmap"]

test-utils = ["revm/test-utils", "revm/std", "revm/serde-json", "revm/alloydb"]
estimate_gas = ["optional_eip3607", "optional_no_base_fee", "dep:tracing"]

test-utils = ["revm/std", "revm/serde-json", "revm/alloydb"]

secp256k1 = ["revm/secp256k1"]
c-kzg = ["revm/c-kzg"]
@@ -80,23 +99,18 @@ dev = [
"optional_balance_check",
"optional_block_gas_limit",
"optional_eip3607",
"optional_gas_refund",
"optional_no_base_fee",
"optional_beneficiary_reward",
]

memory_limit = ["revm/memory_limit"]
optional_balance_check = ["revm/optional_balance_check"]
optional_beneficiary_reward = ["revm/optional_beneficiary_reward"]
optional_block_gas_limit = ["revm/optional_block_gas_limit"]
optional_eip3607 = ["revm/optional_eip3607"]
optional_gas_refund = ["revm/optional_gas_refund"]
optional_no_base_fee = ["revm/optional_no_base_fee"]
full_env_cfg = [
"optional_balance_check",
"optional_block_gas_limit",
"optional_eip3607",
"optional_gas_refund",
"optional_no_base_fee",
"optional_beneficiary_reward",
]
tracing-inspectors = ["dep:revm-inspectors", "alloy/rpc-types-trace"]
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -28,6 +28,21 @@ Trevm is useful for:
- searchers
- any other transaction simulation usecase

## Note on Trevm Versioning

Trevm generally uses [semantic versioning](https://semver.org/). While pre-1.0,
we also strive to indicate the MAJOR version of revm in the MINOR version of
trevm. For example, trevm `0.19.x` SHOULD BE compatible with revm `19.x.x`. In
general, we will try to maintain compatibility with the latest revm version,
and will not backport trevm fixes to older trevm or revm versions. It is
generally not advised to use old revm versions, as the EVM is a living spec.

In order to maintain this relationship (that trevm MINOR == revm MAJOR) we will
sometimes make breaking changes in patch versions. This is technically semver
compliant pre-1.0, but will cause build breakage downstream for users of those
features. We will take care to document breaking changes in patch releases
via github release notes.

## Limitations

Trevm is a work in progress and is not feature complete. In particular, trevm
33 changes: 19 additions & 14 deletions examples/basic_transact.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! Simple TREVM example that demonstrates how to execute a transaction on a contract.
//! It simply loads the contract bytecode and executes a transaction.
use revm::context::TransactTo;
use trevm::{
revm::{
inspector_handle_register,
inspectors::TracerEip3155,
primitives::{hex, AccountInfo, Address, Bytecode, TransactTo, U256},
EvmBuilder, InMemoryDB,
bytecode::Bytecode,
database::InMemoryDB,
inspector::inspectors::TracerEip3155,
primitives::{hex, Address, U256},
state::AccountInfo,
},
trevm_aliases, NoopBlock, NoopCfg, TrevmBuilder, Tx,
};
@@ -27,18 +29,18 @@ const CALLER_ADDR: Address = Address::with_last_byte(1);
struct SampleTx;

impl Tx for SampleTx {
fn fill_tx_env(&self, tx_env: &mut revm::primitives::TxEnv) {
fn fill_tx_env(&self, tx_env: &mut revm::context::TxEnv) {
tx_env.caller = CALLER_ADDR;
tx_env.transact_to = TransactTo::Call(CONTRACT_ADDR);
tx_env.kind = TransactTo::Call(CONTRACT_ADDR);
tx_env.data = hex::decode(PROGRAM_INPUT).unwrap().into();
}
}

// Produce aliases for the Trevm type
trevm_aliases!(TracerEip3155, InMemoryDB);

fn main() {
let mut db = revm::InMemoryDB::default();
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut db = revm::database::InMemoryDB::default();

let bytecode = Bytecode::new_raw(hex::decode(CONTRACT_BYTECODE).unwrap().into());
let acc_info = AccountInfo::new(U256::ZERO, 1, bytecode.hash_slow(), bytecode);
@@ -47,18 +49,19 @@ fn main() {
db.insert_contract(&mut acc_info.clone());
db.insert_account_info(CONTRACT_ADDR, acc_info);

let evm = EvmBuilder::default()
let insp = TracerEip3155::new(Box::new(std::io::stdout()));

let trevm = TrevmBuilder::new()
.with_db(db)
.with_external_context(TracerEip3155::new(Box::new(std::io::stdout())))
.append_handler_register(inspector_handle_register)
.build_trevm()
.with_insp(insp)
.build_trevm()?
.fill_cfg(&NoopCfg)
.fill_block(&NoopBlock);

let account = evm.read_account_ref(CONTRACT_ADDR).unwrap();
let account = trevm.read_account_ref(CONTRACT_ADDR).unwrap();
println!("account: {account:?}");

let evm = evm.fill_tx(&SampleTx).run();
let evm = trevm.fill_tx(&SampleTx).run();

match evm {
Ok(res) => {
@@ -69,4 +72,6 @@ fn main() {
println!("Execution error: {e:?}");
}
};

Ok(())
}
37 changes: 17 additions & 20 deletions examples/fork_ref_transact.rs.bak → examples/fork_ref_transact.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
//! This example demonstrates how to query storage slots of a contract, using
//! [`AlloyDB`].

//! This example is currently disabled while waiting for revm @ 14.0.4

use alloy::{eips::BlockId, providers::ProviderBuilder};
use alloy_primitives::{address, Address, TxKind, U256};
use alloy_sol_types::{sol, SolCall};
use trevm::{
revm::{
db::{AlloyDB, CacheDB},
Evm,
},
NoopBlock, NoopCfg, TrevmBuilder, Tx,
//! [`AlloyDb`].
use alloy::{
eips::BlockId,
primitives::{address, Address, TxKind, U256},
providers::ProviderBuilder,
sol,
sol_types::SolCall,
};
use revm::{context::TxEnv, database::WrapDatabaseAsync};
use trevm::{db::alloy::AlloyDb, revm::database::CacheDB, NoopBlock, NoopCfg, TrevmBuilder, Tx};

sol! {
#[allow(missing_docs)]
@@ -22,10 +19,10 @@ sol! {
struct GetReservesFiller;

impl Tx for GetReservesFiller {
fn fill_tx_env(&self, tx_env: &mut revm::primitives::TxEnv) {
fn fill_tx_env(&self, tx_env: &mut TxEnv) {
tx_env.caller = Address::with_last_byte(0);
// ETH/USDT pair on Uniswap V2
tx_env.transact_to = TxKind::Call(POOL_ADDRESS);
tx_env.kind = TxKind::Call(POOL_ADDRESS);
// calldata formed via alloy's abi encoder
tx_env.data = getReservesCall::new(()).abi_encode().into();
// transaction value in wei
@@ -40,7 +37,7 @@ async fn main() -> eyre::Result<()> {
// create ethers client and wrap it in Arc<M>
let rpc_url = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";

let client = ProviderBuilder::new().on_http(rpc_url.parse()?);
let client = ProviderBuilder::new().connect_http(rpc_url.parse()?);

// ----------------------------------------------------------- //
// Storage slots of UniV2Pair contract //
@@ -55,15 +52,15 @@ async fn main() -> eyre::Result<()> {
// =========================================================== //

// initialize new AlloyDB
let alloydb = AlloyDB::new(client, BlockId::default()).unwrap();
let alloydb = WrapDatabaseAsync::new(AlloyDb::new(client, BlockId::default())).unwrap();

// initialise empty in-memory-db
let cache_db = CacheDB::new(alloydb);

// initialise an empty (default) EVM
let evm = Evm::builder()
let evm = TrevmBuilder::new()
.with_db(cache_db)
.build_trevm()
.build_trevm()?
.fill_cfg(&NoopCfg)
.fill_block(&NoopBlock)
.fill_tx(&GetReservesFiller)
@@ -76,7 +73,7 @@ async fn main() -> eyre::Result<()> {
let output = evm.output().expect("Execution halted");

// decode bytes to reserves + ts via alloy's abi decode
let return_vals = getReservesCall::abi_decode_returns(output, true)?;
let return_vals = getReservesCall::abi_decode_returns_validate(output)?;

// Print emulated getReserves() call output
println!("Reserve0: {:#?}", return_vals.reserve0);
91 changes: 91 additions & 0 deletions src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::{evm::Trevm, helpers::Ctx, states::EvmNeedsCfg};
use revm::{
database::in_memory_db::InMemoryDB, handler::EthPrecompiles, inspector::NoOpInspector,
precompile::Precompiles, primitives::hardfork::SpecId, Database, Inspector, MainBuilder,
};

/// Error that can occur when building a Trevm instance.
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum TrevmBuilderError {
/// Database not set.
#[error("Database not set")]
DatabaseNotSet,
}

/// A builder for [`Trevm`] that allows configuring the EVM.
#[derive(Debug, Clone)]
pub struct TrevmBuilder<Db, Insp> {
pub(crate) db: Option<Db>,
pub(crate) insp: Insp,
pub(crate) spec: SpecId,
pub(crate) precompiles: Option<&'static Precompiles>,
}

impl TrevmBuilder<InMemoryDB, NoOpInspector> {
/// Create a new builder with the default database and inspector.
#[allow(clippy::new_without_default)] // default would make bad devex :(
pub const fn new() -> Self {
Self { db: None, insp: NoOpInspector, spec: SpecId::PRAGUE, precompiles: None }
}
}

impl<Db, Insp> TrevmBuilder<Db, Insp> {
/// Set the database for the EVM.
pub fn with_db<Odb>(self, db: Odb) -> TrevmBuilder<Odb, Insp>
where
Db: Database,
{
TrevmBuilder {
db: Some(db),
insp: self.insp,
spec: self.spec,
precompiles: self.precompiles,
}
}

/// Set the inspector for the EVM.
pub fn with_insp<OInsp>(self, insp: OInsp) -> TrevmBuilder<Db, OInsp> {
TrevmBuilder { db: self.db, insp, spec: self.spec, precompiles: self.precompiles }
}

/// Set the spec id for the EVM.
pub const fn with_spec_id(mut self, spec: SpecId) -> Self {
self.spec = spec;
self
}

/// Set the precompiles for the EVM.
///
/// The precompiles must be a static reference to a precompiles instance.
/// If not using a built-in [`Precompiles`], it is generally recommended to
/// use a `OnceLock` to create this borrow.
pub const fn with_precompiles(mut self, precompiles: &'static Precompiles) -> Self {
self.precompiles = Some(precompiles);
self
}

/// Set the precompiles for the EVM from the current spec id.
pub fn with_precompiles_from_spec(mut self) -> Self {
self.precompiles = Some(Precompiles::new(self.spec.into()));
self
}

/// Build the Trevm instance.
pub fn build_trevm(self) -> Result<EvmNeedsCfg<Db, Insp>, TrevmBuilderError>
where
Db: Database,
Insp: Inspector<Ctx<Db>>,
{
let db = self.db.ok_or(TrevmBuilderError::DatabaseNotSet)?;
let ctx = Ctx::new(db, self.spec);

let mut evm = ctx.build_mainnet_with_inspector(self.insp);

if let Some(precompiles) = self.precompiles {
evm.precompiles = EthPrecompiles { precompiles, spec: self.spec };
}

Ok(Trevm::from(evm))
}
}
59 changes: 30 additions & 29 deletions src/connect.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::{
helpers::Ctx, Block, Cfg, EvmErrored, EvmNeedsBlock, EvmNeedsCfg, EvmNeedsTx, EvmReady,
EvmTransacted, Tx,
};
use core::convert::Infallible;
use revm::{
primitives::{EVMError, ResultAndState},
Database, DatabaseCommit,
context::result::{EVMError, ResultAndState},
Database, Inspector,
};
use std::format;

use crate::{
Block, Cfg, EvmErrored, EvmNeedsBlock, EvmNeedsCfg, EvmNeedsTx, EvmReady, EvmTransacted, Tx,
};

/// Trait for types that can be used to connect to a database.
///
/// Connectors should contain configuration information like filesystem paths.
@@ -19,20 +19,20 @@ use crate::{
/// connector. E.g. the connector may contain some `Db` and the resulting Db may
/// contain `&Db`. This allows for (e.g.) shared caches between DBs on multiple
/// threads.
pub trait DbConnect<'a>: Sync {
pub trait DbConnect: Sync {
/// The database type returned when connecting.
type Database: Database + DatabaseCommit;
type Database: Database;

/// The error type returned when connecting to the database.
type Error: core::error::Error;

/// Connect to the database.
fn connect(&'a self) -> Result<Self::Database, Self::Error>;
fn connect(&self) -> Result<Self::Database, Self::Error>;
}

impl<Db> DbConnect<'_> for Db
impl<Db> DbConnect for Db
where
Db: Database + DatabaseCommit + Clone + Sync,
Db: Database + Clone + Sync,
{
type Database = Self;

@@ -45,28 +45,32 @@ where

/// Trait for types that can create EVM instances.
///
/// Factories should contain configuration information like chain `EXT` types,
/// and database connections. They are intended to enable parallel instantiation
/// Factories should contain configuration information like `Insp` types, and
/// database connections. They are intended to enable parallel instantiation
/// of multiple EVMs in multiple threads sharing some configuration or backing
/// store.
///
/// The lifetime on this trait allows the resulting EVM to borrow from the
/// connector. E.g. the connector may contain some `Db` and the resulting EVM
/// may contain `&Db`. This allows for (e.g.) shared caches between EVMs on
/// multiple threads.
pub trait EvmFactory<'a>: DbConnect<'a> {
/// The `Ext` type used in the resulting EVM.
type Ext: Sync;
pub trait EvmFactory: DbConnect {
/// The `Insp` type used in the resulting EVM.
///
/// Recommend using [`NoOpInspector`] for most use cases.
///
/// [`NoOpInspector`]: revm::inspector::NoOpInspector
type Insp: Sync + Inspector<Ctx<Self::Database>>;

/// Create a new EVM instance with the given database connection and
/// extension.
fn create(&'a self) -> Result<EvmNeedsCfg<'a, Self::Ext, Self::Database>, Self::Error>;
fn create(&self) -> Result<EvmNeedsCfg<Self::Database, Self::Insp>, Self::Error>;

/// Create a new EVM instance and parameterize it with a [`Cfg`].
fn create_with_cfg<C>(
&'a self,
&self,
cfg: &C,
) -> Result<EvmNeedsBlock<'a, Self::Ext, Self::Database>, Self::Error>
) -> Result<EvmNeedsBlock<Self::Database, Self::Insp>, Self::Error>
where
C: Cfg,
{
@@ -76,10 +80,10 @@ pub trait EvmFactory<'a>: DbConnect<'a> {
/// Create a new EVM instance and parameterize it with a [`Cfg`] and a
/// [`Block`].
fn create_with_block<C, B>(
&'a self,
&self,
cfg: &C,
block: &B,
) -> Result<EvmNeedsTx<'a, Self::Ext, Self::Database>, Self::Error>
) -> Result<EvmNeedsTx<Self::Database, Self::Insp>, Self::Error>
where
C: Cfg,
B: Block,
@@ -90,11 +94,11 @@ pub trait EvmFactory<'a>: DbConnect<'a> {
/// Create a new EVM instance, and parameterize it with a [`Cfg`], a
/// [`Block`], and a [`Tx`], yielding an [`EvmReady`].
fn create_with_tx<C, B, T>(
&'a self,
&self,
cfg: &C,
block: &B,
tx: &T,
) -> Result<EvmReady<'a, Self::Ext, Self::Database>, Self::Error>
) -> Result<EvmReady<Self::Database, Self::Insp>, Self::Error>
where
C: Cfg,
B: Block,
@@ -108,15 +112,12 @@ pub trait EvmFactory<'a>: DbConnect<'a> {
/// [`EvmTransacted`] or [`EvmErrored`].
#[allow(clippy::type_complexity)]
fn transact<C, B, T>(
&'a self,
&self,
cfg: &C,
block: &B,
tx: &T,
) -> Result<
Result<
EvmTransacted<'a, Self::Ext, Self::Database>,
EvmErrored<'a, Self::Ext, Self::Database>,
>,
Result<EvmTransacted<Self::Database, Self::Insp>, EvmErrored<Self::Database, Self::Insp>>,
Self::Error,
>
where
@@ -131,7 +132,7 @@ pub trait EvmFactory<'a>: DbConnect<'a> {
/// Run a transaction, take the [`ResultAndState`], and discard the Evm.
/// This is a high-level shortcut function.
fn run<C, B, T>(
&'a self,
&self,
cfg: &C,
block: &B,
tx: &T,
154 changes: 154 additions & 0 deletions src/db/alloy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use alloy::{
eips::BlockId,
primitives::{StorageValue, U256},
providers::{
network::{primitives::HeaderResponse, BlockResponse},
Network, Provider,
},
transports::TransportError,
};
use core::error::Error;
use revm::{
database_interface::{async_db::DatabaseAsyncRef, DBErrorMarker},
primitives::{Address, B256},
state::{AccountInfo, Bytecode},
};
use std::fmt::Display;

/// A type alias for the storage key used in the database.
/// We use this instead of alloy's [`alloy::primitives::StorageKey`] as Revm requires
/// the actual type to be an [`U256`] instead of a [`B256`].
pub type StorageKey = U256;

/// An error that can occur when using [`AlloyDb`].
#[derive(Debug)]
pub struct DBTransportError(pub TransportError);

impl DBErrorMarker for DBTransportError {}

impl Display for DBTransportError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Transport error: {}", self.0)
}
}

impl Error for DBTransportError {}

impl From<TransportError> for DBTransportError {
fn from(e: TransportError) -> Self {
Self(e)
}
}

/// An alloy-powered REVM [`Database`][revm::database_interface::Database].
///
/// When accessing the database, it'll use the given provider to fetch the corresponding account's data.
#[derive(Debug)]
pub struct AlloyDb<N: Network, P: Provider<N>> {
/// The provider to fetch the data from.
provider: P,
/// The block number on which the queries will be based on.
block_number: BlockId,
_marker: core::marker::PhantomData<fn() -> N>,
}

impl<N: Network, P: Provider<N>> AlloyDb<N, P> {
/// Creates a new AlloyDB instance, with a [`Provider`] and a block.
pub fn new(provider: P, block_number: BlockId) -> Self {
Self { provider, block_number, _marker: core::marker::PhantomData }
}

/// Sets the block number on which the queries will be based on.
pub const fn set_block_number(&mut self, block_number: BlockId) {
self.block_number = block_number;
}
}

impl<N: Network, P: Provider<N>> DatabaseAsyncRef for AlloyDb<N, P> {
type Error = DBTransportError;

async fn basic_async_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
let nonce = self.provider.get_transaction_count(address).block_id(self.block_number);
let balance = self.provider.get_balance(address).block_id(self.block_number);
let code = self.provider.get_code_at(address).block_id(self.block_number);

let (nonce, balance, code) = tokio::join!(nonce, balance, code,);

let balance = balance?;
let code = Bytecode::new_raw(code?.0.into());
let code_hash = code.hash_slow();
let nonce = nonce?;

Ok(Some(AccountInfo::new(balance, nonce, code_hash, code)))
}

async fn block_hash_async_ref(&self, number: u64) -> Result<B256, Self::Error> {
let block = self
.provider
// We know number <= u64::MAX, so we can safely convert it to u64
.get_block_by_number(number.into())
.await?;
// If the number is given, the block is supposed to be finalized, so unwrapping is safe.
Ok(B256::new(*block.unwrap().header().hash()))
}

async fn code_by_hash_async_ref(&self, _code_hash: B256) -> Result<Bytecode, Self::Error> {
panic!("This should not be called, as the code is already loaded");
// This is not needed, as the code is already loaded with basic_ref
}

async fn storage_async_ref(
&self,
address: Address,
index: StorageKey,
) -> Result<StorageValue, Self::Error> {
Ok(self.provider.get_storage_at(address, index).block_id(self.block_number).await?)
}
}

#[cfg(test)]
mod tests {
use super::*;
use alloy::providers::ProviderBuilder;
use revm::database_interface::{DatabaseRef, WrapDatabaseAsync};

#[test]
#[ignore = "flaky RPC"]
fn can_get_basic() {
let client = ProviderBuilder::new().connect_http(
"https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27".parse().unwrap(),
);
let alloydb = AlloyDb::new(client, BlockId::from(16148323));
let wrapped_alloydb = WrapDatabaseAsync::new(alloydb).unwrap();

// ETH/USDT pair on Uniswap V2
let address: Address = "0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852".parse().unwrap();

let acc_info = wrapped_alloydb.basic_ref(address).unwrap().unwrap();
assert!(acc_info.exists());
}
}

// This code has been reproduced from the original AlloyDB implementation
// contained in revm.
// <https://github.com/bluealloy/revm>
// The original license is included below:
//
// MIT License
// Copyright (c) 2021-2025 draganrakita
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
289 changes: 289 additions & 0 deletions src/db/cow/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
use crate::db::CachingDb;
use alloy::{
consensus::constants::KECCAK_EMPTY,
primitives::{Address, B256, U256},
};
use revm::{
bytecode::Bytecode,
database::{in_memory_db::Cache, AccountState, DbAccount},
primitives::HashMap,
state::{Account, AccountInfo},
Database, DatabaseCommit, DatabaseRef,
};

use super::TryCachingDb;

/// A version of [`CacheDB`] that caches only on write, not on read.
///
/// This saves memory when wrapping some other caching database, like [`State`]
/// or [`ConcurrentState`].
///
/// [`CacheDB`]: revm::database::in_memory_db::CacheDB
/// [`State`]: revm::database::State
/// [`ConcurrentState`]: crate::db::sync::ConcurrentState
#[derive(Debug)]
pub struct CacheOnWrite<Db> {
cache: Cache,
inner: Db,
}

impl<Db> Default for CacheOnWrite<Db>
where
Db: Default,
{
fn default() -> Self {
Self::new(Db::default())
}
}

impl<Db> CacheOnWrite<Db> {
/// Create a new `CacheOnWrite` with the given inner database.
pub fn new(inner: Db) -> Self {
Self { cache: Default::default(), inner }
}

/// Create a new `CacheOnWrite` with the given inner database and cache.
pub const fn new_with_cache(inner: Db, cache: Cache) -> Self {
Self { cache, inner }
}

/// Get a reference to the inner database.
pub const fn inner(&self) -> &Db {
&self.inner
}

/// Get a mutable reference to the inner database.
pub const fn inner_mut(&mut self) -> &mut Db {
&mut self.inner
}

/// Get a refernce to the [`Cache`].
pub const fn cache(&self) -> &Cache {
&self.cache
}

/// Get a mutable reference to the [`Cache`].
pub const fn cache_mut(&mut self) -> &mut Cache {
&mut self.cache
}

/// Deconstruct the `CacheOnWrite` into its parts.
pub fn into_parts(self) -> (Db, Cache) {
(self.inner, self.cache)
}

/// Deconstruct the `CacheOnWrite` into its cache, dropping the `Db`.
pub fn into_cache(self) -> Cache {
self.cache
}

/// Nest the `CacheOnWrite` into a double cache.
pub fn nest(self) -> CacheOnWrite<Self> {
CacheOnWrite::new(self)
}

/// Inserts the account's code into the cache.
///
/// Accounts objects and code are stored separately in the cache, this will take the code from the account and instead map it to the code hash.
///
/// Note: This will not insert into the underlying external database.
fn insert_contract(&mut self, account: &mut AccountInfo) {
// Reproduced from
// revm/crates/database/src/in_memory_db.rs
if let Some(code) = &account.code {
if !code.is_empty() {
if account.code_hash == KECCAK_EMPTY {
account.code_hash = code.hash_slow();
}
self.cache.contracts.entry(account.code_hash).or_insert_with(|| code.clone());
}
}
if account.code_hash.is_zero() {
account.code_hash = KECCAK_EMPTY;
}
}
}

impl<Db> CachingDb for CacheOnWrite<Db> {
fn cache(&self) -> &Cache {
&self.cache
}

fn cache_mut(&mut self) -> &mut Cache {
&mut self.cache
}

fn into_cache(self) -> Cache {
self.cache
}
}

impl<Db> CacheOnWrite<Db>
where
Db: CachingDb,
{
/// Flattens a nested cache by applying the outer cache to the inner cache.
///
/// The behavior is as follows:
/// - Accounts are overridden with outer accounts
/// - Contracts are overridden with outer contracts
/// - Block hashes are overridden with outer block hashes
pub fn flatten(self) -> Db {
let Self { cache, mut inner } = self;

inner.extend(cache);
inner
}
}

impl<Db> CacheOnWrite<Db>
where
Db: TryCachingDb,
{
/// Attempts to flatten a nested cache by applying the outer cache to the
/// inner cache. This is a fallible version of [`CacheOnWrite::flatten`].
///
/// The behavior is as follows:
/// - Accounts are overridden with outer accounts
/// - Contracts are overridden with outer contracts
/// - Block hashes are overridden with outer block hashes
pub fn try_flatten(self) -> Result<Db, Db::Error> {
let Self { cache, mut inner } = self;

inner.try_extend(cache)?;
Ok(inner)
}
}

impl<Db: DatabaseRef> Database for CacheOnWrite<Db> {
type Error = Db::Error;

fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
if let Some(account) = self.cache.accounts.get(&address).map(DbAccount::info) {
return Ok(account);
}
self.inner.basic_ref(address)
}

fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
if let Some(code) = self.cache.contracts.get(&code_hash) {
return Ok(code.clone());
}
self.inner.code_by_hash_ref(code_hash)
}

fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
if let Some(storage) =
self.cache.accounts.get(&address).map(|a| a.storage.get(&index).cloned())
{
return Ok(storage.unwrap_or_default());
}
self.inner.storage_ref(address, index)
}

fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
if let Some(hash) = self.cache.block_hashes.get(&U256::from(number)) {
return Ok(*hash);
}
self.inner.block_hash_ref(number)
}
}

impl<Db: DatabaseRef> DatabaseRef for CacheOnWrite<Db> {
type Error = Db::Error;

fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
if let Some(account) = self.cache.accounts.get(&address).map(DbAccount::info) {
return Ok(account);
}
self.inner.basic_ref(address)
}

fn code_by_hash_ref(&self, code_hash: B256) -> Result<Bytecode, Self::Error> {
if let Some(code) = self.cache.contracts.get(&code_hash) {
return Ok(code.clone());
}
self.inner.code_by_hash_ref(code_hash)
}

fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
if let Some(storage) =
self.cache.accounts.get(&address).map(|a| a.storage.get(&index).cloned())
{
return Ok(storage.unwrap_or_default());
}
self.inner.storage_ref(address, index)
}

fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
if let Some(hash) = self.cache.block_hashes.get(&U256::from(number)) {
return Ok(*hash);
}
self.inner.block_hash_ref(number)
}
}

impl<Db> DatabaseCommit for CacheOnWrite<Db> {
fn commit(&mut self, changes: HashMap<Address, Account>) {
// Reproduced from
// revm/crates/database/src/in_memory_db.rs
for (address, mut account) in changes {
if !account.is_touched() {
continue;
}

if account.is_selfdestructed() {
let db_account = self.cache.accounts.entry(address).or_default();
db_account.storage.clear();
db_account.account_state = AccountState::NotExisting;
db_account.info = AccountInfo::default();
continue;
}

let is_newly_created = account.is_created();
self.insert_contract(&mut account.info);

let db_account = self.cache.accounts.entry(address).or_default();
db_account.info = account.info;

db_account.account_state = if is_newly_created {
db_account.storage.clear();
AccountState::StorageCleared
} else if db_account.account_state.is_storage_cleared() {
// Preserve old account state if it already exists
AccountState::StorageCleared
} else {
AccountState::Touched
};

db_account.storage.extend(
account.storage.into_iter().map(|(key, value)| (key, value.present_value())),
);
}
}
}

// Some code above and documentation is adapted from the revm crate, and is
// reproduced here under the terms of the MIT license.
//
// MIT License
//
// Copyright (c) 2021-2024 draganrakita
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
52 changes: 12 additions & 40 deletions src/db/mod.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,15 @@
mod builder;
/// Concurrent version of [`revm::database::State`]
#[cfg(feature = "concurrent-db")]
pub mod sync;

pub use builder::ConcurrentStateBuilder;
/// Database abstraction traits.
mod traits;
pub use traits::{ArcUpgradeError, CachingDb, StateAcc, TryCachingDb, TryStateAcc};

mod cache_state;
pub use cache_state::ConcurrentCacheState;
/// Cache-on-write database. A memory cache that caches only on write, not on
/// read. Intended to wrap some other caching database.
pub mod cow;

mod sync_state;
pub use sync_state::{ConcurrentState, ConcurrentStateInfo};

use crate::{EvmNeedsBlock, Trevm};
use revm::{
db::{states::bundle_state::BundleRetention, BundleState},
DatabaseRef,
};

impl<Ext, Db: DatabaseRef + Sync, TrevmState> Trevm<'_, Ext, ConcurrentState<Db>, TrevmState> {
/// Set the [EIP-161] state clear flag, activated in the Spurious Dragon
/// hardfork.
///
/// This function changes the behavior of the inner [`ConcurrentState`].
pub fn set_state_clear_flag(&mut self, flag: bool) {
self.inner.db_mut().set_state_clear_flag(flag)
}
}

impl<Ext, Db: DatabaseRef + Sync> EvmNeedsBlock<'_, Ext, ConcurrentState<Db>> {
/// Finish execution and return the outputs.
///
/// If the State has not been built with
/// [revm::StateBuilder::with_bundle_update] then the returned
/// [`BundleState`] will be meaningless.
///
/// See [`ConcurrentState::merge_transitions`] and
/// [`ConcurrentState::take_bundle`].
pub fn finish(self) -> BundleState {
let Self { inner: mut evm, .. } = self;
evm.db_mut().merge_transitions(BundleRetention::Reverts);
let bundle = evm.db_mut().take_bundle();

bundle
}
}
#[cfg(feature = "alloy-db")]
/// Alloy-powered revm Database implementation that fetches data over the network.
pub mod alloy;
8 changes: 4 additions & 4 deletions src/db/builder.rs → src/db/sync/builder.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::db::ConcurrentState;
use crate::db::sync::ConcurrentState;
use revm::{
db::{
database::{
states::{BundleState, TransitionState},
EmptyDB,
DatabaseRef, EmptyDB,
},
primitives::{db::DatabaseRef, B256},
primitives::B256,
};
use std::collections::BTreeMap;

26 changes: 20 additions & 6 deletions src/db/cache_state.rs → src/db/sync/cache.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
//! The majority of this code has been reproduced from revm.
use alloy_primitives::{Address, B256};
use alloy::primitives::{Address, B256};
use dashmap::DashMap;
use revm::{
db::states::{plain_account::PlainStorage, CacheAccount},
primitives::{Account, AccountInfo, Bytecode, EvmState},
CacheState, TransitionAccount,
database::{
states::{plain_account::PlainStorage, CacheAccount},
CacheState, TransitionAccount,
},
state::{Account, AccountInfo, Bytecode, EvmState},
};

/// A concurrent version of [`revm::db::CacheState`].
/// A concurrent version of [`revm::database::CacheState`].
///
/// Most of the code for this has been reproduced from revm.
#[derive(Debug, Clone)]
@@ -44,8 +46,20 @@ impl ConcurrentCacheState {
Self { accounts: DashMap::default(), contracts: DashMap::default(), has_state_clear }
}

/// Absorb other into self, overwriting any existing values.
pub fn absorb(&self, other: Self) {
// NB: the `Extend` trait takes self by `&mut self`, so we have inlined
// it here
for pair in other.accounts.into_iter() {
self.accounts.insert(pair.0, pair.1);
}
for pair in other.contracts.into_iter() {
self.contracts.insert(pair.0, pair.1);
}
}

/// Set state clear flag. EIP-161.
pub fn set_state_clear_flag(&mut self, has_state_clear: bool) {
pub const fn set_state_clear_flag(&mut self, has_state_clear: bool) {
self.has_state_clear = has_state_clear;
}

1 change: 1 addition & 0 deletions src/db/sync/child.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

25 changes: 25 additions & 0 deletions src/db/sync/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use crate::db::ArcUpgradeError;

/// Errors that can occur when working with a concurrent state.
#[derive(Debug, thiserror::Error, Clone, Copy, PartialEq, Eq)]
pub enum ConcurrentStateError {
/// Failed to upgrade the arc.
#[error("{0}")]
Arc(#[from] ArcUpgradeError),

/// This DB is not the parent of the child.
#[error("Child belongs to a different parent")]
NotParent,
}

impl ConcurrentStateError {
/// Create a new error for when the DB is not the parent of the child.
pub const fn not_parent() -> Self {
Self::NotParent
}

/// Create a new error for when the arc upgrade fails.
pub const fn not_unique() -> Self {
Self::Arc(ArcUpgradeError::NotUnique)
}
}
37 changes: 37 additions & 0 deletions src/db/sync/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
mod builder;
pub use builder::ConcurrentStateBuilder;

mod cache;
pub use cache::ConcurrentCacheState;

mod error;
pub use error::ConcurrentStateError;

mod state;
pub use state::{Child, ConcurrentState, ConcurrentStateInfo};

use crate::db::StateAcc;
use revm::{
database::{states::bundle_state::BundleRetention, BundleState},
primitives::B256,
DatabaseRef,
};
use std::collections::BTreeMap;

impl<Db: DatabaseRef + Sync> StateAcc for ConcurrentState<Db> {
fn set_state_clear_flag(&mut self, flag: bool) {
Self::set_state_clear_flag(self, flag)
}

fn merge_transitions(&mut self, retention: BundleRetention) {
Self::merge_transitions(self, retention)
}

fn take_bundle(&mut self) -> BundleState {
Self::take_bundle(self)
}

fn set_block_hashes(&mut self, block_hashes: &BTreeMap<u64, B256>) {
self.info.block_hashes.write().unwrap().extend(block_hashes)
}
}
333 changes: 229 additions & 104 deletions src/db/sync_state.rs → src/db/sync/state.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
use crate::db::ConcurrentCacheState;
use alloy_primitives::{Address, B256, U256};
use crate::db::sync::{ConcurrentCacheState, ConcurrentStateError};
use alloy::primitives::{Address, B256, U256};
use dashmap::mapref::one::RefMut;
use revm::{
db::{
database::{
states::{bundle_state::BundleRetention, plain_account::PlainStorage, CacheAccount},
BundleState, State,
BundleState, State, TransitionAccount, TransitionState,
},
primitives::{Account, AccountInfo, Bytecode},
Database, DatabaseCommit, DatabaseRef, TransitionAccount, TransitionState,
state::{Account, AccountInfo, Bytecode},
Database, DatabaseCommit, DatabaseRef,
};
use std::{
collections::{hash_map, BTreeMap},
sync::RwLock,
sync::{Arc, RwLock},
};

/// A [`Child`] is a [`ConcurrentState`] wrapping another [`ConcurrentState`]
/// in an [`Arc`]. This allows for tiered caching, where the child can be
/// merged back into the parent.
pub type Child<Db> = ConcurrentState<Arc<ConcurrentState<Db>>>;

/// State of the blockchain.
///
/// A version of [`revm::db::State`] that can be shared between threads.
/// A version of [`revm::database::State`] that can be shared between threads.
#[derive(Debug)]
pub struct ConcurrentState<Db> {
database: Db,
@@ -42,45 +47,7 @@ where
}
}

/// Non-DB contents of [`ConcurrentState`]
#[derive(Debug, Default)]
pub struct ConcurrentStateInfo {
/// Cached state contains both changed from evm execution and cached/loaded
/// account/storages from database. This allows us to have only one layer
/// of cache where we can fetch data. Additionally we can introduce some
/// preloading of data from database.
pub cache: ConcurrentCacheState,
/// Block state, it aggregates transactions transitions into one state.
///
/// Build reverts and state that gets applied to the state.
pub transition_state: Option<TransitionState>,
/// After block is finishes we merge those changes inside bundle.
/// Bundle is used to update database and create changesets.
/// Bundle state can be set on initialization if we want to use preloaded
/// bundle.
pub bundle_state: BundleState,
/// Addition layer that is going to be used to fetched values before
/// fetching values from database.
///
/// Bundle is the main output of the state execution and this allows
/// setting previous bundle and using its values for execution.
pub use_preloaded_bundle: bool,
/// If EVM asks for block hash we will first check if they are found here.
/// and then ask the database.
///
/// This map can be used to give different values for block hashes if in
/// case the fork block is different or some blocks are not saved inside
/// database.
pub block_hashes: RwLock<BTreeMap<u64, B256>>,
}

impl<Db: DatabaseRef + Sync> ConcurrentState<Db> {
/// Create a new [`ConcurrentState`] with the given database and cache
/// state.
pub const fn new(database: Db, info: ConcurrentStateInfo) -> Self {
Self { database, info }
}

impl<Db> ConcurrentState<Db> {
/// Deconstruct the [`ConcurrentState`] into its parts.
pub fn into_parts(self) -> (Db, ConcurrentStateInfo) {
(self.database, self.info)
@@ -92,6 +59,74 @@ impl<Db: DatabaseRef + Sync> ConcurrentState<Db> {
self.info.bundle_state.size_hint()
}

/// State clear EIP-161 is enabled in Spurious Dragon hardfork.
pub const fn set_state_clear_flag(&mut self, has_state_clear: bool) {
self.info.cache.set_state_clear_flag(has_state_clear);
}

/// Insert not existing account into cache state.
pub fn insert_not_existing(&mut self, address: Address) {
self.info.cache.insert_not_existing(address)
}

/// Insert account into cache state.
pub fn insert_account(&mut self, address: Address, info: AccountInfo) {
self.info.cache.insert_account(address, info)
}

/// Insert account with storage into cache state.
pub fn insert_account_with_storage(
&mut self,
address: Address,
info: AccountInfo,
storage: PlainStorage,
) {
self.info.cache.insert_account_with_storage(address, info, storage)
}

/// Apply evm transitions to transition state.
pub fn apply_transition(&mut self, transitions: Vec<(Address, TransitionAccount)>) {
// add transition to transition state.
if let Some(s) = self.info.transition_state.as_mut() {
s.add_transitions(transitions)
}
}

/// Take all transitions and merge them inside bundle state.
/// This action will create final post state and all reverts so that
/// we at any time revert state of bundle to the state before transition
/// is applied.
pub fn merge_transitions(&mut self, retention: BundleRetention) {
if let Some(transition_state) = self.info.transition_state.take() {
self.info
.bundle_state
.apply_transitions_and_create_reverts(transition_state, retention);
}
}

/// Takes the [`BundleState`] changeset from the [`ConcurrentState`],
/// replacing it
/// with an empty one.
///
/// This will not apply any pending [`TransitionState`]. It is recommended
/// to call [`ConcurrentState::merge_transitions`] before taking the bundle.
///
/// If the [`State`] has been built with the
/// [`revm::database::StateBuilder::with_bundle_prestate`] option, the
/// pre-state will be taken along with any changes made by
/// [`ConcurrentState::merge_transitions`].
pub fn take_bundle(&mut self) -> BundleState {
core::mem::take(&mut self.info.bundle_state)
}
}

impl<Db: DatabaseRef + Sync> ConcurrentState<Db> {
/// Create a new [`ConcurrentState`] with the given database and cache
/// state.
pub const fn new(database: Db, info: ConcurrentStateInfo) -> Self {
Self { database, info }
}

/// Iterate over received balances and increment all account balances.
/// If account is not found inside cache state it will be loaded from database.
///
@@ -146,51 +181,6 @@ impl<Db: DatabaseRef + Sync> ConcurrentState<Db> {
Ok(balances)
}

/// State clear EIP-161 is enabled in Spurious Dragon hardfork.
pub fn set_state_clear_flag(&mut self, has_state_clear: bool) {
self.info.cache.set_state_clear_flag(has_state_clear);
}

/// Insert not existing account into cache state.
pub fn insert_not_existing(&mut self, address: Address) {
self.info.cache.insert_not_existing(address)
}

/// Insert account into cache state.
pub fn insert_account(&mut self, address: Address, info: AccountInfo) {
self.info.cache.insert_account(address, info)
}

/// Insert account with storage into cache state.
pub fn insert_account_with_storage(
&mut self,
address: Address,
info: AccountInfo,
storage: PlainStorage,
) {
self.info.cache.insert_account_with_storage(address, info, storage)
}

/// Apply evm transitions to transition state.
pub fn apply_transition(&mut self, transitions: Vec<(Address, TransitionAccount)>) {
// add transition to transition state.
if let Some(s) = self.info.transition_state.as_mut() {
s.add_transitions(transitions)
}
}

/// Take all transitions and merge them inside bundle state.
/// This action will create final post state and all reverts so that
/// we at any time revert state of bundle to the state before transition
/// is applied.
pub fn merge_transitions(&mut self, retention: BundleRetention) {
if let Some(transition_state) = self.info.transition_state.take() {
self.info
.bundle_state
.apply_transitions_and_create_reverts(transition_state, retention);
}
}

/// Get a mutable reference to the [`CacheAccount`] for the given address.
/// If the account is not found in the cache, it will be loaded from the
/// database and inserted into the cache.
@@ -228,20 +218,68 @@ impl<Db: DatabaseRef + Sync> ConcurrentState<Db> {
}
}

// TODO make cache aware of transitions dropping by having global transition counter.
/// Takes the [`BundleState`] changeset from the [`ConcurrentState`],
/// replacing it
/// with an empty one.
/// Create a [`Child`] DB that wraps this state in another cache layer.
///
/// This will not apply any pending [`TransitionState`]. It is recommended
/// to call [`ConcurrentState::merge_transitions`] before taking the bundle.
/// This allows the [`ConcurrentState`] to be shared between threads, with
/// each thread having a separate cache for its local changes. The child
/// can later be merged back into the parent with [`Self::merge_child`] IF
/// no other children or copies of the `Arc<Self>` exist.
pub fn child(self: &Arc<Self>) -> Child<Db>
where
Db: Send,
{
ConcurrentState::new(self.clone(), Default::default())
}

/// Merge a child DB into this DB, incorporating its changes and overwriting
/// any present values.
///
/// If the `State` has been built with the
/// [`revm::StateBuilder::with_bundle_prestate`] option, the pre-state will be
/// taken along with any changes made by
/// [`ConcurrentState::merge_transitions`].
pub fn take_bundle(&mut self) -> BundleState {
core::mem::take(&mut self.info.bundle_state)
/// This function needs to take ownership of the [`Child`], as the child
/// copntains an `Arc` of the parent. This [`Arc`] must be dropped to ensure
/// that the parent's pointer is unique, allowing the parent to be modified
/// via [`Arc::get_mut`].
///
/// If other children or other copies of the `Arc<Self>` exist, this will
/// fail with a [`ConcurrentStateError`]. error. If the child belongs to
/// another parent, this will also fail.
///
/// These conditions can be checked via [`Self::can_merge`], please see
/// that function's documentation for a possible race condition.
pub fn merge_child(self: &mut Arc<Self>, child: Child<Db>) -> Result<(), ConcurrentStateError> {
self.can_merge(&child)?;

let (_, info) = child.into_parts();

let this = Arc::get_mut(self).ok_or_else(ConcurrentStateError::not_unique)?;

this.info.cache.absorb(info.cache);
Ok(())
}

/// True if the child can be merged into this state, false otherwise.
///
/// ## Note
///
/// Race conditions can occur if the child is shared between threads, as
/// the child may be cloned AFTER this check is made, but BEFORE the child
/// is merged. In this case the function will spuriously return `Ok(())`
/// even though the merge will fail. To avoid this, ensure that the child
/// is not shared between threads when [`Self::can_merge`] is called.
pub fn can_merge(self: &Arc<Self>, child: &Child<Db>) -> Result<(), ConcurrentStateError> {
if !self.is_parent(child) {
return Err(ConcurrentStateError::not_parent());
}

if Arc::strong_count(self) != 2 {
return Err(ConcurrentStateError::not_unique());
}

Ok(())
}

/// True if the child is a child of this state, false otherwise.
pub fn is_parent(self: &Arc<Self>, child: &Child<Db>) -> bool {
Arc::ptr_eq(self, &child.database)
}
}

@@ -354,6 +392,93 @@ impl<Db: DatabaseRef + Sync> Database for ConcurrentState<Db> {
}
}

/// Non-DB contents of [`ConcurrentState`]
#[derive(Debug, Default)]
pub struct ConcurrentStateInfo {
/// Cached state contains both changed from evm execution and cached/loaded
/// account/storages from database. This allows us to have only one layer
/// of cache where we can fetch data. Additionally we can introduce some
/// preloading of data from database.
pub cache: ConcurrentCacheState,
/// Block state, it aggregates transactions transitions into one state.
///
/// Build reverts and state that gets applied to the state.
pub transition_state: Option<TransitionState>,
/// After block is finishes we merge those changes inside bundle.
/// Bundle is used to update database and create changesets.
/// Bundle state can be set on initialization if we want to use preloaded
/// bundle.
pub bundle_state: BundleState,
/// Addition layer that is going to be used to fetched values before
/// fetching values from database.
///
/// Bundle is the main output of the state execution and this allows
/// setting previous bundle and using its values for execution.
pub use_preloaded_bundle: bool,
/// If EVM asks for block hash we will first check if they are found here.
/// and then ask the database.
///
/// This map can be used to give different values for block hashes if in
/// case the fork block is different or some blocks are not saved inside
/// database.
pub block_hashes: RwLock<BTreeMap<u64, B256>>,
}

#[cfg(test)]
mod test {
use super::*;
use revm::database::EmptyDB;

#[test]
const fn assert_child_trait_impls() {
const fn assert_database_ref<T: DatabaseRef>() {}
const fn assert_database_commit<T: DatabaseCommit>() {}
const fn assert_database<T: Database>() {}

assert_database_ref::<Child<EmptyDB>>();
assert_database_commit::<Child<EmptyDB>>();
assert_database::<Child<EmptyDB>>();
}

#[test]
fn merge_child() {
let addr = Address::repeat_byte(1);

let mut parent = Arc::new(ConcurrentState::new(EmptyDB::new(), Default::default()));
let mut child = parent.child();

child.increment_balances([(addr, 100)]).unwrap();

// Check that the parent is not modified
assert!(parent.load_cache_account_mut(addr).unwrap().value().account_info().is_none());
assert_eq!(
child.load_cache_account_mut(addr).unwrap().value().account_info().unwrap().balance,
U256::from(100)
);
assert_eq!(Arc::strong_count(&parent), 2);

// Check that it errors if there are 2 kids
let child_2 = parent.child();
assert_eq!(parent.can_merge(&child_2).unwrap_err(), ConcurrentStateError::not_unique());
assert_eq!(parent.merge_child(child_2).unwrap_err(), ConcurrentStateError::not_unique());

// Check that it won't absorb the child of a different parent
let parent_2 = Arc::new(ConcurrentState::new(EmptyDB::new(), Default::default()));
let child_2 = parent_2.child();
assert_eq!(parent.merge_child(child_2).unwrap_err(), ConcurrentStateError::not_parent());

// now merge
parent.can_merge(&child).unwrap();
parent.merge_child(child).unwrap();

// Check that the child is now merged
assert_eq!(
parent.load_cache_account_mut(addr).unwrap().value().account_info().unwrap().balance,
U256::from(100)
);
}
}

// Some code above and documentation is adapted from the revm crate, and is
// reproduced here under the terms of the MIT license.
//
272 changes: 272 additions & 0 deletions src/db/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
use revm::{
database::{states::bundle_state::BundleRetention, BundleState, Cache, CacheDB, State},
primitives::B256,
Database,
};
use std::{collections::BTreeMap, convert::Infallible, sync::Arc};

/// Abstraction trait covering types that accumulate state changes into a
/// [`BundleState`]. The prime example of this is [`State`]. These types are
/// use to accumulate state changes during the execution of a sequence of
/// transactions, and then provide access to the net changes in the form of a
/// [`BundleState`].
pub trait StateAcc {
/// Set the state clear flag. See [`State::set_state_clear_flag`].
fn set_state_clear_flag(&mut self, flag: bool);

/// Merge transitions into the bundle. See [`State::merge_transitions`].
fn merge_transitions(&mut self, retention: BundleRetention);

/// Take the bundle. See [`State::take_bundle`].
fn take_bundle(&mut self) -> BundleState;

/// Set the block hashes, overriding any existing values, and inserting any
/// absent values.
fn set_block_hashes(&mut self, block_hashes: &BTreeMap<u64, B256>);
}

impl<Db: Database> StateAcc for State<Db> {
fn set_state_clear_flag(&mut self, flag: bool) {
Self::set_state_clear_flag(self, flag)
}

fn merge_transitions(&mut self, retention: BundleRetention) {
Self::merge_transitions(self, retention)
}

fn take_bundle(&mut self) -> BundleState {
Self::take_bundle(self)
}

fn set_block_hashes(&mut self, block_hashes: &BTreeMap<u64, B256>) {
self.block_hashes.extend(block_hashes)
}
}

/// Fallible version of [`StateAcc`].
///
/// Abstraction trait covering types that accumulate state changes into a
/// [`BundleState`]. The prime example of this is [`State`]. These types are
/// use to accumulate state changes during the execution of a sequence of
/// transactions, and then provide access to the net changes in the form of a
/// [`BundleState`].
///
/// The primary motivator for this trait is to allow for the implementation of
/// [`StateAcc`] for [`Arc`]-wrapped DBs, which may fail to mutate if the
/// reference is not unique.
pub trait TryStateAcc: Sync {
/// Error type to be thrown when state accumulation fails.
type Error: core::error::Error;

/// Attempt to set the state clear flag. See [`State::set_state_clear_flag`].
fn try_set_state_clear_flag(&mut self, flag: bool) -> Result<(), Self::Error>;

/// Attempt to merge transitions into the bundle. See
/// [`State::merge_transitions`].
fn try_merge_transitions(&mut self, retention: BundleRetention) -> Result<(), Self::Error>;

/// Attempt to take the bundle. See [`State::take_bundle`].
fn try_take_bundle(&mut self) -> Result<BundleState, Self::Error>;

/// Attempt to set the block hashes, overriding any existing values, and
/// inserting any absent values.
fn try_set_block_hashes(
&mut self,
block_hashes: &BTreeMap<u64, B256>,
) -> Result<(), Self::Error>;
}

impl<Db> TryStateAcc for Db
where
Db: StateAcc + Sync,
{
type Error = Infallible;

fn try_set_state_clear_flag(&mut self, flag: bool) -> Result<(), Infallible> {
self.set_state_clear_flag(flag);
Ok(())
}

fn try_merge_transitions(&mut self, retention: BundleRetention) -> Result<(), Infallible> {
self.merge_transitions(retention);
Ok(())
}

fn try_take_bundle(&mut self) -> Result<BundleState, Infallible> {
Ok(self.take_bundle())
}

fn try_set_block_hashes(
&mut self,
block_hashes: &BTreeMap<u64, B256>,
) -> Result<(), Infallible> {
self.set_block_hashes(block_hashes);
Ok(())
}
}

/// Error type for implementation of [`TryStateAcc`] for [`Arc`]-wrapped
/// DBs.
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArcUpgradeError {
/// Arc reference is not unique. Ensure that all other references are
/// dropped before attempting to mutate the state.
#[error("Arc reference is not unique, cannot mutate")]
NotUnique,
}

impl<Db> TryStateAcc for Arc<Db>
where
Db: StateAcc + Sync + Send,
{
type Error = ArcUpgradeError;

fn try_set_state_clear_flag(&mut self, flag: bool) -> Result<(), ArcUpgradeError> {
Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.set_state_clear_flag(flag);
Ok(())
}

fn try_merge_transitions(&mut self, retention: BundleRetention) -> Result<(), ArcUpgradeError> {
Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.merge_transitions(retention);
Ok(())
}

fn try_take_bundle(&mut self) -> Result<BundleState, ArcUpgradeError> {
Ok(Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.take_bundle())
}

fn try_set_block_hashes(
&mut self,
block_hashes: &BTreeMap<u64, B256>,
) -> Result<(), ArcUpgradeError> {
Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique)?.set_block_hashes(block_hashes);
Ok(())
}
}

/// Trait for Databases that have a [`Cache`].
pub trait CachingDb {
/// Get the cache.
fn cache(&self) -> &Cache;

/// Get the cache mutably.
fn cache_mut(&mut self) -> &mut Cache;

/// Deconstruct into the cache
fn into_cache(self) -> Cache;

/// Extend the cache with the given cache by copying data.
///
/// The behavior is as follows:
/// - Accounts are overridden with outer accounts
/// - Contracts are overridden with outer contracts
/// - Logs are appended
/// - Block hashes are overridden with outer block hashes
fn extend_ref(&mut self, cache: &Cache) {
self.cache_mut().accounts.extend(cache.accounts.iter().map(|(k, v)| (*k, v.clone())));
self.cache_mut().contracts.extend(cache.contracts.iter().map(|(k, v)| (*k, v.clone())));
self.cache_mut().logs.extend(cache.logs.iter().cloned());
self.cache_mut().block_hashes.extend(cache.block_hashes.iter().map(|(k, v)| (*k, *v)));
}

/// Extend the cache with the given cache by moving data.
///
/// The behavior is as follows:
/// - Accounts are overridden with outer accounts
/// - Contracts are overridden with outer contracts
/// - Logs are appended
/// - Block hashes are overridden with outer block hashes
fn extend(&mut self, cache: Cache) {
self.cache_mut().accounts.extend(cache.accounts);
self.cache_mut().contracts.extend(cache.contracts);
self.cache_mut().logs.extend(cache.logs);
self.cache_mut().block_hashes.extend(cache.block_hashes);
}
}

impl<Db> CachingDb for CacheDB<Db> {
fn cache(&self) -> &Cache {
&self.cache
}

fn cache_mut(&mut self) -> &mut Cache {
&mut self.cache
}

fn into_cache(self) -> Cache {
self.cache
}
}

/// Trait for Databases that have a [`Cache`] and can fail to mutably access
/// it. E.g. `Arc<CacheDB<Db>>`
pub trait TryCachingDb {
/// Error type to be thrown when cache access fails.
type Error: core::error::Error;

/// Attempt to get the cache.
fn cache(&self) -> &Cache;

/// Attempt to get the cache mutably.
fn try_cache_mut(&mut self) -> Result<&mut Cache, Self::Error>;

/// Attempt to deconstruct into the cache
fn try_into_cache(self) -> Result<Cache, Self::Error>;

/// Attempt to fold a cache into the database.
///
/// The behavior is as follows:
/// - Accounts are overridden with outer accounts
/// - Contracts are overridden with outer contracts
/// - Logs are appended
/// - Block hashes are overridden with outer block hashes
fn try_extend_ref(&mut self, cache: &Cache) -> Result<(), Self::Error>
where
Self: Sized,
{
let inner_cache = self.try_cache_mut()?;
inner_cache.accounts.extend(cache.accounts.iter().map(|(k, v)| (*k, v.clone())));
inner_cache.contracts.extend(cache.contracts.iter().map(|(k, v)| (*k, v.clone())));
inner_cache.logs.extend(cache.logs.iter().cloned());
inner_cache.block_hashes.extend(cache.block_hashes.iter().map(|(k, v)| (*k, *v)));
Ok(())
}

/// Attempt to extend the cache with the given cache by moving data.
///
/// The behavior is as follows:
/// - Accounts are overridden with outer accounts
/// - Contracts are overridden with outer contracts
/// - Logs are appended
/// - Block hashes are overridden with outer block hashes
fn try_extend(&mut self, cache: Cache) -> Result<(), Self::Error>
where
Self: Sized,
{
let inner_cache = self.try_cache_mut()?;
inner_cache.accounts.extend(cache.accounts);
inner_cache.contracts.extend(cache.contracts);
inner_cache.logs.extend(cache.logs);
inner_cache.block_hashes.extend(cache.block_hashes);
Ok(())
}
}

impl<Db> TryCachingDb for Arc<Db>
where
Db: CachingDb,
{
type Error = ArcUpgradeError;

fn cache(&self) -> &Cache {
self.as_ref().cache()
}

fn try_cache_mut(&mut self) -> Result<&mut Cache, Self::Error> {
Self::get_mut(self).ok_or(ArcUpgradeError::NotUnique).map(|db| db.cache_mut())
}

fn try_into_cache(self) -> Result<Cache, Self::Error> {
Self::into_inner(self).ok_or(ArcUpgradeError::NotUnique).map(|db| db.into_cache())
}
}
238 changes: 124 additions & 114 deletions src/driver/alloy.rs

Large diffs are not rendered by default.

33 changes: 16 additions & 17 deletions src/driver/block.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
use crate::{Block, EvmBlockDriverErrored, EvmNeedsBlock, EvmNeedsTx};
use revm::{primitives::EVMError, Database, DatabaseCommit};
use crate::{helpers::Ctx, Block, EvmBlockDriverErrored, EvmNeedsBlock, EvmNeedsTx};
use revm::{
context::result::EVMError, inspector::NoOpInspector, Database, DatabaseCommit, Inspector,
};

/// The result of running transactions for a block driver.
pub type RunTxResult<'a, Ext, Db, T> =
Result<EvmNeedsTx<'a, Ext, Db>, EvmBlockDriverErrored<'a, Ext, Db, T>>;
pub type RunTxResult<T, Db, Insp> =
Result<EvmNeedsTx<Db, Insp>, EvmBlockDriverErrored<T, Db, Insp>>;

/// The result of driving a block to completion.
pub type DriveBlockResult<'a, Ext, Db, T> =
Result<EvmNeedsBlock<'a, Ext, Db>, EvmBlockDriverErrored<'a, Ext, Db, T>>;
pub type DriveBlockResult<T, Db, Insp> =
Result<EvmNeedsBlock<Db, Insp>, EvmBlockDriverErrored<T, Db, Insp>>;

/// Driver for a single trevm block. This trait allows a type to specify the
/// entire lifecycle of a trevm block, from opening the block to driving the
/// trevm to completion.
pub trait BlockDriver<Ext> {
pub trait BlockDriver<Db, Insp = NoOpInspector>
where
Db: Database + DatabaseCommit,
Insp: Inspector<Ctx<Db>>,
{
/// The [`Block`] filler for this driver.
type Block: Block;

/// An error type for this driver.
type Error<Db: Database>: core::error::Error + From<EVMError<Db::Error>>;
type Error: core::error::Error + From<EVMError<Db::Error>>;

/// Get a reference to the block filler for this driver.
fn block(&self) -> &Self::Block;

/// Run the transactions for the block.
fn run_txns<'a, Db: Database + DatabaseCommit>(
&mut self,
trevm: EvmNeedsTx<'a, Ext, Db>,
) -> RunTxResult<'a, Ext, Db, Self>;

fn run_txns(&mut self, trevm: EvmNeedsTx<Db, Insp>) -> RunTxResult<Self, Db, Insp>;
/// Run post
fn post_block<Db: Database + DatabaseCommit>(
&mut self,
trevm: &EvmNeedsBlock<'_, Ext, Db>,
) -> Result<(), Self::Error<Db>>;
fn post_block(&mut self, trevm: &EvmNeedsBlock<Db, Insp>) -> Result<(), Self::Error>;
}
28 changes: 14 additions & 14 deletions src/driver/bundle.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
use crate::{states::EvmBundleDriverErrored, EvmNeedsTx};
use revm::{primitives::EVMError, Database, DatabaseCommit};
use crate::{helpers::Ctx, states::EvmBundleDriverErrored, EvmNeedsTx};
use revm::{
context::result::EVMError, inspector::NoOpInspector, Database, DatabaseCommit, Inspector,
};

/// The result of driving a bundle to completion.
pub type DriveBundleResult<'a, Ext, Db, T> =
Result<EvmNeedsTx<'a, Ext, Db>, EvmBundleDriverErrored<'a, Ext, Db, T>>;
pub type DriveBundleResult<T, Db, Insp> =
Result<EvmNeedsTx<Db, Insp>, EvmBundleDriverErrored<T, Db, Insp>>;

/// Driver for a bundle of transactions. This trait allows a type to specify the
/// entire lifecycle of a bundle, simulating the entire list of transactions.
pub trait BundleDriver<Ext> {
pub trait BundleDriver<Db, Insp = NoOpInspector>
where
Db: Database + DatabaseCommit,
Insp: Inspector<Ctx<Db>>,
{
/// An error type for this driver.
type Error<Db: Database>: core::error::Error + From<EVMError<Db::Error>>;
type Error: core::error::Error + From<EVMError<Db::Error>>;

/// Run the transactions contained in the bundle.
fn run_bundle<'a, Db: Database + DatabaseCommit>(
&mut self,
trevm: EvmNeedsTx<'a, Ext, Db>,
) -> DriveBundleResult<'a, Ext, Db, Self>;
fn run_bundle(&mut self, trevm: EvmNeedsTx<Db, Insp>) -> DriveBundleResult<Self, Db, Insp>;

/// Run post
fn post_bundle<Db: Database + DatabaseCommit>(
&mut self,
trevm: &EvmNeedsTx<'_, Ext, Db>,
) -> Result<(), Self::Error<Db>>;
fn post_bundle(&mut self, trevm: &EvmNeedsTx<Db, Insp>) -> Result<(), Self::Error>;
}
30 changes: 17 additions & 13 deletions src/driver/chain.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
use crate::{BlockDriver, EvmChainDriverErrored, EvmNeedsBlock};
use crate::{helpers::Ctx, BlockDriver, EvmChainDriverErrored, EvmNeedsBlock};
use revm::{
primitives::{EVMError, SpecId},
Database, DatabaseCommit,
context::result::EVMError, inspector::NoOpInspector, primitives::hardfork::SpecId, Database,
DatabaseCommit, Inspector,
};

/// The result of driving a chain to completion.
pub type DriveChainResult<'a, Ext, Db, D> =
Result<EvmNeedsBlock<'a, Ext, Db>, EvmChainDriverErrored<'a, Ext, Db, D>>;
pub type DriveChainResult<D, Db, Insp> =
Result<EvmNeedsBlock<Db, Insp>, EvmChainDriverErrored<D, Db, Insp>>;

/// Driver for a chain of blocks.
pub trait ChainDriver<Ext> {
pub trait ChainDriver<Db, Insp = NoOpInspector>
where
Db: Database + DatabaseCommit,
Insp: Inspector<Ctx<Db>>,
{
/// The block driver for this chain.
type BlockDriver: BlockDriver<Ext>;
type BlockDriver: BlockDriver<Db, Insp>;

/// An error type for this driver.
type Error<Db: Database>: core::error::Error
type Error: core::error::Error
+ From<EVMError<Db::Error>>
+ From<<Self::BlockDriver as BlockDriver<Ext>>::Error<Db>>;
+ From<<Self::BlockDriver as BlockDriver<Db, Insp>>::Error>;

/// Get the spec id for a block.
fn spec_id_for(&self, block: &<Self::BlockDriver as BlockDriver<Ext>>::Block) -> SpecId;
fn spec_id_for(&self, block: &<Self::BlockDriver as BlockDriver<Db, Insp>>::Block) -> SpecId;

/// Get the blocks in this chain. The blocks should be in order, and this
/// function MUST NOT return an empty slice.
@@ -29,9 +33,9 @@ pub trait ChainDriver<Ext> {
/// or parent-child relationships.
///
/// The `idx` parameter is the index of the block in the chain.
fn interblock<Db: Database + DatabaseCommit>(
fn interblock(
&mut self,
trevm: &EvmNeedsBlock<'_, Ext, Db>,
trevm: &EvmNeedsBlock<Db, Insp>,
idx: usize,
) -> Result<(), Self::Error<Db>>;
) -> Result<(), Self::Error>;
}
298 changes: 298 additions & 0 deletions src/est.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
use revm::{
context::result::{ExecutionResult, HaltReason, Output},
primitives::Bytes,
};
use std::ops::Range;

/// Simple wrapper around a range of u64 values, with convenience methods for
/// binary searching.
pub(crate) struct SearchRange(Range<u64>);

impl core::fmt::Display for SearchRange {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}..{}", self.0.start, self.0.end)
}
}

impl From<Range<u64>> for SearchRange {
fn from(value: Range<u64>) -> Self {
Self(value)
}
}

impl From<SearchRange> for Range<u64> {
fn from(value: SearchRange) -> Self {
value.0
}
}

impl SearchRange {
/// Create a new search range.
pub(crate) const fn new(start: u64, end: u64) -> Self {
Self(start..end)
}

/// Calculate the midpoint of the search range.
pub(crate) const fn midpoint(&self) -> u64 {
(self.max() + self.min()) / 2
}

/// Get the start of the search range.
pub(crate) const fn min(&self) -> u64 {
self.0.start
}

/// Set the start of the search range.
pub(crate) const fn set_min(&mut self, min: u64) {
self.0.start = min;
}

/// Raise the minimum of the search range, if the candidate is higher.
pub(crate) const fn maybe_raise_min(&mut self, candidate: u64) {
if candidate > self.min() {
self.set_min(candidate);
}
}

/// Get the end of the search range.
pub(crate) const fn max(&self) -> u64 {
self.0.end
}

/// Set the end of the search range.
pub(crate) const fn set_max(&mut self, max: u64) {
self.0.end = max;
}

/// Lower the maximum of the search range, if the candidate is lower.
pub(crate) const fn maybe_lower_max(&mut self, candidate: u64) {
if candidate < self.max() {
self.set_max(candidate);
}
}

/// Calculate the search ratio.
pub(crate) const fn ratio(&self) -> f64 {
(self.max() - self.min()) as f64 / self.max() as f64
}

/// True if the search range contains the given value.
pub(crate) fn contains(&self, value: u64) -> bool {
self.0.contains(&value)
}

/// Return the size of the range.
pub(crate) const fn size(&self) -> u64 {
self.0.end - self.0.start
}
}

/// The result of gas estimation.
///
/// This is a trimmed version of [`ExecutionResult`], that contains only
/// information relevant to gas estimation.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EstimationResult {
/// The estimation was successful, the result is the gas estimation.
Success {
/// The input estimation limit.
limit: u64,
/// The amount of gas that was refunded to the caller as unused.
refund: u64,
/// The amount of gas used in the execution.
gas_used: u64,
/// The output of execution.
output: Output,
},
/// Estimation failed due to contract revert.
Revert {
/// The input estimation limit.
limit: u64,
/// The revert reason.
reason: Bytes,
/// The amount of gas used in the execution.
gas_used: u64,
},
/// The estimation failed due to EVM halt.
Halt {
/// The input estimation limit.
limit: u64,
/// The halt reason.
reason: HaltReason,
/// The amount of gas used in the execution
gas_used: u64,
},
}

impl core::fmt::Display for EstimationResult {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Success { limit, refund, gas_used, .. } => {
write!(
f,
"Success {{ gas_limit: {limit}, refund: {refund}, gas_used: {gas_used}, .. }}",
)
}
Self::Revert { limit, gas_used, .. } => {
write!(f, "Revert {{ gas_limit: {limit}, gas_used: {gas_used}, .. }}")
}
Self::Halt { limit, reason, gas_used } => {
write!(f, "Halt {{ gas_limit: {limit}, reason: {reason:?}, gas_used: {gas_used} }}")
}
}
}
}

impl EstimationResult {
/// Initialize the estimation result from an execution result and the gas
/// limit of the transaction that produced the estimation.
pub fn from_limit_and_execution_result(limit: u64, value: &ExecutionResult) -> Self {
match value {
ExecutionResult::Success { gas_used, output, gas_refunded, .. } => Self::Success {
limit,
refund: *gas_refunded,
gas_used: *gas_used,
output: output.clone(),
},
ExecutionResult::Revert { output, gas_used } => {
Self::Revert { limit, reason: output.clone(), gas_used: *gas_used }
}
ExecutionResult::Halt { reason, gas_used } => {
Self::Halt { limit, reason: *reason, gas_used: *gas_used }
}
}
}

/// Create a successful estimation result with a gas estimation of 21000.
pub const fn basic_transfer_success(estimation: u64) -> Self {
Self::Success {
limit: estimation,
refund: 0,
gas_used: estimation,
output: Output::Call(Bytes::new()),
}
}

/// Return true if the execution was successful.
pub const fn is_success(&self) -> bool {
matches!(self, Self::Success { .. })
}

/// Return true if the execution was not successful.
pub const fn is_failure(&self) -> bool {
!self.is_success()
}

/// Get the gas limit that was set in the EVM when the estimation was
/// produced.
pub const fn limit(&self) -> u64 {
match self {
Self::Success { limit, .. } => *limit,
Self::Revert { limit, .. } => *limit,
Self::Halt { limit, .. } => *limit,
}
}

/// Get the gas refunded, if the execution was successful.
pub const fn gas_refunded(&self) -> Option<u64> {
match self {
Self::Success { refund, .. } => Some(*refund),
_ => None,
}
}

/// Get the output, if the execution was successful.
pub const fn output(&self) -> Option<&Output> {
match self {
Self::Success { output, .. } => Some(output),
_ => None,
}
}

/// Get the gas used in execution, regardless of the outcome.
pub const fn gas_used(&self) -> u64 {
match self {
Self::Success { gas_used, .. } => *gas_used,
Self::Revert { gas_used, .. } => *gas_used,
Self::Halt { gas_used, .. } => *gas_used,
}
}

/// Return true if the execution failed due to revert.
pub const fn is_revert(&self) -> bool {
matches!(self, Self::Revert { .. })
}

/// Get the revert reason if the execution failed due to revert.
pub const fn revert_reason(&self) -> Option<&Bytes> {
match self {
Self::Revert { reason, .. } => Some(reason),
_ => None,
}
}

/// Return true if the execution failed due to EVM halt.
pub const fn is_halt(&self) -> bool {
matches!(self, Self::Halt { .. })
}

/// Get the halt reason if the execution failed due to EVM halt.
pub const fn halt_reason(&self) -> Option<&HaltReason> {
match self {
Self::Halt { reason, .. } => Some(reason),
_ => None,
}
}

/// Adjust the binary search range based on the estimation outcome.
pub(crate) const fn adjust_binary_search_range(
&self,
range: &mut SearchRange,
) -> Result<(), Self> {
match self {
Self::Success { limit, .. } => range.set_max(*limit),
Self::Revert { limit, .. } => range.set_min(*limit),
Self::Halt { limit, reason, gas_used } => {
// Both `OutOfGas` and `InvalidEFOpcode` can occur dynamically
// if the gas left is too low. Treat this as an out of gas
// condition, knowing that the call succeeds with a
// higher gas limit.
//
// Common usage of invalid opcode in OpenZeppelin:
// <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/94697be8a3f0dfcd95dfb13ffbd39b5973f5c65d/contracts/metatx/ERC2771Forwarder.sol#L360-L367>
if matches!(reason, HaltReason::OutOfGas(_) | HaltReason::InvalidFEOpcode) {
range.set_min(*limit);
} else {
// NB: can't clone here as this is a const fn.
return Err(Self::Halt { limit: *limit, reason: *reason, gas_used: *gas_used });
}
}
}
Ok(())
}
}

// Some code above is reproduced from `reth`. It is reused here under the MIT
// license.
//
// The MIT License (MIT)
//
// Copyright (c) 2022-2024 Reth Contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
1,684 changes: 1,413 additions & 271 deletions src/evm.rs

Large diffs are not rendered by default.

21 changes: 13 additions & 8 deletions src/ext.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use alloy_primitives::{Address, B256, U256};
use alloy::primitives::{Address, B256, U256};
use revm::{
primitives::{Account, AccountInfo, Bytecode, EvmState, EvmStorageSlot, HashMap},
context::{ContextTr, Evm},
primitives::HashMap,
state::{Account, AccountInfo, Bytecode, EvmState, EvmStorageSlot},
Database, DatabaseCommit,
};

/// Extension trait for [`revm::Evm`] with convenience functions for reading
/// and modifying state.
/// Extension trait for [`revm::context::Evm`] with convenience functions for
/// reading and modifying state.
pub trait EvmExtUnchecked<Db: Database> {
/// Get a mutable reference to the database.
fn db_mut_ext(&mut self) -> &mut Db;
@@ -81,7 +83,7 @@ pub trait EvmExtUnchecked<Db: Database> {
let mut acct = self.account(address)?;
let old = self.storage(address, index)?;

let change = EvmStorageSlot::new_changed(old, value);
let change = EvmStorageSlot::new_changed(old, value, 0);
acct.storage.insert(index, change);
acct.mark_touch();

@@ -169,8 +171,11 @@ pub trait EvmExtUnchecked<Db: Database> {
}
}

impl<Ext, Db: Database> EvmExtUnchecked<Db> for revm::Evm<'_, Ext, Db> {
fn db_mut_ext(&mut self) -> &mut Db {
self.db_mut()
impl<Ctx, Insp, Inst, Prec, Frame> EvmExtUnchecked<Ctx::Db> for Evm<Ctx, Insp, Inst, Prec, Frame>
where
Ctx: ContextTr,
{
fn db_mut_ext(&mut self) -> &mut Ctx::Db {
self.ctx.db_mut()
}
}
350 changes: 234 additions & 116 deletions src/fill/alloy.rs

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions src/fill/fillers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use crate::fill::traits::{Cfg, Tx};
use revm::context::{BlockEnv, CfgEnv, TxEnv};

/// A [`Cfg`] that disables gas-related checks and payment of the
/// beneficiary reward, while leaving other cfg options unchanged.
///
/// ## Warning
///
/// This filler relies on the following optional features:
/// - `optional_balance_check`
/// - `optional_beneficiary_reward`
/// - `optional_gas_refund`
/// - `optional_no_base_fee`
///
/// It will disable the corresponding checks if the features are enabled. **If
/// none of the features are enabled, this filler will do nothing.**
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DisableGasChecks;

impl Cfg for DisableGasChecks {
#[allow(unused_variables)]
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
#[cfg(feature = "optional_balance_check")]
{
cfg_env.disable_balance_check = true;
}
#[cfg(feature = "optional_no_base_fee")]
{
cfg_env.disable_base_fee = true;
}
}
}

/// A [`Cfg`] that disables the nonce check, while leaving other [`CfgEnv`]
/// attributes untouched.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DisableNonceCheck;

impl Cfg for DisableNonceCheck {
#[allow(unused_variables)]
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
cfg_env.disable_nonce_check = true;
}
}

/// Prime the EVM for gas estimation. This filler is all of [`Cfg`], and
/// [`Tx`]. It is used internally by [`crate::Trevm::estimate_gas`], and is
/// considered a low-level API. Generally it is not correct to import this
/// type.
#[cfg(feature = "estimate_gas")]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[repr(transparent)]
pub(crate) struct GasEstimationFiller {
pub(crate) gas_limit: u64,
}

#[cfg(feature = "estimate_gas")]
impl From<u64> for GasEstimationFiller {
fn from(gas_limit: u64) -> Self {
Self { gas_limit }
}
}

#[cfg(feature = "estimate_gas")]
impl Cfg for GasEstimationFiller {
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
cfg_env.disable_base_fee = true;
cfg_env.disable_eip3607 = true;
DisableNonceCheck.fill_cfg_env(cfg_env);
}

fn fill_cfg<Db: revm::Database, Insp, Inst, Prec, Frame>(
&self,
evm: &mut revm::context::Evm<crate::helpers::Ctx<Db>, Insp, Inst, Prec, Frame>,
) {
evm.ctx.modify_cfg(|cfg_env| self.fill_cfg_env(cfg_env));

let chain_id = evm.ctx.cfg.chain_id;

evm.ctx.modify_tx(|tx_env| {
tx_env.chain_id = Some(chain_id);
});
}
}

#[cfg(feature = "estimate_gas")]
impl Tx for GasEstimationFiller {
fn fill_tx_env(&self, tx_env: &mut TxEnv) {
tx_env.gas_limit = self.gas_limit;
}
}

#[cfg(feature = "call")]
pub(crate) struct CallFiller {
pub gas_limit: u64,
}

#[cfg(feature = "call")]
impl Cfg for CallFiller {
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
cfg_env.disable_base_fee = true;
cfg_env.disable_eip3607 = true;
DisableNonceCheck.fill_cfg_env(cfg_env);
}

fn fill_cfg<Db: revm::Database, Insp, Inst, Prec, Frame>(
&self,
evm: &mut revm::context::Evm<crate::helpers::Ctx<Db>, Insp, Inst, Prec, Frame>,
) {
evm.ctx.modify_cfg(|cfg_env| self.fill_cfg_env(cfg_env));

let chain_id = evm.ctx.cfg.chain_id;

evm.ctx.modify_tx(|tx_env| {
tx_env.chain_id = Some(chain_id);
});
}
}

#[cfg(feature = "call")]
impl crate::Block for CallFiller {
fn fill_block_env(&self, block_env: &mut BlockEnv) {
block_env.gas_limit = self.gas_limit;
}
}
60 changes: 4 additions & 56 deletions src/fill/mod.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,10 @@
mod alloy;

mod traits;
pub use traits::{Block, Cfg, Tx};
/// Provided fillers that adjust the [`Cfg`], [`Block`] or [`Tx`] environment.
pub mod fillers;

mod noop;
pub use noop::{NoopBlock, NoopCfg};

mod zenith;

use revm::primitives::{CfgEnv, TxEnv};

/// A [`Cfg`] that disables gas-related checks and payment of the
/// beneficiary reward, while leaving other cfg options unchanged.
///
/// ## Warning
///
/// This filler relies on the following optional features:
/// - `optional_balance_check`
/// - `optional_beneficiary_reward`
/// - `optional_gas_refund`
/// - `optional_no_base_fee`
///
///
/// It will disable the corresponding checks if the features are enabled. **If
/// none of the features are enabled, this filler will do nothing.**
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DisableGasChecks;

impl Cfg for DisableGasChecks {
#[allow(unused_variables)]
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
#[cfg(feature = "optional_balance_check")]
{
cfg_env.disable_balance_check = true;
}
#[cfg(feature = "optional_beneficiary_reward")]
{
cfg_env.disable_beneficiary_reward = true;
}
#[cfg(feature = "optional_gas_refund")]
{
cfg_env.disable_gas_refund = true;
}
#[cfg(feature = "optional_no_base_fee")]
{
cfg_env.disable_base_fee = true;
}
}
}

/// A [`Tx`] that disables the nonce check, while leaving other [`TxEnv`]
/// attributes untouched.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DisableNonceCheck;

impl Tx for DisableNonceCheck {
#[allow(unused_variables)]
fn fill_tx_env(&self, tx_env: &mut TxEnv) {
tx_env.nonce = None;
}
}
mod traits;
pub use traits::{Block, Cfg, Tx};
2 changes: 1 addition & 1 deletion src/fill/noop.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use revm::primitives::{BlockEnv, CfgEnv};
use revm::context::{BlockEnv, CfgEnv};

use crate::{Block, Cfg};

154 changes: 133 additions & 21 deletions src/fill/traits.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use crate::helpers::Ctx;
use revm::{
primitives::{BlockEnv, CfgEnv, TxEnv},
Database, Evm,
context::{BlockEnv, CfgEnv, Evm, TxEnv},
Database,
};
use std::sync::Arc;

/// Types that can fill the EVM transaction environment [`TxEnv`].
pub trait Tx: Send + Sync {
@@ -17,11 +19,43 @@ pub trait Tx: Send + Sync {
fn fill_tx_env(&self, tx_env: &mut TxEnv);

/// Fill the transaction environment on the EVM.
fn fill_tx<Ext, Db: Database>(&self, evm: &mut Evm<'_, Ext, Db>) {
let tx_env: &mut TxEnv = evm.tx_mut();
self.fill_tx_env(tx_env);
fn fill_tx<Db: Database, Insp, Inst, Prec, Frame>(
&self,
evm: &mut Evm<Ctx<Db>, Insp, Inst, Prec, Frame>,
) where
Self: Sized,
{
evm.ctx.modify_tx(|tx_env| self.fill_tx_env(tx_env));
}
}

impl Tx for TxEnv {
fn fill_tx_env(&self, tx_env: &mut TxEnv) {
*tx_env = self.clone();
}
}

impl Tx for Arc<dyn Tx> {
fn fill_tx_env(&self, tx_env: &mut TxEnv) {
self.as_ref().fill_tx_env(tx_env);
}
}

impl Tx for Box<dyn Tx> {
fn fill_tx_env(&self, tx_env: &mut TxEnv) {
self.as_ref().fill_tx_env(tx_env);
}
}

impl<T> Tx for T
where
T: Fn(&mut TxEnv) + Send + Sync,
{
fn fill_tx_env(&self, tx_env: &mut TxEnv) {
self(tx_env);
}
}

/// Types that can fill the EVM block environment [`BlockEnv`].
pub trait Block: Send + Sync {
/// Fill the block environment.
@@ -35,9 +69,13 @@ pub trait Block: Send + Sync {
fn fill_block_env(&self, block_env: &mut BlockEnv);

/// Fill the block environment on the EVM.
fn fill_block<Ext, Db: Database>(&self, evm: &mut Evm<'_, Ext, Db>) {
let block_env: &mut BlockEnv = evm.block_mut();
self.fill_block_env(block_env);
fn fill_block<Db: Database, Insp, Inst, Prec, Frame>(
&self,
evm: &mut Evm<Ctx<Db>, Insp, Inst, Prec, Frame>,
) where
Self: Sized,
{
evm.ctx.modify_block(|block_env| self.fill_block_env(block_env));
}

/// Get the transaction count hint from the filler. This can be used for
@@ -47,6 +85,33 @@ pub trait Block: Send + Sync {
}
}

impl<T> Block for T
where
T: Fn(&mut BlockEnv) + Send + Sync,
{
fn fill_block_env(&self, block_env: &mut BlockEnv) {
self(block_env);
}
}

impl Block for BlockEnv {
fn fill_block_env(&self, block_env: &mut BlockEnv) {
*block_env = self.clone();
}
}

impl Block for Arc<dyn Block> {
fn fill_block_env(&self, block_env: &mut BlockEnv) {
self.as_ref().fill_block_env(block_env);
}
}

impl Block for Box<dyn Block> {
fn fill_block_env(&self, block_env: &mut BlockEnv) {
self.as_ref().fill_block_env(block_env);
}
}

/// Types that can fill the EVM configuration environment [`CfgEnv`].
///
/// Because the CFG env has quite a few conditionally compiled properties, it
@@ -69,42 +134,89 @@ pub trait Cfg: Send + Sync {
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv);

/// Fill the configuration environment on the EVM.
fn fill_cfg<Ext, Db: Database>(&self, evm: &mut Evm<'_, Ext, Db>) {
let cfg_env: &mut CfgEnv = evm.cfg_mut();
self.fill_cfg_env(cfg_env);
fn fill_cfg<Db: Database, Insp, Inst, Prec, Frame>(
&self,
evm: &mut Evm<Ctx<Db>, Insp, Inst, Prec, Frame>,
) where
Self: Sized,
{
evm.ctx.modify_cfg(|cfg_env| self.fill_cfg_env(cfg_env));
}
}

impl Cfg for Arc<dyn Cfg> {
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
self.as_ref().fill_cfg_env(cfg_env);
}
}

impl Cfg for CfgEnv {
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
*cfg_env = self.clone();
}
}

impl Cfg for Box<dyn Cfg> {
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
self.as_ref().fill_cfg_env(cfg_env);
}
}

impl<T> Cfg for T
where
T: Fn(&mut CfgEnv) + Send + Sync,
{
fn fill_cfg_env(&self, cfg_env: &mut CfgEnv) {
self(cfg_env);
}
}

#[cfg(test)]
mod test {
use alloy::consensus::constants::GWEI_TO_WEI;
use alloy_primitives::{B256, U256};
use revm::primitives::{BlobExcessGasAndPrice, BlockEnv, CfgEnv};
use alloy::{
consensus::constants::GWEI_TO_WEI,
primitives::{B256, U256},
};
use revm::{
context::{BlockEnv, CfgEnv},
context_interface::block::BlobExcessGasAndPrice,
};

use super::*;

#[allow(dead_code)]
fn object_safety(cfg: Box<dyn Cfg>, block: Box<dyn Block>, tx: Box<dyn Tx>) {
crate::test_utils::test_trevm().fill_cfg(&cfg).fill_block(&block).fill_tx(&tx);

unimplemented!("compilation check only")
}

impl Block for () {
fn fill_block_env(&self, block_env: &mut BlockEnv) {
let BlockEnv {
number,
coinbase,
beneficiary,
timestamp,
gas_limit,
basefee,
difficulty,
prevrandao,
blob_excess_gas_and_price,
} = block_env;
*number = U256::from(1);
*coinbase = Default::default();
*timestamp = U256::from(1720450148); // Time when I was writing the test code
*gas_limit = U256::from(30_000_000);
*basefee = U256::from(5 * GWEI_TO_WEI);
*number = U256::ONE;
*beneficiary = Default::default();
*timestamp = U256::from(1720450148u64); // Time when I was writing the test code
*gas_limit = 30_000_000;
*basefee = 5 * GWEI_TO_WEI;

let diff = B256::repeat_byte(0xab);
*prevrandao = Some(diff);
*difficulty = U256::from_be_bytes(diff.into());
*blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(1_000_000));

*blob_excess_gas_and_price = Some(BlobExcessGasAndPrice::new(
1_000_000,
revm::primitives::eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE,
));
}

fn tx_count_hint(&self) -> Option<usize> {
81 changes: 0 additions & 81 deletions src/fill/zenith.rs

This file was deleted.

27 changes: 27 additions & 0 deletions src/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use revm::{
context::{BlockEnv, CfgEnv, TxEnv},
context_interface::context::ContextError,
handler::{instructions::EthInstructions, EthFrame, EthPrecompiles},
inspector::NoOpInspector,
interpreter::{interpreter::EthInterpreter, InstructionContext, InterpreterTypes},
Context, Database, Journal,
};

/// [`revm::Context`] with default env types and adjustable DB
pub type Ctx<Db, J = Journal<Db>, C = ()> = Context<BlockEnv, TxEnv, CfgEnv, Db, J, C>;

/// EVM with default env types and adjustable DB.
pub type Evm<Db, Insp = NoOpInspector, Inst = Instructions<Db>, Prec = EthPrecompiles> =
revm::context::Evm<Ctx<Db>, Insp, Inst, Prec, EthFrame>;

/// Handler table for EVM opcodes.
pub type Instructions<Db> = EthInstructions<EthInterpreter, Ctx<Db>>;

/// The handler type for an EVM opcode.
pub type Instruction<Db> = revm::interpreter::Instruction<EthInterpreter, Ctx<Db>>;

/// An [`Instruction`] that sets a [`ContextError`] in the [`Ctx`] whenever it
/// is executed.
pub fn forbidden<Db: Database, Int: InterpreterTypes>(ctx: InstructionContext<'_, Ctx<Db>, Int>) {
ctx.host.error = Err(ContextError::Custom("forbidden opcode".to_string()));
}
129 changes: 129 additions & 0 deletions src/inspectors/layer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
use revm::{
interpreter::{
CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, InterpreterTypes,
},
primitives::{Address, Log, U256},
Inspector,
};

/// A layer in a stack of inspectors. Contains its own inspector and an
/// inner inspector. This is used to create a stack of inspectors that can
/// be used to inspect the execution of a contract.
///
/// Use `Layered` when you need to retain type information about the inner
/// inspectors.
///
/// The current inspector will be invoked first, then the inner inspector.
/// For functions that may return values (e.g. [`Inspector::call`]), if the
/// current inspector returns a value, the inner inspector will not be invoked.
#[derive(Clone, Debug)]
pub struct Layered<Outer, Inner> {
outer: Outer,
inner: Inner,
}

impl<Outer, Inner> Layered<Outer, Inner> {
/// Create a new [`Layered`] inspector with the given current and inner
/// inspectors.
pub const fn new(outer: Outer, inner: Inner) -> Self {
Self { outer, inner }
}

/// Wrap this inspector in another, creating a new [`Layered`] inspector.
/// with this as the inner inspector.
pub const fn wrap_in<Other>(self, outer: Other) -> Layered<Other, Self> {
Layered { outer, inner: self }
}

/// Wrap this inspector around another, creating a new [`Layered`] inspector
/// with this as the outer inspector.
pub const fn wrap_around<Other>(self, inner: Other) -> Layered<Self, Other> {
Layered { outer: self, inner }
}

/// Decompose the [`Layered`] inspector into its outer and inner
/// inspectors.
pub fn into_parts(self) -> (Outer, Inner) {
(self.outer, self.inner)
}

/// Get a reference to the outer inspector.
pub const fn outer(&self) -> &Outer {
&self.outer
}

/// Get a mutable reference to the outer inspector.
pub const fn outer_mut(&mut self) -> &mut Outer {
&mut self.outer
}

/// Get a reference to the inner inspector.
pub const fn inner(&self) -> &Inner {
&self.inner
}

/// Get a mutable reference to the inner inspector.
pub const fn inner_mut(&mut self) -> &mut Inner {
&mut self.inner
}
}

impl<Ctx, Int: InterpreterTypes, Outer, Inner> Inspector<Ctx, Int> for Layered<Outer, Inner>
where
Outer: Inspector<Ctx, Int>,
Inner: Inspector<Ctx, Int>,
{
fn initialize_interp(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
self.outer.initialize_interp(interp, context);
self.inner.initialize_interp(interp, context);
}

fn step(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
self.outer.step(interp, context);
self.inner.step(interp, context);
}

fn step_end(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
self.outer.step_end(interp, context);
self.inner.step_end(interp, context);
}

fn log(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx, log: Log) {
self.outer.log(interp, context, log.clone());
self.inner.log(interp, context, log);
}

fn call(&mut self, context: &mut Ctx, inputs: &mut CallInputs) -> Option<CallOutcome> {
if let Some(outcome) = self.outer.call(context, inputs) {
return Some(outcome);
}
self.inner.call(context, inputs)
}

fn call_end(&mut self, context: &mut Ctx, inputs: &CallInputs, outcome: &mut CallOutcome) {
self.outer.call_end(context, inputs, outcome);
self.inner.call_end(context, inputs, outcome);
}

fn create(&mut self, context: &mut Ctx, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
if let Some(outcome) = self.outer.create(context, inputs) {
return Some(outcome);
}
self.inner.create(context, inputs)
}

fn create_end(
&mut self,
context: &mut Ctx,
inputs: &CreateInputs,
outcome: &mut CreateOutcome,
) {
self.outer.create_end(context, inputs, outcome);
self.inner.create_end(context, inputs, outcome);
}

fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
self.outer.selfdestruct(contract, target, value);
self.inner.selfdestruct(contract, target, value);
}
}
48 changes: 48 additions & 0 deletions src/inspectors/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
mod layer;
pub use layer::Layered;

mod timeout;
pub use timeout::TimeLimit;

#[cfg(feature = "tracing-inspectors")]
mod tracing;
#[cfg(feature = "tracing-inspectors")]
pub use tracing::TracingInspectorOutput;

mod set;
pub use set::InspectorSet;

mod spanning;
pub use spanning::SpanningInspector;

mod with_output;
pub use with_output::InspectorWithOutput;

#[cfg(test)]
mod test {
use super::*;
use crate::{test_utils::TestInspector, NoopBlock, NoopCfg};
use revm::{database::InMemoryDB, inspector::InspectorEvmTr, primitives::B256};
use std::time::Duration;

#[test]
fn test_timeout() {
let inspector =
Layered::new(TimeLimit::new(Duration::from_micros(10)), SpanningInspector::at_info())
.wrap_around(TestInspector::default());

let mut trevm = crate::TrevmBuilder::new()
.with_db(InMemoryDB::default())
.with_insp(inspector)
.build_trevm()
.unwrap()
.fill_cfg(&NoopCfg)
.fill_block(&NoopBlock);

let err = trevm.apply_eip4788(B256::repeat_byte(0xaa)).unwrap_err();
assert!(matches!(err, revm::context::result::EVMError::Custom(_)));
assert!(format!("{err}").contains("timeout during evm execution"));

assert!(trevm.inner_mut_unchecked().inspector().outer().outer().has_elapsed());
}
}
130 changes: 130 additions & 0 deletions src/inspectors/set.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use std::collections::VecDeque;

use revm::{
interpreter::{
CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter, InterpreterTypes,
},
primitives::{Address, Log, U256},
Inspector,
};

/// A stack of [`Inspector`]s.
///
/// This is a thin wrapper around a [`VecDeque`] of inspectors.
#[derive(Default)]
pub struct InspectorSet<Ctx, Int> {
inspectors: VecDeque<Box<dyn Inspector<Ctx, Int>>>,
}

impl<Ctx, Int> core::ops::Deref for InspectorSet<Ctx, Int> {
type Target = VecDeque<Box<dyn Inspector<Ctx, Int>>>;

fn deref(&self) -> &Self::Target {
&self.inspectors
}
}

impl<Ctx, Int> core::ops::DerefMut for InspectorSet<Ctx, Int> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inspectors
}
}

impl<Ctx, Int> core::fmt::Debug for InspectorSet<Ctx, Int> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("InspectorStack").field("inspectors", &self.inspectors.len()).finish()
}
}

impl<Ctx, Int> InspectorSet<Ctx, Int>
where
Int: InterpreterTypes,
{
/// Instantiate a new empty inspector stack.
pub fn new() -> Self {
Self { inspectors: Default::default() }
}

/// Instantiate a new empty stack with pre-allocated capacity.
pub fn with_capacity(cap: usize) -> Self {
Self { inspectors: VecDeque::with_capacity(cap) }
}

/// Push an inspector to the back of the stack.
pub fn push_back<I: Inspector<Ctx, Int> + 'static>(&mut self, inspector: I) {
self.inspectors.push_back(Box::new(inspector));
}

/// Push an inspector to the front of the stack.
pub fn push_front<I: Inspector<Ctx, Int> + 'static>(&mut self, inspector: I) {
self.inspectors.push_front(Box::new(inspector));
}

/// Pop an inspector from the back of the stack.
pub fn pop_back(&mut self) -> Option<Box<dyn Inspector<Ctx, Int>>> {
self.inspectors.pop_back()
}

/// Pop an inspector from the front of the stack.
pub fn pop_front(&mut self) -> Option<Box<dyn Inspector<Ctx, Int>>> {
self.inspectors.pop_front()
}
}

impl<Ctx, Int> Inspector<Ctx, Int> for InspectorSet<Ctx, Int>
where
Int: InterpreterTypes,
{
fn initialize_interp(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
self.inspectors.iter_mut().for_each(|i| i.initialize_interp(interp, context));
}

fn step(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
self.inspectors.iter_mut().for_each(|i| i.step(interp, context));
}

fn step_end(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx) {
self.inspectors.iter_mut().for_each(|i| i.step_end(interp, context));
}

fn log(&mut self, interp: &mut Interpreter<Int>, context: &mut Ctx, log: Log) {
self.inspectors.iter_mut().for_each(|i| i.log(interp, context, log.clone()));
}

fn call(&mut self, context: &mut Ctx, inputs: &mut CallInputs) -> Option<CallOutcome> {
for inspector in self.inspectors.iter_mut() {
let outcome = inspector.call(context, inputs);
if outcome.is_some() {
return outcome;
}
}
None
}

fn call_end(&mut self, context: &mut Ctx, inputs: &CallInputs, outcome: &mut CallOutcome) {
self.inspectors.iter_mut().for_each(|i| i.call_end(context, inputs, outcome))
}

fn create(&mut self, context: &mut Ctx, inputs: &mut CreateInputs) -> Option<CreateOutcome> {
for inspector in self.inspectors.iter_mut() {
let outcome = inspector.create(context, inputs);
if outcome.is_some() {
return outcome;
}
}
None
}

fn create_end(
&mut self,
context: &mut Ctx,
inputs: &CreateInputs,
outcome: &mut CreateOutcome,
) {
self.inspectors.iter_mut().for_each(|i| i.create_end(context, inputs, outcome))
}

fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
self.inspectors.iter_mut().for_each(|i| i.selfdestruct(contract, target, value))
}
}
Loading