From 5347cb834a8eb9ac4155a581a97bcbe4b3b1a415 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 25 Sep 2025 10:20:17 -0400 Subject: [PATCH 1/3] wip: split evm --- Cargo.toml | 2 +- src/db/mod.rs | 2 +- src/db/traits.rs | 34 + src/driver/bundle.rs | 2 +- src/evm.rs | 2475 ---------------------------- src/evm/all_states.rs | 920 +++++++++++ src/{ => evm}/builder.rs | 52 +- src/evm/errored.rs | 135 ++ src/{connect.rs => evm/factory.rs} | 89 +- src/evm/has_block.rs | 129 ++ src/evm/has_cfg.rs | 318 ++++ src/evm/has_tx.rs | 350 ++++ src/evm/mod.rs | 22 + src/evm/need_block.rs | 183 ++ src/evm/need_cfg.rs | 65 + src/evm/need_tx.rs | 224 +++ src/evm/ready.rs | 337 ++++ src/{ => evm}/states.rs | 0 src/evm/struct.rs | 48 + src/evm/transacted.rs | 263 +++ src/lib.rs | 23 +- src/test_utils.rs | 3 +- 22 files changed, 3144 insertions(+), 2532 deletions(-) delete mode 100644 src/evm.rs create mode 100644 src/evm/all_states.rs rename src/{ => evm}/builder.rs (55%) create mode 100644 src/evm/errored.rs rename src/{connect.rs => evm/factory.rs} (56%) create mode 100644 src/evm/has_block.rs create mode 100644 src/evm/has_cfg.rs create mode 100644 src/evm/has_tx.rs create mode 100644 src/evm/mod.rs create mode 100644 src/evm/need_block.rs create mode 100644 src/evm/need_cfg.rs create mode 100644 src/evm/need_tx.rs create mode 100644 src/evm/ready.rs rename src/{ => evm}/states.rs (100%) create mode 100644 src/evm/struct.rs create mode 100644 src/evm/transacted.rs diff --git a/Cargo.toml b/Cargo.toml index 97b9a2a..d095f77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,7 +86,7 @@ concurrent-db = ["dep:dashmap"] estimate_gas = ["optional_eip3607", "optional_no_base_fee", "dep:tracing"] -test-utils = ["revm/std", "revm/serde-json", "revm/alloydb"] +test-utils = ["revm/std", "revm/serde-json", "revm/alloydb", "alloy/signers", "alloy/signer-local"] secp256k1 = ["revm/secp256k1"] c-kzg = ["revm/c-kzg"] diff --git a/src/db/mod.rs b/src/db/mod.rs index aad4c92..960cb35 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -4,7 +4,7 @@ pub mod sync; /// Database abstraction traits. mod traits; -pub use traits::{ArcUpgradeError, CachingDb, StateAcc, TryCachingDb, TryStateAcc}; +pub use traits::{ArcUpgradeError, CachingDb, DbConnect, StateAcc, TryCachingDb, TryStateAcc}; /// Cache-on-write database. A memory cache that caches only on write, not on /// read. Intended to wrap some other caching database. diff --git a/src/db/traits.rs b/src/db/traits.rs index fd515b2..232fe75 100644 --- a/src/db/traits.rs +++ b/src/db/traits.rs @@ -5,6 +5,40 @@ use revm::{ }; use std::{collections::BTreeMap, convert::Infallible, sync::Arc}; +/// Trait for types that can be used to connect to a database. +/// +/// Connectors should contain configuration information like filesystem paths. +/// They are intended to enable parallel instantiation of multiple EVMs in +/// multiple threads sharing some database configuration +/// +/// The lifetime on this trait allows the resulting DB to borrow from the +/// 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: Sync { + /// The database type returned when connecting. + type Database: Database; + + /// The error type returned when connecting to the database. + type Error: core::error::Error; + + /// Connect to the database. + fn connect(&self) -> Result; +} + +impl DbConnect for Db +where + Db: Database + Clone + Sync, +{ + type Database = Self; + + type Error = Infallible; + + fn connect(&self) -> Result { + Ok(self.clone()) + } +} + /// 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 diff --git a/src/driver/bundle.rs b/src/driver/bundle.rs index 7a83219..ac1c946 100644 --- a/src/driver/bundle.rs +++ b/src/driver/bundle.rs @@ -1,4 +1,4 @@ -use crate::{helpers::Ctx, states::EvmBundleDriverErrored, EvmNeedsTx}; +use crate::{helpers::Ctx, EvmBundleDriverErrored, EvmNeedsTx}; use revm::{ context::result::EVMError, inspector::NoOpInspector, Database, DatabaseCommit, Inspector, }; diff --git a/src/evm.rs b/src/evm.rs deleted file mode 100644 index cfeaf70..0000000 --- a/src/evm.rs +++ /dev/null @@ -1,2475 +0,0 @@ -use crate::{ - db::{StateAcc, TryStateAcc}, - driver::DriveBlockResult, - helpers::{Ctx, Evm, Instruction}, - inspectors::Layered, - Block, BlockDriver, BundleDriver, Cfg, ChainDriver, DriveBundleResult, DriveChainResult, - ErroredState, EvmErrored, EvmExtUnchecked, EvmNeedsBlock, EvmNeedsCfg, EvmNeedsTx, EvmReady, - EvmTransacted, HasBlock, HasCfg, HasTx, NeedsCfg, NeedsTx, TransactedState, Tx, -}; -use alloy::{ - primitives::{Address, Bytes, U256}, - rpc::types::{state::StateOverride, BlockOverrides}, -}; -use core::{convert::Infallible, fmt}; -use revm::{ - bytecode::opcode::DIFFICULTY, - context::{ - result::{EVMError, ExecutionResult, InvalidTransaction, ResultAndState}, - Block as _, BlockEnv, Cfg as _, ContextSetters, ContextTr, Transaction as _, TxEnv, - }, - database::{states::bundle_state::BundleRetention, BundleState, TryDatabaseCommit}, - handler::EthPrecompiles, - inspector::NoOpInspector, - interpreter::{gas::calculate_initial_tx_gas_for_tx, instructions::block_info}, - primitives::{hardfork::SpecId, TxKind}, - state::{AccountInfo, Bytecode, EvmState}, - Database, DatabaseCommit, DatabaseRef, InspectEvm, Inspector, -}; - -/// Trevm provides a type-safe interface to the EVM, using the typestate pattern. -/// -/// See the [crate-level documentation](crate) for more information. -pub struct Trevm -where - Db: Database, - Insp: Inspector>, -{ - pub(crate) inner: Box>, - pub(crate) state: TrevmState, -} - -impl fmt::Debug for Trevm -where - Db: Database, - Insp: Inspector>, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Trevm").finish_non_exhaustive() - } -} - -impl AsRef> for Trevm -where - Db: Database, - Insp: Inspector>, -{ - fn as_ref(&self) -> &Evm { - &self.inner - } -} - -impl From> for EvmNeedsCfg -where - Db: Database, - Insp: Inspector>, -{ - fn from(inner: Evm) -> Self { - Self { inner: Box::new(inner), state: NeedsCfg::new() } - } -} - -// --- ALL STATES - -impl Trevm -where - Db: Database, - Insp: Inspector>, -{ - /// Get a reference to the current [`Evm`]. - /// - /// [`Evm`]: revm::context::Evm - pub fn inner(&self) -> &Evm { - self.as_ref() - } - - /// Get a mutable reference to the current [`Evm`]. This should be used with - /// caution, as modifying the EVM may lead to inconsistent Trevmstate or - /// invalid execution. - /// - /// [`Evm`]: revm::context::Evm - pub fn inner_mut_unchecked(&mut self) -> &mut Evm { - &mut self.inner - } - - /// Destructure the [`Trevm`] into its inner EVM. - pub fn into_inner(self) -> Box> { - self.inner - } - - /// Deconstruct the [`Trevm`] into the backing DB, dropping all other types. - pub fn into_db(self) -> Db { - self.inner.ctx.journaled_state.database - } - - /// Get a reference to the inspector. - pub fn inspector(&self) -> &Insp { - &self.inner.inspector - } - - /// Get a mutable reference to the inspector. - pub fn inspector_mut(&mut self) -> &mut Insp { - &mut self.inner.inspector - } - - /// Replace the current inspector with a new inspector of the same type. - pub fn replace_inspector(mut self, inspector: Insp) -> Self { - *self.inspector_mut() = inspector; - self - } - - /// Layer a new inspector on top of the current one. - pub fn layer_inspector( - self, - inspector: Insp2, - ) -> Trevm, TrevmState> - where - Insp2: Inspector>, - { - let inner = Box::new(Evm { - ctx: self.inner.ctx, - inspector: Layered::new(inspector, self.inner.inspector), - instruction: self.inner.instruction, - precompiles: self.inner.precompiles, - frame_stack: self.inner.frame_stack, - }); - Trevm { inner, state: self.state } - } - - /// Take the inspector out of the Trevm, replacing it with a - /// [`NoOpInspector`]. - pub fn take_inspector(self) -> (Insp, Trevm) { - let inspector = self.inner.inspector; - let inner = Box::new(Evm { - ctx: self.inner.ctx, - inspector: NoOpInspector, - instruction: self.inner.instruction, - precompiles: self.inner.precompiles, - frame_stack: self.inner.frame_stack, - }); - (inspector, Trevm { inner, state: self.state }) - } - - /// Replace the current inspector with a new one, dropping the old one. - pub fn set_inspector(self, inspector: Insp2) -> Trevm - where - Insp2: Inspector>, - { - let inner = Box::new(Evm { - ctx: self.inner.ctx, - inspector, - instruction: self.inner.instruction, - precompiles: self.inner.precompiles, - frame_stack: self.inner.frame_stack, - }); - Trevm { inner, state: self.state } - } - - /// Run the closure with a different inspector, then restore the previous - /// one. - pub fn with_inspector( - self, - inspector: Insp2, - f: F, - ) -> Trevm - where - Insp2: Inspector>, - F: FnOnce(Trevm) -> Trevm, - { - let (old, this) = self.take_inspector(); - let this = f(this.set_inspector(inspector)); - this.set_inspector(old) - } - - /// Run a fallible function with the provided inspector, then restore the - /// previous inspector. If the function returns an error, it will be - /// wrapped in an [`EvmErrored`] along with the current EVM state. - pub fn try_with_inspector( - self, - inspector: Insp2, - f: F, - ) -> Result, EvmErrored> - where - Insp2: Inspector>, - F: FnOnce( - Trevm, - ) -> Result, EvmErrored>, - { - let (previous, this) = self.take_inspector(); - let this = this.set_inspector(inspector); - - match f(this) { - Ok(evm) => Ok(evm.set_inspector(previous)), - Err(evm) => Err(evm.set_inspector(previous)), - } - } - - /// Get the id of the currently running hardfork spec. - pub fn spec_id(&self) -> SpecId { - self.inner.ctx.cfg().spec() - } - - /// Set the [SpecId], modifying the EVM handlers accordingly. This function - /// should be called at hardfork boundaries when running multi-block trevm - /// flows. - pub fn set_spec_id(&mut self, spec_id: SpecId) { - self.inner.modify_cfg(|cfg| cfg.spec = spec_id); - } - - /// Run a closure with a different [SpecId], then restore the previous - /// setting. - pub fn with_spec_id(mut self, spec_id: SpecId, f: F) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let old = self.spec_id(); - self.set_spec_id(spec_id); - let mut this = f(self); - this.set_spec_id(old); - this - } - - /// Convert self into [`EvmErrored`] by supplying an error - pub fn errored(self, error: E) -> EvmErrored { - EvmErrored { inner: self.inner, state: ErroredState { error } } - } - - /// Apply [`StateOverride`]s to the current state. Errors if the overrides - /// contain invalid bytecode. - pub fn apply_state_overrides( - mut self, - overrides: &StateOverride, - ) -> Result::Error>> - where - Db: DatabaseCommit, - { - for (address, account_override) in overrides { - if let Some(balance) = account_override.balance { - self.inner.set_balance(*address, balance).map_err(EVMError::Database)?; - } - if let Some(nonce) = account_override.nonce { - self.inner.set_nonce(*address, nonce).map_err(EVMError::Database)?; - } - if let Some(code) = account_override.code.as_ref() { - self.inner - .set_bytecode( - *address, - Bytecode::new_raw_checked(code.clone()) - .map_err(|_| EVMError::Custom("Invalid bytecode".to_string()))?, - ) - .map_err(EVMError::Database)?; - } - if let Some(state) = account_override.state.as_ref() { - for (slot, value) in state { - self.inner - .set_storage( - *address, - U256::from_be_bytes((*slot).into()), - U256::from_be_bytes((*value).into()), - ) - .map_err(EVMError::Database)?; - } - } - } - Ok(self) - } - - /// Apply [`StateOverride`]s to the current state, if they are provided. - pub fn maybe_apply_state_overrides( - self, - overrides: Option<&StateOverride>, - ) -> Result::Error>> - where - Db: DatabaseCommit, - { - if let Some(overrides) = overrides { - self.apply_state_overrides(overrides) - } else { - Ok(self) - } - } - - /// Overide an opcode with a custom handler. Returns the previous - /// instruction handler for the opcode. - pub fn override_opcode(&mut self, opcode: u8, handler: Instruction) -> Instruction { - std::mem::replace(&mut self.inner.instruction.instruction_table[opcode as usize], handler) - } - - /// Disable an opcode by replacing it with unknown opcode behavior. This is - /// a shortcut for [`Self::override_opcode`] with [`crate::helpers::forbidden`]. - pub fn disable_opcode(&mut self, opcode: u8) -> Instruction { - self.override_opcode(opcode, crate::helpers::forbidden) - } - - /// Run some closure with an opcode override, then restore the previous - /// setting. - pub fn with_opcode_override( - mut self, - opcode: u8, - handler: Instruction, - f: F, - ) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let old = self.override_opcode(opcode, handler); - self.inner.instruction.insert_instruction(opcode, handler); - let mut this = f(self); - this.override_opcode(opcode, old); - this - } - - /// Disable the prevrandao opcode, by replacing it with unknown opcode - /// behavior. This is useful for block simulation, where the prevrandao - /// opcode may produce incorrect results. - pub fn disable_prevrandao(&mut self) -> Instruction { - self.disable_opcode(DIFFICULTY) - } - - /// Enable the prevrandao opcode. If the prevrandao opcode was not - /// previously disabled or replaced, this will have no effect on behavior. - pub fn enable_prevrandao(&mut self) -> Instruction { - self.override_opcode(DIFFICULTY, block_info::difficulty) - } - - /// Run some code with the prevrandao opcode disabled, then restore the - /// previous setting. This is useful for block simulation, where the - /// prevrandao opcode may produce incorrect results. - pub fn without_prevrandao(self, f: F) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - self.with_opcode_override(DIFFICULTY, crate::helpers::forbidden, f) - } - - /// Set the precompiles for the EVM. This will replace the current - /// precompiles with the provided ones. - pub fn override_precompiles(&mut self, precompiles: EthPrecompiles) -> EthPrecompiles { - std::mem::replace(&mut self.inner.precompiles, precompiles) - } - - /// Run a closure with a different set of precompiles, then restore the - /// previous setting. - pub fn with_precompiles( - mut self, - precompiles: EthPrecompiles, - f: F, - ) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let old = self.override_precompiles(precompiles); - let mut this = f(self); - this.override_precompiles(old); - this - } -} - -// Fallible DB Reads with &mut self -impl Trevm -where - Db: Database, - Insp: Inspector>, -{ - /// Get the current account info for a specific address. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn try_read_account( - &mut self, - address: Address, - ) -> Result, ::Error> { - self.inner.db_mut().basic(address) - } - - /// Get the current nonce for a specific address - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn try_read_nonce(&mut self, address: Address) -> Result::Error> { - self.try_read_account(address).map(|a| a.map(|a| a.nonce).unwrap_or_default()) - } - - /// Get the current nonce for a specific address - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn try_read_balance(&mut self, address: Address) -> Result::Error> { - self.try_read_account(address).map(|a| a.map(|a| a.balance).unwrap_or_default()) - } - - /// Get the value of a storage slot. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn try_read_storage( - &mut self, - address: Address, - slot: U256, - ) -> Result::Error> { - self.inner.db_mut().storage(address, slot) - } - - /// Get the code at the given account, if any. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn try_read_code( - &mut self, - address: Address, - ) -> Result, ::Error> { - let acct_info = self.try_read_account(address)?; - match acct_info { - Some(acct) => Ok(Some(self.inner.db_mut().code_by_hash(acct.code_hash)?)), - None => Ok(None), - } - } - - /// Get the gas allowance for a specific caller and gas price. - pub fn try_gas_allowance( - &mut self, - caller: Address, - gas_price: u128, - ) -> Result::Error> { - if gas_price == 0 { - return Ok(u64::MAX); - } - let gas_price = U256::from(gas_price); - let balance = self.try_read_balance(caller)?; - Ok((balance / gas_price).saturating_to()) - } -} - -// Fallible DB Reads with &self -impl Trevm -where - Db: Database + DatabaseRef, - Insp: Inspector>, -{ - /// Get the current account info for a specific address. - pub fn try_read_account_ref( - &self, - address: Address, - ) -> Result, ::Error> { - self.inner.db_ref().basic_ref(address) - } - - /// Get the current nonce for a specific address - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn try_read_nonce_ref(&self, address: Address) -> Result::Error> { - self.try_read_account_ref(address).map(|a| a.map(|a| a.nonce).unwrap_or_default()) - } - - /// Get the current nonce for a specific address - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn try_read_balance_ref( - &self, - address: Address, - ) -> Result::Error> { - self.try_read_account_ref(address).map(|a| a.map(|a| a.balance).unwrap_or_default()) - } - - /// Get the value of a storage slot. - pub fn try_read_storage_ref( - &self, - address: Address, - slot: U256, - ) -> Result::Error> { - self.inner.db_ref().storage_ref(address, slot) - } - - /// Get the code at the given account, if any. - pub fn try_read_code_ref( - &self, - address: Address, - ) -> Result, ::Error> { - let acct_info = self.try_read_account_ref(address)?; - match acct_info { - Some(acct) => Ok(Some(self.inner.db_ref().code_by_hash_ref(acct.code_hash)?)), - None => Ok(None), - } - } - - /// Get the gas allowance for a specific caller and gas price. - pub fn try_gas_allowance_ref( - &self, - caller: Address, - gas_price: U256, - ) -> Result::Error> { - if gas_price.is_zero() { - return Ok(u64::MAX); - } - let gas_price = U256::from(gas_price); - let balance = self.try_read_balance_ref(caller)?; - Ok((balance / gas_price).saturating_to()) - } -} - -// Infallible DB Reads with &mut self -impl Trevm -where - Db: Database, - Insp: Inspector>, -{ - /// Get the current account info for a specific address. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn read_account(&mut self, address: Address) -> Option { - self.inner.db_mut().basic(address).expect("infallible") - } - - /// Get the current nonce for a specific address - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn read_nonce(&mut self, address: Address) -> u64 { - self.read_account(address).map(|a: AccountInfo| a.nonce).unwrap_or_default() - } - - /// Get the current nonce for a specific address - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn read_balance(&mut self, address: Address) -> U256 { - self.read_account(address).map(|a: AccountInfo| a.balance).unwrap_or_default() - } - - /// Get the value of a storage slot. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn read_storage(&mut self, address: Address, slot: U256) -> U256 { - self.inner.db_mut().storage(address, slot).expect("infallible") - } - - /// Get the code at the given account, if any. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn read_code(&mut self, address: Address) -> Option { - let acct_info = self.read_account(address)?; - Some(self.inner.db_mut().code_by_hash(acct_info.code_hash).expect("infallible")) - } -} - -// Infalible DB Reads with &self -impl Trevm -where - Db: Database + DatabaseRef, - Insp: Inspector>, -{ - /// Get the current account info for a specific address. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn read_account_ref(&self, address: Address) -> Option { - self.inner.db_ref().basic_ref(address).expect("infallible") - } - - /// Get the current nonce for a specific address - pub fn read_nonce_ref(&self, address: Address) -> u64 { - self.read_account_ref(address).map(|a: AccountInfo| a.nonce).unwrap_or_default() - } - - /// Get the current nonce for a specific address - pub fn read_balance_ref(&self, address: Address) -> U256 { - self.read_account_ref(address).map(|a: AccountInfo| a.balance).unwrap_or_default() - } - - /// Get the value of a storage slot. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn read_storage_ref(&self, address: Address, slot: U256) -> U256 { - self.inner.db_ref().storage_ref(address, slot).expect("infallible") - } - - /// Get the code at the given account, if any. - /// - /// Note: due to revm's DB model, this requires a mutable pointer. - pub fn read_code_ref(&self, address: Address) -> Option { - let acct_info = self.read_account_ref(address)?; - Some(self.inner.db_ref().code_by_hash_ref(acct_info.code_hash).expect("infallible")) - } -} - -impl Trevm -where - Db: Database, - Insp: Inspector>, -{ - /// Commit a set of state changes to the database. This is a low-level API, - /// and is not intended for general use. Regular users should prefer - /// executing a transaction. - pub fn commit_unchecked(&mut self, state: EvmState) - where - Db: DatabaseCommit, - { - self.inner.db_mut().commit(state); - } - - /// Modify an account with a closure and commit the modified account. This - /// is a low-level API, and is not intended for general use. - pub fn try_modify_account_unchecked( - &mut self, - address: Address, - f: F, - ) -> Result::Error> - where - Db: DatabaseCommit, - { - self.inner.modify_account(address, f) - } - - /// Set the nonce of an account, returning the previous nonce. This is a - /// low-level API, and is not intended for general use. - pub fn try_set_nonce_unchecked( - &mut self, - address: Address, - nonce: u64, - ) -> Result::Error> - where - Db: DatabaseCommit, - { - self.inner.set_nonce(address, nonce) - } - - /// Increment the nonce of an account, returning the previous nonce. This is - /// a low-level API, and is not intended for general use. - /// - /// If the nonce is already at the maximum value, it will not be - /// incremented. - pub fn try_increment_nonce_unchecked( - &mut self, - address: Address, - ) -> Result::Error> - where - Db: DatabaseCommit, - { - self.inner.increment_nonce(address) - } - - /// Decrement the nonce of an account, returning the previous nonce. This is - /// a low-level API, and is not intended for general use. - /// - /// If the nonce is already 0, it will not be decremented. - pub fn try_decrement_nonce_unchecked( - &mut self, - address: Address, - ) -> Result::Error> - where - Db: DatabaseCommit, - { - self.inner.decrement_nonce(address) - } - - /// Set the EVM storage at a slot. This is a low-level API, and is not - /// intended for general use. - pub fn try_set_storage_unchecked( - &mut self, - address: Address, - slot: U256, - value: U256, - ) -> Result::Error> - where - Db: DatabaseCommit, - { - self.inner.set_storage(address, slot, value) - } - - /// Set the bytecode at a specific address, returning the previous bytecode - /// at that address. This is a low-level API, and is not intended for - /// general use. - pub fn try_set_bytecode_unchecked( - &mut self, - address: Address, - bytecode: Bytecode, - ) -> Result, ::Error> - where - Db: DatabaseCommit, - { - self.inner.set_bytecode(address, bytecode) - } - - /// Increase the balance of an account. Returns the previous balance. This - /// is a low-level API, and is not intended for general use. - /// - /// If this would cause an overflow, the balance will be increased to the - /// maximum value. - pub fn try_increase_balance_unchecked( - &mut self, - address: Address, - amount: U256, - ) -> Result::Error> - where - Db: DatabaseCommit, - { - self.inner.increase_balance(address, amount) - } - - /// Decrease the balance of an account. Returns the previous balance. This - /// is a low-level API, and is not intended for general use. - /// - /// If this would cause an underflow, the balance will be decreased to 0. - pub fn try_decrease_balance_unchecked( - &mut self, - address: Address, - amount: U256, - ) -> Result::Error> - where - Db: DatabaseCommit, - { - self.inner.decrease_balance(address, amount) - } - - /// Set the balance of an account. Returns the previous balance. This is a - /// low-level API, and is not intended for general use. - pub fn try_set_balance_unchecked( - &mut self, - address: Address, - amount: U256, - ) -> Result::Error> - where - Db: DatabaseCommit, - { - self.inner.set_balance(address, amount) - } -} - -impl Trevm -where - Db: Database, - Insp: Inspector>, -{ - /// Modify an account with a closure and commit the modified account. This - /// is a low-level API, and is not intended for general use. - pub fn modify_account_unchecked( - &mut self, - address: Address, - f: impl FnOnce(&mut AccountInfo), - ) -> AccountInfo - where - Db: DatabaseCommit, - { - self.try_modify_account_unchecked(address, f).expect("infallible") - } - - /// Set the nonce of an account, returning the previous nonce. This is a - /// low-level API, and is not intended for general use. - pub fn set_nonce_unchecked(&mut self, address: Address, nonce: u64) -> u64 - where - Db: DatabaseCommit, - { - self.try_set_nonce_unchecked(address, nonce).expect("infallible") - } - - /// Increment the nonce of an account, returning the previous nonce. This is - /// a low-level API, and is not intended for general use. - /// - /// If this would cause the nonce to overflow, the nonce will be set to the - /// maximum value. - pub fn increment_nonce_unchecked(&mut self, address: Address) -> u64 - where - Db: DatabaseCommit, - { - self.try_increment_nonce_unchecked(address).expect("infallible") - } - - /// Decrement the nonce of an account, returning the previous nonce. This is - /// a low-level API, and is not intended for general use. - /// - /// If this would cause the nonce to underflow, the nonce will be set to 0. - pub fn decrement_nonce_unchecked(&mut self, address: Address) -> u64 - where - Db: DatabaseCommit, - { - self.try_decrement_nonce_unchecked(address).expect("infallible") - } - - /// Set the EVM storage at a slot. This is a low-level API, and is not - /// intended for general use. - pub fn set_storage_unchecked(&mut self, address: Address, slot: U256, value: U256) -> U256 - where - Db: DatabaseCommit, - { - self.try_set_storage_unchecked(address, slot, value).expect("infallible") - } - - /// Set the bytecode at a specific address, returning the previous bytecode - /// at that address. This is a low-level API, and is not intended for - /// general use. - pub fn set_bytecode_unchecked( - &mut self, - address: Address, - bytecode: Bytecode, - ) -> Option - where - Db: DatabaseCommit, - { - self.try_set_bytecode_unchecked(address, bytecode).expect("infallible") - } - - /// Increase the balance of an account. Returns the previous balance. This - /// is a low-level API, and is not intended for general use. - pub fn increase_balance_unchecked(&mut self, address: Address, amount: U256) -> U256 - where - Db: DatabaseCommit, - { - self.try_increase_balance_unchecked(address, amount).expect("infallible") - } - - /// Decrease the balance of an account. Returns the previous balance. This - /// is a low-level API, and is not intended for general use. - pub fn decrease_balance_unchecked(&mut self, address: Address, amount: U256) -> U256 - where - Db: DatabaseCommit, - { - self.try_decrease_balance_unchecked(address, amount).expect("infallible") - } - - /// Set the balance of an account. Returns the previous balance. This is a - /// low-level API, and is not intended for general use. - pub fn set_balance_unchecked(&mut self, address: Address, amount: U256) -> U256 - where - Db: DatabaseCommit, - { - self.try_set_balance_unchecked(address, amount).expect("infallible") - } -} - -// Layered inspector -impl Trevm, TrevmState> -where - Db: Database, - Outer: Inspector>, - Inner: Inspector>, -{ - /// Remove the outer-layer inspector, leaving the inner-layer inspector in - /// place. - pub fn take_outer(self) -> (Outer, Trevm) { - let (outer, inner) = self.inner.inspector.into_parts(); - - ( - outer, - Trevm { - inner: Box::new(Evm { - ctx: self.inner.ctx, - inspector: inner, - instruction: self.inner.instruction, - precompiles: self.inner.precompiles, - frame_stack: self.inner.frame_stack, - }), - state: self.state, - }, - ) - } - - /// Remove the outer-layer inspector, leaving the inner-layer inspector in - /// place. - pub fn remove_outer(self) -> Trevm { - self.take_outer().1 - } - - /// Remove the inner-layer inspector, leaving the outer-layer inspector in - /// place. - pub fn take_inner(self) -> (Inner, Trevm) { - let (outer, inner) = self.inner.inspector.into_parts(); - - ( - inner, - Trevm { - inner: Box::new(Evm { - ctx: self.inner.ctx, - inspector: outer, - instruction: self.inner.instruction, - precompiles: self.inner.precompiles, - frame_stack: self.inner.frame_stack, - }), - state: self.state, - }, - ) - } - - /// Remove the inner-layer inspector, leaving the outer-layer inspector in - /// place. - pub fn remove_inner(self) -> Trevm { - self.take_inner().1 - } -} - -// --- ALL STATES, WITH State - -impl Trevm -where - Db: Database + StateAcc, - Insp: Inspector>, -{ - /// Set the [EIP-161] state clear flag, activated in the Spurious Dragon - /// hardfork. - pub fn set_state_clear_flag(&mut self, flag: bool) { - self.inner.db_mut().set_state_clear_flag(flag) - } -} - -impl Trevm -where - Db: Database + TryStateAcc, - Insp: Inspector>, -{ - /// Fallibly set the [EIP-161] state clear flag, activated in the Spurious - /// Dragon hardfork. This function is intended to be used by shared states, - /// where mutable access may fail, e.g. an `Arc`. - /// - /// Prefer [`Self::set_state_clear_flag`] when available. - pub fn try_set_state_clear_flag( - &mut self, - flag: bool, - ) -> Result<(), ::Error> { - self.inner.db_mut().try_set_state_clear_flag(flag) - } -} - -// --- NEEDS CFG - -impl EvmNeedsCfg -where - Db: Database, - Insp: Inspector>, -{ - /// Fill the configuration environment. - pub fn fill_cfg(mut self, filler: &T) -> EvmNeedsBlock { - filler.fill_cfg(&mut self.inner); - // SAFETY: Same size and repr. Only phantomdata type changes - unsafe { core::mem::transmute(self) } - } -} - -// --- HAS CFG - -impl Trevm -where - Db: Database, - Insp: Inspector>, - TrevmState: HasCfg, -{ - /// Set the [EIP-170] contract code size limit. By default this is set to - /// 0x6000 bytes (~25KiB). Contracts whose bytecode is larger than this - /// limit cannot be deployed and will produce a [`CreateInitCodeSizeLimit`] - /// error. - /// - /// [`CreateInitCodeSizeLimit`]: InvalidTransaction::CreateInitCodeSizeLimit - /// [`Eip-170`]: https://eips.ethereum.org/EIPS/eip-170 - pub fn set_code_size_limit(&mut self, limit: usize) -> Option { - let mut csl = None; - self.inner.ctx.modify_cfg(|cfg| { - csl = cfg.limit_contract_code_size.replace(limit); - }); - csl - } - - /// Disable the [EIP-170] contract code size limit, returning the previous - /// setting. - /// - /// [`Eip-170`]: https://eips.ethereum.org/EIPS/eip-170 - pub fn disable_code_size_limit(&mut self) -> Option { - let mut csl = None; - self.inner.ctx.modify_cfg(|cfg| csl = cfg.limit_contract_code_size.take()); - csl - } - - /// Run a closure with the code size limit disabled, then restore the - /// previous setting. - pub fn without_code_size_limit(mut self, f: F) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let limit = self.disable_code_size_limit(); - let mut new = f(self); - if let Some(limit) = limit { - new.set_code_size_limit(limit); - } - new - } - - /// Set the [EIP-170] contract code size limit to the default value of - /// 0x6000 bytes (~25KiB), returning the previous setting. Contracts whose - /// bytecode is larger than this limit cannot be deployed and will produce - /// a [`CreateInitCodeSizeLimit`] error. - /// - /// [`CreateInitCodeSizeLimit`]: InvalidTransaction::CreateInitCodeSizeLimit - /// [`Eip-170`]: https://eips.ethereum.org/EIPS/eip-170 - pub fn set_default_code_size_limit(&mut self) -> Option { - self.set_code_size_limit(0x6000) - } - - /// Run a function with the provided configuration, then restore the - /// previous configuration. This will not affect the block and tx, if those - /// have been filled. - pub fn with_cfg(mut self, cfg: &C, f: F) -> Trevm - where - C: Cfg, - F: FnOnce(Self) -> Trevm, - NewState: HasCfg, - { - let previous = self.inner.cfg().clone(); - cfg.fill_cfg(&mut self.inner); - - let mut this = f(self); - this.inner.ctx.modify_cfg(|cfg| *cfg = previous); - this - } - - /// Run a fallible function with the provided configuration, then restore the - /// previous configuration. This will not affect the block and tx, if those - /// have been filled. - pub fn try_with_cfg( - mut self, - cfg: &C, - f: F, - ) -> Result, EvmErrored> - where - C: Cfg, - F: FnOnce(Self) -> Result, EvmErrored>, - NewState: HasCfg, - { - let previous = self.inner.cfg().clone(); - cfg.fill_cfg(&mut self.inner); - - match f(self) { - Ok(mut evm) => { - evm.inner.modify_cfg(|cfg| *cfg = previous); - Ok(evm) - } - Err(mut evm) => { - evm.inner.modify_cfg(|cfg| *cfg = previous); - Err(evm) - } - } - } - - /// Set a limit beyond which a callframe's memory cannot be resized. - /// Attempting to resize beyond this limit will result in a - /// [OutOfGasError::Memory] error. - /// - /// In cases where the gas limit may be extraordinarily high, it is - /// recommended to set this to a sane value to prevent memory allocation - /// panics. Defaults to `2^32 - 1` bytes per EIP-1985. - /// - /// [OutOfGasError::Memory]: revm::context::result::OutOfGasError::Memory - #[cfg(feature = "memory_limit")] - pub fn set_memory_limit(&mut self, new_limit: u64) -> u64 { - let mut ml = 0; - self.inner.ctx.modify_cfg(|cfg| ml = core::mem::replace(&mut cfg.memory_limit, new_limit)); - ml - } - - /// Disable balance checks. If the sender does not have enough balance to - /// cover the transaction, the sender will be given enough ether to ensure - /// execution doesn't fail. - #[cfg(feature = "optional_balance_check")] - pub fn disable_balance_check(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_balance_check = true) - } - - /// Enable balance checks. See [`Self::disable_balance_check`]. - #[cfg(feature = "optional_balance_check")] - pub fn enable_balance_check(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_balance_check = false) - } - - /// Run a closure with balance checks disabled, then restore the previous - /// setting. - #[cfg(feature = "optional_balance_check")] - pub fn without_balance_check(mut self, f: F) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let previous = self.inner.cfg().disable_balance_check; - self.disable_balance_check(); - let mut new = f(self); - new.inner.ctx.modify_cfg(|cfg| cfg.disable_balance_check = previous); - new - } - - /// Disable block gas limits. This allows transactions to execute even if - /// they gas needs exceed the block gas limit. This is useful for - /// simulating large transactions like forge scripts. - #[cfg(feature = "optional_block_gas_limit")] - pub fn disable_block_gas_limit(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_block_gas_limit = true); - } - - /// Enable block gas limits. See [`Self::disable_block_gas_limit`]. - #[cfg(feature = "optional_block_gas_limit")] - pub fn enable_block_gas_limit(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_block_gas_limit = false); - } - - /// Run a closure with block gas limits disabled, then restore the previous - /// setting. - #[cfg(feature = "optional_block_gas_limit")] - pub fn without_block_gas_limit(mut self, f: F) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let previous = self.inner.cfg().disable_block_gas_limit; - self.disable_block_gas_limit(); - let mut new = f(self); - new.inner.ctx.modify_cfg(|cfg| cfg.disable_block_gas_limit = previous); - new - } - - /// Disable [EIP-3607]. This allows transactions to originate from accounts - /// that contain code. This is useful for simulating smart-contract calls. - /// - /// [EIP-3607]: https://eips.ethereum.org/EIPS/eip-3607 - #[cfg(feature = "optional_eip3607")] - pub fn disable_eip3607(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_eip3607 = true); - } - - /// Enable [EIP-3607]. See [`Self::disable_eip3607`]. - /// - /// [EIP-3607]: https://eips.ethereum.org/EIPS/eip-3607 - #[cfg(feature = "optional_eip3607")] - pub fn enable_eip3607(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_eip3607 = false); - } - - /// Run a closure with [EIP-3607] disabled, then restore the previous - /// setting. - #[cfg(feature = "optional_eip3607")] - pub fn without_eip3607(mut self, f: F) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let previous = self.inner.cfg().disable_eip3607; - self.disable_eip3607(); - - let mut new = f(self); - new.inner.ctx.modify_cfg(|cfg| cfg.disable_eip3607 = previous); - new - } - - /// Disables [EIP-1559] base fee checks. This is useful for testing method - /// calls with zero gas price. - /// - /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 - #[cfg(feature = "optional_no_base_fee")] - pub fn disable_base_fee(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_base_fee = true) - } - - /// Enable [EIP-1559] base fee checks. See [`Self::disable_base_fee`]. - /// - /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 - #[cfg(feature = "optional_no_base_fee")] - pub fn enable_base_fee(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_base_fee = false) - } - - /// Run a closure with [EIP-1559] base fee checks disabled, then restore the - /// previous setting. - /// - /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 - #[cfg(feature = "optional_no_base_fee")] - pub fn without_base_fee(mut self, f: F) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let previous = self.inner.cfg().disable_base_fee; - self.disable_base_fee(); - - let mut new = f(self); - new.inner.ctx.modify_cfg(|cfg| cfg.disable_base_fee = previous); - new - } - - /// Disable nonce checks. This allows transactions to be sent with - /// incorrect nonces, and is useful for things like system transactions. - pub fn disable_nonce_check(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_nonce_check = true) - } - - /// Enable nonce checks. See [`Self::disable_nonce_check`]. - pub fn enable_nonce_check(&mut self) { - self.inner.ctx.modify_cfg(|cfg| cfg.disable_nonce_check = false) - } - - /// Run a closure with nonce checks disabled, then restore the previous - /// setting. This will not affect the block and tx, if those have been - /// filled. - pub fn without_nonce_check(mut self, f: F) -> Trevm - where - F: FnOnce(Self) -> Trevm, - { - let previous = self.inner.cfg().disable_nonce_check; - self.disable_nonce_check(); - - let mut new = f(self); - new.inner.ctx.modify_cfg(|cfg| cfg.disable_nonce_check = previous); - new - } -} - -// --- NEEDS BLOCK - -impl EvmNeedsBlock -where - Db: Database, - Insp: Inspector>, -{ - /// Open a block, apply some logic, and return the EVM ready for the next - /// block. - pub fn drive_block(self, driver: &mut D) -> DriveBlockResult - where - D: BlockDriver, - Db: DatabaseCommit, - { - let trevm = self.fill_block(driver.block()); - let trevm = driver.run_txns(trevm)?; - - let trevm = trevm.close_block(); - - match driver.post_block(&trevm) { - Ok(_) => Ok(trevm), - Err(e) => Err(trevm.errored(e)), - } - } - - /// Drive trevm through a set of blocks. - /// - /// # Panics - /// - /// If the driver contains no blocks. - pub fn drive_chain(self, driver: &mut D) -> DriveChainResult - where - D: ChainDriver, - Db: DatabaseCommit, - { - let block_count = driver.blocks().len(); - - let mut trevm = self - .drive_block(&mut driver.blocks()[0]) - .map_err(EvmErrored::err_into::<>::Error>)?; - - if let Err(e) = driver.interblock(&trevm, 0) { - return Err(trevm.errored(e)); - } - - for i in 1..block_count { - trevm = { - let trevm = trevm - .drive_block(&mut driver.blocks()[i]) - .map_err(EvmErrored::err_into::<>::Error>)?; - if let Err(e) = driver.interblock(&trevm, i) { - return Err(trevm.errored(e)); - } - trevm - }; - } - Ok(trevm) - } - - /// Fill a block and return the EVM ready for a transaction. - /// - /// This does not perform any pre- or post-block logic. To manage block - /// lifecycles, use [`Self::drive_block`] or [`Self::drive_chain`] instead. - pub fn fill_block(mut self, filler: &B) -> EvmNeedsTx { - filler.fill_block(self.inner_mut_unchecked()); - // SAFETY: Same size and repr. Only phantomdata type changes - unsafe { core::mem::transmute(self) } - } -} - -// --- HAS BLOCK - -impl Trevm -where - Db: Database, - Insp: Inspector>, - TrevmState: HasBlock, -{ - /// Get a reference to the current block environment. - pub fn block(&self) -> &BlockEnv { - self.inner.block() - } - - /// Get the current block gas limit. - pub fn block_gas_limit(&self) -> u64 { - self.block().gas_limit() - } - - /// Get the current block number. - pub fn block_number(&self) -> U256 { - self.block().number() - } - - /// Get the current block timestamp. - pub fn block_timestamp(&self) -> U256 { - self.block().timestamp() - } - - /// Get the block beneficiary address. - pub fn beneficiary(&self) -> Address { - self.block().beneficiary() - } - - /// Run a function with the provided block, then restore the previous block. - pub fn with_block(mut self, b: &B, f: F) -> Trevm - where - B: Block, - F: FnOnce(Self) -> Trevm, - NewState: HasBlock, - { - let previous = self.inner.block().clone(); - b.fill_block(&mut self.inner); - - let mut this = f(self); - this.inner.ctx.set_block(previous); - this - } - - /// Run a fallible function with the provided block, then restore the previous block. - pub fn try_with_block( - mut self, - b: &B, - f: F, - ) -> Result, EvmErrored> - where - F: FnOnce(Self) -> Result, EvmErrored>, - B: Block, - NewState: HasBlock, - { - let previous = self.inner.block().clone(); - b.fill_block(&mut self.inner); - - match f(self) { - Ok(mut evm) => { - evm.inner.ctx.set_block(previous); - Ok(evm) - } - Err(mut evm) => { - evm.inner.ctx.set_block(previous); - Err(evm) - } - } - } -} - -// --- Needs Block with State - -impl EvmNeedsBlock -where - Db: Database + StateAcc, - Insp: Inspector>, -{ - /// Finish execution and return the outputs. - /// - /// If the State has not been built with - /// [revm::database::StateBuilder::with_bundle_update] then the returned - /// [`BundleState`] will be meaningless. - /// - /// See [`State::merge_transitions`] and [`State::take_bundle`]. - /// - /// [`State::merge_transitions`]: revm::database::State::merge_transitions - /// [`State::take_bundle`]: revm::database::State::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 - } -} - -impl EvmNeedsBlock -where - Db: Database + TryStateAcc, - Insp: Inspector>, -{ - /// Fallibly finish execution and return the outputs. This function is - /// intended to be used by shared states, where mutable access may fail, e. - /// g. an `Arc`. Prefer [`Self::finish`] when available. - /// - /// If the State has not been built with - /// [revm::database::StateBuilder::with_bundle_update] then the returned - /// [`BundleState`] will be meaningless. - /// - /// See [`State::merge_transitions`] and [`State::take_bundle`]. - /// - /// [`State::merge_transitions`]: revm::database::State::merge_transitions - /// [`State::take_bundle`]: revm::database::State::take_bundle - pub fn try_finish( - mut self, - ) -> Result::Error>> { - let db = self.inner.db_mut(); - - trevm_try!(db.try_merge_transitions(BundleRetention::Reverts), self); - - let bundle = trevm_try!(db.try_take_bundle(), self); - - Ok(bundle) - } -} - -// --- NEEDS TX - -impl EvmNeedsTx -where - Db: Database, - Insp: Inspector>, -{ - /// Close the current block, returning the EVM ready for the next block. - pub fn close_block(self) -> EvmNeedsBlock { - // SAFETY: Same size and repr. Only phantomdata type changes - unsafe { core::mem::transmute(self) } - } - - /// Drive a bundle to completion, apply some post-bundle logic, and return the - /// EVM ready for the next bundle or tx. - pub fn drive_bundle(self, driver: &mut D) -> DriveBundleResult - where - D: BundleDriver, - Db: DatabaseCommit, - { - let trevm = driver.run_bundle(self)?; - - match driver.post_bundle(&trevm) { - Ok(_) => Ok(trevm), - Err(e) => Err(trevm.errored(e)), - } - } - - /// Fill the transaction environment. - pub fn fill_tx(mut self, filler: &T) -> EvmReady { - filler.fill_tx(&mut self.inner); - // SAFETY: Same size and repr. Only phantomdata type changes - unsafe { core::mem::transmute(self) } - } - - /// Execute a transaction. Shortcut for `fill_tx(tx).run()`. - pub fn run_tx( - self, - filler: &T, - ) -> Result, EvmErrored> { - self.fill_tx(filler).run() - } - - /// Simulate the transaction, and return the [`ExecutionResult`]. The - /// following modifications are made to the environment while simulating. - /// - /// - [EIP-3607] is disabled. - /// - Base fee checks are disabled. - /// - Nonce checks are disabled. - /// - /// [EIP-3607]: https://eips.ethereum.org/EIPS/eip-3607 - #[cfg(feature = "call")] - pub fn call_tx( - self, - filler: &T, - ) -> Result<(ExecutionResult, Self), EvmErrored> { - self.fill_tx(filler).call() - } - - /// Estimate the gas cost of a transaction. Shortcut for `fill_tx(tx). - /// estimate()`. Returns an [`EstimationResult`] and the EVM populated with - /// the transaction. - /// - /// [`EstimationResult`]: crate::EstimationResult - #[cfg(feature = "estimate_gas")] - pub fn estimate_tx_gas( - self, - filler: &T, - ) -> Result<(crate::EstimationResult, EvmReady), EvmErrored> { - self.fill_tx(filler).estimate_gas() - } -} - -// --- HAS TX - -impl Trevm -where - Db: Database, - Insp: Inspector>, - TrevmState: HasTx, -{ - #[cfg(feature = "call")] - fn try_with_call_filler( - self, - filler: &crate::fillers::CallFiller, - f: impl FnOnce(Self) -> Result, EvmErrored>, - ) -> Result, EvmErrored> { - // override all relevant env bits - self.try_with_cfg(filler, |this| this.try_with_block(filler, f)) - } - - /// Convenience function to use the estimator to fill both Cfg and Tx, and - /// run a fallible function. - #[cfg(feature = "estimate_gas")] - fn try_with_estimate_gas_filler( - self, - filler: &crate::fillers::GasEstimationFiller, - f: impl FnOnce(Self) -> Result>, - ) -> Result> { - self.try_with_cfg(filler, |this| this.try_with_tx(filler, f)) - } - - /// Get a reference to the loaded tx env that will be executed. - pub fn tx(&self) -> &TxEnv { - self.inner.tx() - } - /// True if the transaction is a simple transfer. - pub fn is_transfer(&self) -> bool { - self.inner.tx().input().is_empty() && self.to().is_call() - } - - /// True if the transaction is a contract creation. - pub fn is_create(&self) -> bool { - self.to().is_create() - } - - /// Get a reference to the transaction input data, which will be used as - /// calldata or initcode during EVM execution. - pub fn input(&self) -> &Bytes { - self.tx().input() - } - - /// Read the target of the transaction. - pub fn to(&self) -> TxKind { - self.tx().kind() - } - - /// Read the value in wei of the transaction. - pub fn value(&self) -> U256 { - self.tx().value() - } - - /// Get the gas limit of the loaded transaction. - pub fn gas_limit(&self) -> u64 { - self.tx().gas_limit() - } - - /// Get the gas price of the loaded transaction. - pub fn gas_price(&self) -> u128 { - self.tx().gas_price() - } - - /// Get the address of the caller. - pub fn caller(&self) -> Address { - self.tx().caller() - } - - /// Get the account of the caller. Error if the DB errors. - pub fn caller_account(&mut self) -> Result::Error>> { - self.try_read_account(self.caller()) - .map(Option::unwrap_or_default) - .map_err(EVMError::Database) - } - - /// Get the address of the callee. `None` if `Self::is_create` is true. - pub fn callee(&self) -> Option
{ - self.to().into() - } - - /// Get the account of the callee. - /// - /// Returns as follows: - /// - if `Self::is_create` is true, `Ok(None)` - /// - if the callee account does not exist, `Ok(AccountInfo::default())` - /// - if the DB errors, `Err(EVMError::Database(err))` - pub fn callee_account( - &mut self, - ) -> Result, EVMError<::Error>> { - self.callee().map_or(Ok(None), |addr| { - self.try_read_account(addr) - .map(Option::unwrap_or_default) - .map(Some) - .map_err(EVMError::Database) - }) - } - - /// Get the account of the callee. `None` if `Self::is_create` is true, - /// error if the DB errors. - pub fn callee_account_ref(&self) -> Result, ::Error> - where - Db: DatabaseRef, - { - self.callee().map_or(Ok(None), |addr| self.try_read_account_ref(addr)) - } - - /// Run a function with the provided transaction, then restore the previous - /// transaction. - pub fn with_tx(mut self, t: &T, f: F) -> Trevm - where - T: Tx, - F: FnOnce(Self) -> Trevm, - NewState: HasTx, - { - let previous = self.inner.tx().clone(); - t.fill_tx(&mut self.inner); - let mut this = f(self); - this.inner.ctx.set_tx(previous); - this - } - - /// Run a fallible function with the provided transaction, then restore the - /// previous transaction. - pub fn try_with_tx( - mut self, - t: &T, - f: F, - ) -> Result, EvmErrored> - where - T: Tx, - F: FnOnce(Self) -> Result, EvmErrored>, - NewState: HasTx, - { - let previous = self.inner.tx().clone(); - t.fill_tx(&mut self.inner); - match f(self) { - Ok(mut evm) => { - evm.inner.ctx.set_tx(previous); - Ok(evm) - } - Err(mut evm) => { - evm.inner.ctx.set_tx(previous); - Err(evm) - } - } - } - - /// Return the maximum gas that the caller can purchase. This is the balance - /// of the caller divided by the gas price. - pub fn caller_gas_allowance(&mut self) -> Result::Error>> { - // Avoid DB read if gas price is zero - let gas_price = self.gas_price(); - self.try_gas_allowance(self.caller(), gas_price).map_err(EVMError::Database) - } - - /// This function caps the gas limit of the transaction to the allowance of - /// the caller. - /// - /// This is useful for e.g. call simulation, where the exact amount of gas - /// used is less important than ensuring that the call succeeds and returns - /// a meaningful result. - /// - /// # Returns - /// - /// The gas limit after the operation. - pub fn cap_tx_gas_to_allowance(&mut self) -> Result::Error>> { - let allowance = self.caller_gas_allowance()?; - - self.inner.modify_tx(|tx| tx.gas_limit = tx.gas_limit.min(allowance)); - - Ok(self.gas_limit()) - } - - /// Cap the gas limit of the transaction to the minimum of the block gas - /// limit and the transaction's gas limit. - /// - /// This is useful for ensuring that the transaction does not exceed the - /// block gas limit, e.g. during call simulation. - /// - /// # Returns - /// - /// The gas limit after the operation. - pub fn cap_tx_gas_to_block_limit(&mut self) -> u64 { - let block_gas_limit = self.block_gas_limit(); - - self.inner.modify_tx(|tx| tx.gas_limit = tx.gas_limit.min(block_gas_limit)); - - self.tx().gas_limit - } - - /// This function caps the gas limit of the transaction to the minimum of - /// the block limit and the caller's gas allowance. - /// - /// This is equivalent to calling [`Self::cap_tx_gas_to_block_limit`] and - /// [`Self::cap_tx_gas_to_allowance`] in sequence. - /// - /// # Returns - /// - /// The gas limit after the operation. - pub fn cap_tx_gas(&mut self) -> Result::Error>> { - self.cap_tx_gas_to_block_limit(); - self.cap_tx_gas_to_allowance() - } -} - -// -- NEEDS TX with State - -impl EvmNeedsTx -where - Db: Database + StateAcc, - Insp: Inspector>, -{ - /// Apply block overrides to the current block. - /// - /// Note that this is NOT reversible. The overrides are applied directly to - /// the underlying state and these changes cannot be removed. If it is - /// important that you have access to the pre-change state, you should wrap - /// the existing DB in a new [`State`] and apply the overrides to that. - /// - /// [`State`]: revm::database::State - pub fn apply_block_overrides(mut self, overrides: &BlockOverrides) -> Self { - overrides.fill_block(&mut self.inner); - - if let Some(hashes) = overrides.block_hash.as_ref() { - self.inner.db_mut().set_block_hashes(hashes) - } - - self - } - - /// Apply block overrides to the current block, if they are provided. - /// - /// Note that this is NOT reversible. The overrides are applied directly to - /// the underlying state and these changes cannot be removed. If it is - /// important that you have access to the pre-change state, you should wrap - /// the existing DB in a new [`State`] and apply the overrides to that. - /// - /// [`State`]: revm::database::State - pub fn maybe_apply_block_overrides(self, overrides: Option<&BlockOverrides>) -> Self { - if let Some(overrides) = overrides { - self.apply_block_overrides(overrides) - } else { - self - } - } -} - -impl EvmNeedsTx -where - Db: Database + TryStateAcc, - Insp: Inspector>, -{ - /// Apply block overrides to the current block. This function is - /// intended to be used by shared states, where mutable access may fail, e. - /// g. an `Arc`. Prefer [`Self::apply_block_overrides`] when - /// available. - /// - /// Note that this is NOT reversible. The overrides are applied directly to - /// the underlying state and these changes cannot be removed. If it is - /// important that you have access to the pre-change state, you should wrap - /// the existing DB in a new [`State`] and apply the overrides to that. - /// - /// [`State`]: revm::database::State - pub fn try_apply_block_overrides( - mut self, - overrides: &BlockOverrides, - ) -> Result::Error>> { - overrides.fill_block(&mut self.inner); - - if let Some(hashes) = overrides.block_hash.as_ref() { - trevm_try!(self.inner.db_mut().try_set_block_hashes(hashes), self); - } - - Ok(self) - } - - /// Apply block overrides to the current block, if they are provided. This - /// function is intended to be used by shared states, where mutable access - /// may fail, e.g. an `Arc`.Prefer - /// [`Self::maybe_apply_block_overrides`] when available. - /// - /// Note that this is NOT reversible. The overrides are applied directly to - /// the underlying state and these changes cannot be removed. If it is - /// important that you have access to the pre-change state, you should wrap - /// the existing DB in a new [`State`] and apply the overrides to that. - /// - /// [`State`]: revm::database::State - pub fn try_maybe_apply_block_overrides( - self, - overrides: Option<&BlockOverrides>, - ) -> Result::Error>> { - if let Some(overrides) = overrides { - self.try_apply_block_overrides(overrides) - } else { - Ok(self) - } - } -} - -// --- READY - -impl EvmReady -where - Db: Database, - Insp: Inspector>, -{ - /// Clear the current transaction environment. - pub fn clear_tx(self) -> EvmNeedsTx { - // NB: we do not clear the tx env here, as we may read it during post-tx - // logic in a block driver - - // SAFETY: Same size and repr. Only phantomdata type changes - unsafe { core::mem::transmute(self) } - } - - /// Execute the loaded transaction. This is a wrapper around - /// [`InspectEvm::inspect_tx`] and produces either [`EvmTransacted`] or - /// [`EvmErrored`]. - pub fn run(mut self) -> Result, EvmErrored> { - let result = self.inner.inspect_tx(self.tx().clone()); - - let Self { inner, .. } = self; - - match result { - Ok(result) => Ok(Trevm { inner, state: TransactedState { result } }), - Err(error) => Err(EvmErrored { inner, state: ErroredState { error } }), - } - } - - /// Simulate the transaction, and return the [`ExecutionResult`]. The - /// following modifications are made to the environment while simulating. - /// - /// - [EIP-3607] is disabled. - /// - Base fee checks are disabled. - /// - Nonce checks are disabled. - /// - /// [EIP-3607]: https://eips.ethereum.org/EIPS/eip-3607 - #[cfg(feature = "call")] - pub fn call(self) -> Result<(ExecutionResult, EvmNeedsTx), EvmErrored> { - let mut output = std::mem::MaybeUninit::uninit(); - - let gas_limit = self.tx().gas_limit; - - let this = - self.try_with_call_filler(&crate::fillers::CallFiller { gas_limit }, |this| { - let t = this.run()?; - - let (o, t) = t.take_result(); - - output.write(o); - - Ok(t) - })?; - Ok((unsafe { output.assume_init() }, this)) - } - - /// Calculate the minimum gas required to start EVM execution. - /// - /// This uses [`calculate_initial_tx_gas_for_tx`] to calculate the initial - /// gas. Its output is dependent on - /// - the EVM spec - /// - the input data - /// - whether the transaction is a contract creation or a call - /// - the EIP-2930 access list - /// - the number of [EIP-7702] authorizations - /// - /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 - /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702 - pub fn calculate_initial_gas(&self) -> u64 { - calculate_initial_tx_gas_for_tx(self.tx(), self.spec_id()).initial_gas - } - - /// Estimate gas for a simple transfer. This will - /// - Check that the transaction has no input data. - /// - Check that the target is not a `create`. - /// - Check that the target is not a contract. - /// - Return the minimum gas required for the transfer. - #[cfg(feature = "estimate_gas")] - fn estimate_gas_simple_transfer( - &mut self, - ) -> Result, EVMError<::Error>> { - use alloy::consensus::constants::KECCAK_EMPTY; - use tracing::trace; - - if !self.is_transfer() { - return Ok(None); - } - - // Shortcut if the tx is create, otherwise read the account - let Some(acc) = self.callee_account()? else { return Ok(None) }; - - // If the code hash is not empty, then the target is a contract - if acc.code_hash != KECCAK_EMPTY { - return Ok(None); - } - - // delegate calculation to revm. This ensures that things like bogus - // 2930 access lists don't mess up our estimates - let initial = self.calculate_initial_gas(); - trace!(initial, "using initial gas for simple transfer"); - Ok(Some(initial)) - } - - /// Convenience function to simplify nesting of [`Self::estimate_gas`]. - #[cfg(feature = "estimate_gas")] - fn run_estimate( - self, - filler: &crate::fillers::GasEstimationFiller, - ) -> Result<(crate::EstimationResult, Self), EvmErrored> { - use tracing::trace; - - let mut estimation = std::mem::MaybeUninit::uninit(); - - let this = self.try_with_estimate_gas_filler(filler, |this| match this.run() { - Ok(trevm) => { - let (e, t) = trevm.take_estimation(); - - estimation.write(e); - Ok(t) - } - Err(err) => Err(err), - })?; - - // SAFETY: if we did not shortcut return, then estimation was - // definitely written - Ok((unsafe { estimation.assume_init() }, this)) - .inspect(|(est, _)| trace!(?est, "gas estimation result",)) - } - - /// Implements gas estimation. This will output an estimate of the minimum - /// amount of gas that the transaction will consume, calculated via - /// iterated simulation. - /// - /// In the worst case this will perform a binary search, resulting in - /// `O(log(n))` simulations. - /// - /// ## Returns - /// - /// An [`EstimationResult`] and the EVM with the transaction populated. - /// Like with the remainder of the API, an EVM revert or an EVM halt is - /// NOT an error. An [`Err`] is returned only if the EVM encounters a - /// condition of use violation or a DB access fails. - /// - /// ## Estimation Algorithm - /// - /// This function is largely based on the reth RPC estimation algorithm, - /// which can be found [here]. The algorithm is as follows: - /// - /// - Disable eip-3607, allowing estimation from contract accounts. - /// - Disable base fee checks. - /// - Check if the transaction is a simple transfer - /// - Is there input data empty? If yes, proceed to regular estimation - /// - Is the callee a contract? If yes, proceed to regular estimation - /// - Otherwise, shortcut return success with [`MIN_TRANSACTION_GAS`]. - /// - Simulate the transaction with the maximum possible gas limit. - /// - If the simulation fails, shortcut return the failure. - /// - If succesful, store the gas used as the search minimum. - /// - Simulate the transaction with an "optimistic" gas limit. - /// - If the simulation fails, shortcut return the failure. - /// - If succesful, begin the binary search around that range. - /// - Binary search loop: - /// - If the search range is small enough, break the loop and return - /// the current estimate. - /// - Calculate a new gas limit based on the midpoint of the search - /// range. - /// - Simulate the transaction with the new gas limit. - /// - Adjust the search range based on the simulation result: - /// - If the result is a success, pull the search max down to the - /// limit. - /// - If the result is a revert, push the search min up to the - /// limit. - /// - If the result is a halt, check if the halt is potentially a - /// gas-dynamic halt. - /// - If it is, treat it as a revert. - /// - If it is not, shortcut return the halt. - /// - Loop. - /// - /// [here]: https://github.com/paradigmxyz/reth/blob/ad503a08fa242b28ad3c1fea9caa83df2dfcf72d/crates/rpc/rpc-eth-api/src/helpers/estimate.rs#L35-L42 - /// [`EstimationREsult`]: crate::EstimationResult - /// [`MIN_TRANSACTION_GAS`]: crate::MIN_TRANSACTION_GAS - #[cfg(feature = "estimate_gas")] - pub fn estimate_gas(mut self) -> Result<(crate::EstimationResult, Self), EvmErrored> { - use tracing::{debug, enabled, trace}; - - if let Some(est) = trevm_try!(self.estimate_gas_simple_transfer(), self) { - return Ok((crate::EstimationResult::basic_transfer_success(est), self)); - } - - // We shrink the gas limit to 64 bits, as using more than 18 quintillion - // gas in a block is unlikely. - let initial_limit = self.gas_limit(); - - // Start the search range at 21_000 gas. - let mut search_range = - crate::est::SearchRange::new(crate::MIN_TRANSACTION_GAS, initial_limit); - - let span = tracing::debug_span!( - "Trevm::estimate_gas", - start_min = search_range.min(), - start_max = search_range.max(), - ); - if enabled!(tracing::Level::TRACE) { - span.record("tx", format!("{:?}", &self.tx())); - span.record("block", format!("{:?}", &self.block())); - } else { - span.record("tx", "omitted. Use TRACE for details"); - } - let _e = span.enter(); - - // Cap the gas limit to the caller's allowance and block limit - trevm_try!(self.cap_tx_gas(), self); - search_range.maybe_lower_max(self.gas_limit()); - - // Raise the floor to the amount of gas required to initialize the EVM. - search_range.maybe_raise_min(self.calculate_initial_gas()); - - // Run an estimate with the max gas limit. - // NB: we declare these mut as we re-use the binding throughout the - // function. - debug!(gas_limit = self.gas_limit(), "running optimistic estimate"); - let (mut estimate, mut trevm) = self.run_estimate(&search_range.max().into())?; - - // If it failed, no amount of gas is likely to work, so we shortcut - // return. - if estimate.is_failure() { - debug!(%estimate, "optimistic estimate failed"); - return Ok((estimate, trevm)); - } - trace!(%estimate, "optimistic estimate succeeded"); - - // Now we know that it succeeds at _some_ gas limit. We can now binary - // search. We start by recording the initial best estimate. We'll update - // this best-estimate as we go inside the `estimate_and_adjust` macro - // invocations. - let mut best = estimate.clone(); - let mut gas_used = estimate.gas_used(); - let gas_refunded = estimate.gas_refunded().expect("checked is_failure"); - - // NB: if we've made it this far it's very unlikely that `gas_used` is - // less than 21_000, but we'll check anyway. - search_range.maybe_raise_min(gas_used - 1); - - // NB: This is a heuristic adopted from geth and reth - // The goal is to check if the first-run is actually very close to the - // real estimate, thereby cutting down on the number of iterations in - // the later search loop. - // https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L132-L135 - // NB: 64 / 63 is due to Ethereum's gas-forwarding rules. Each call - // frame can forward only 63/64 of the gas it has when it makes a new - // frame. - let mut needle = (gas_used + gas_refunded + revm::interpreter::gas::CALL_STIPEND) * 64 / 63; - - // If the first search is outside the range, we don't need to try it. - if search_range.contains(needle) { - estimate_and_adjust!(best, estimate, trevm, needle, search_range); - // NB: `estimate` is rebound in the macro, so do not move this line - // up. - gas_used = estimate.gas_used(); - } - - // NB: This is a heuristic adopted from reth. - // Pick a point that's close to the estimated gas - needle = std::cmp::min(gas_used * 3, search_range.midpoint()); - - // Binary search loop. - // The second conditional is a heuristic adopted from geth and reth. - // An estimation error is allowed once the current gas limit range - // used in the binary search is small enough (less than 1.5% of the - // highest gas limit) - // 1 && search_range.ratio() > 0.015 { - estimate_and_adjust!(best, estimate, trevm, needle, search_range); - needle = search_range.midpoint(); - } - - Ok((best, trevm)) - } -} - -// --- ERRORED - -impl EvmErrored -where - Db: Database, - Insp: Inspector>, -{ - /// Get a reference to the error. - pub const fn error(&self) -> &E { - &self.state.error - } - - /// Inspect the error with a closure. - pub fn inspect_err(&self, f: F) -> T - where - F: FnOnce(&E) -> T, - { - f(self.error()) - } - - /// Discard the error and return the EVM. - pub fn discard_error(self) -> EvmNeedsTx { - Trevm { inner: self.inner, state: NeedsTx::new() } - } - - /// Convert the error into an [`EVMError`]. - pub fn into_error(self) -> E { - self.state.error - } - - /// Reset the EVM, returning the error and the EVM ready for the next - /// transaction. - pub fn take_err(self) -> (E, EvmNeedsTx) { - let Self { inner, state: ErroredState { error } } = self; - (error, Trevm { inner, state: NeedsTx::new() }) - } - - /// Transform the error into a new error type. - pub fn err_into>(self) -> EvmErrored { - self.map_err(Into::into) - } - - /// Map the error to a new error type. - pub fn map_err(self, f: F) -> EvmErrored - where - F: FnOnce(E) -> NewErr, - { - Trevm { inner: self.inner, state: ErroredState { error: f(self.state.error) } } - } -} - -impl EvmErrored -where - Db: Database, - Insp: Inspector>, -{ - /// Check if the error is a transaction error. This is provided as a - /// convenience function for common cases, as Transaction errors should - /// usually be discarded. - pub const fn is_transaction_error(&self) -> bool { - matches!(self.state.error, EVMError::Transaction(_)) - } - - /// Fallible cast to a [`InvalidTransaction`]. - pub const fn as_transaction_error(&self) -> Option<&InvalidTransaction> { - match &self.state.error { - EVMError::Transaction(err) => Some(err), - _ => None, - } - } - - /// Discard the error if it is a transaction error, returning the EVM. If - /// the error is not a transaction error, return self - pub fn discard_transaction_error(self) -> Result, Self> { - if self.is_transaction_error() { - Ok(self.discard_error()) - } else { - Err(self) - } - } -} - -// --- TRANSACTED - -impl AsRef for EvmTransacted -where - Db: Database, - Insp: Inspector>, -{ - fn as_ref(&self) -> &ResultAndState { - &self.state.result - } -} - -impl AsRef for EvmTransacted -where - Db: Database, - Insp: Inspector>, -{ - fn as_ref(&self) -> &ExecutionResult { - &self.state.result.result - } -} - -impl EvmTransacted -where - Db: Database, - Insp: Inspector>, -{ - /// Get a reference to the result. - pub fn result(&self) -> &ExecutionResult { - self.as_ref() - } - - /// Get a mutable reference to the result. Modification of the result is - /// discouraged, as it may lead to inconsistent state. - /// - /// This is primarily intended for use in [`SystemTx`] execution. - /// - /// [`SystemTx`]: crate::system::SystemTx - pub const fn result_mut_unchecked(&mut self) -> &mut ExecutionResult { - &mut self.state.result.result - } - - /// Get a reference to the state. - pub const fn state(&self) -> &EvmState { - &self.state.result.state - } - - /// Get a mutable reference to the state. Modification of the state is - /// discouraged, as it may lead to inconsistent state. - pub const fn state_mut_unchecked(&mut self) -> &mut EvmState { - &mut self.state.result.state - } - - /// Get a reference to the result and state. - pub fn result_and_state(&self) -> &ResultAndState { - self.as_ref() - } - - /// Get a mutable reference to the result and state. Modification of the - /// result and state is discouraged, as it may lead to inconsistent state. - /// - /// This is primarily intended for use in [`SystemTx`] execution. - /// - /// [`SystemTx`]: crate::system::SystemTx - pub const fn result_and_state_mut_unchecked(&mut self) -> &mut ResultAndState { - &mut self.state.result - } - - /// Get the output of the transaction. This is the return value of the - /// outermost callframe. - pub fn output(&self) -> Option<&Bytes> { - self.result().output() - } - - /// Get the output of the transaction, and decode it as the return value of - /// a [`SolCall`]. If `validate` is true, the output will be type- and - /// range-checked. - /// - /// [`SolCall`]: alloy::sol_types::SolCall - pub fn output_sol( - &self, - validate: bool, - ) -> Option> - where - T::Return: alloy::sol_types::SolType, - { - if validate { - return self.output().map(|output| T::abi_decode_returns_validate(output)); - } - - self.output().map(|output| T::abi_decode_returns(output)) - } - - /// Get the gas used by the transaction. - pub fn gas_used(&self) -> u64 { - self.state.result.result.gas_used() - } - - /// Discard the state changes and return the EVM. - pub fn reject(self) -> EvmNeedsTx { - Trevm { inner: self.inner, state: NeedsTx::new() } - } - - /// Take the [`ResultAndState`] and return the EVM. - pub fn into_result_and_state(self) -> ResultAndState { - self.state.result - } - - /// Take the [`ResultAndState`] and return the EVM. - pub fn take_result_and_state(self) -> (ResultAndState, EvmNeedsTx) { - let Self { inner, state: TransactedState { result } } = self; - (result, Trevm { inner, state: NeedsTx::new() }) - } - - /// Take the [`ExecutionResult`], discard the [`EvmState`] and return the - /// EVM. - pub fn take_result(self) -> (ExecutionResult, EvmNeedsTx) { - let Self { inner, state: TransactedState { result } } = self; - (result.result, Trevm { inner, state: NeedsTx::new() }) - } - - /// Accept the state changes, commiting them to the database, and return the - /// EVM with the [`ExecutionResult`]. - pub fn accept(self) -> (ExecutionResult, EvmNeedsTx) - where - Db: DatabaseCommit, - { - let Self { mut inner, state: TransactedState { result } } = self; - - inner.db_mut().commit(result.state); - - (result.result, Trevm { inner, state: NeedsTx::new() }) - } - - /// Try to accept the state changes, commiting them to the database, and - /// return the EVM with the [`ExecutionResult`]. If the commit fails, return - /// the EVM with the error, discarding the state changes. This is a fallible - /// version of [`Self::accept`], intended for use with databases that can - /// fail to commit. Prefer [`Self::accept`] when possible. - // Type alias would make it less clear I think - #[allow(clippy::type_complexity)] - pub fn try_accept( - self, - ) -> Result< - (ExecutionResult, EvmNeedsTx), - EvmErrored::Error>, - > - where - Db: TryDatabaseCommit, - { - let Self { mut inner, state: TransactedState { result } } = self; - - trevm_try!(inner.db_mut().try_commit(result.state), Trevm { inner, state: NeedsTx::new() }); - - Ok((result.result, Trevm { inner, state: NeedsTx::new() })) - } - - /// Accept the state changes, commiting them to the database, dropping the - /// [`ExecutionResult`]. - pub fn accept_state(self) -> EvmNeedsTx - where - Db: DatabaseCommit, - { - self.accept().1 - } - - /// Try to accept the state changes, commiting them to the database. If the - /// commit fails, return the EVM with the error, discarding the state - /// changes. This is a fallible version of [`Self::accept_state`], intended - /// for use with databases that can fail to commit. Prefer - /// [`Self::accept_state`] when possible. - pub fn try_accept_state( - self, - ) -> Result, EvmErrored::Error>> - where - Db: TryDatabaseCommit, - { - self.try_accept().map(|(_, evm)| evm) - } - - /// Create an [`EstimationResult`] from the transaction [`ExecutionResult`]. - /// - /// [`EstimationResult`]: crate::EstimationResult - #[cfg(feature = "estimate_gas")] - pub fn estimation(&self) -> crate::EstimationResult { - use crate::EstimationResult; - - EstimationResult::from_limit_and_execution_result(self.gas_limit(), self.result()) - } - - /// Take the [`EstimationResult`] and return it and the EVM. This discards - /// pending state changes, but leaves the EVM ready to execute the same - /// transaction again. - /// - /// [`EstimationResult`]: crate::EstimationResult - #[cfg(feature = "estimate_gas")] - pub fn take_estimation(self) -> (crate::EstimationResult, EvmReady) { - let estimation = self.estimation(); - (estimation, Trevm { inner: self.inner, state: crate::Ready::new() }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - test_utils::{test_trevm_with_funds, ALICE, BOB, LOG_DEPLOYED_BYTECODE}, - NoopBlock, NoopCfg, TrevmBuilder, - }; - use alloy::{ - consensus::constants::ETH_TO_WEI, - network::{TransactionBuilder, TransactionBuilder7702}, - rpc::types::{Authorization, TransactionRequest}, - signers::SignerSync, - }; - use revm::{context::transaction::AuthorizationTr, database::InMemoryDB, primitives::bytes}; - - #[test] - fn test_estimate_gas_simple_transfer() { - let trevm = test_trevm_with_funds(&[ - (ALICE.address(), U256::from(ETH_TO_WEI)), - (BOB.address(), U256::from(ETH_TO_WEI)), - ]); - - let tx = TransactionRequest::default() - .from(ALICE.address()) - .to(BOB.address()) - .value(U256::from(ETH_TO_WEI / 2)); - - let (estimation, _trevm) = - trevm.fill_cfg(&NoopCfg).fill_block(&NoopBlock).fill_tx(&tx).estimate_gas().unwrap(); - - assert!(estimation.is_success()); - // The gas used should correspond to a simple transfer. - assert_eq!(estimation.gas_used(), 21000); - } - - #[test] - fn test_7702_authorization_estimation() { - // Insert the LogContract code - let db = InMemoryDB::default(); - let log_address = Address::repeat_byte(0x32); - - // Set up trevm, and test balances. - let mut trevm = - TrevmBuilder::new().with_db(db).with_spec_id(SpecId::PRAGUE).build_trevm().unwrap(); - let _ = trevm.test_set_balance(ALICE.address(), U256::from(ETH_TO_WEI)); - let _ = trevm.set_bytecode_unchecked(log_address, Bytecode::new_raw(LOG_DEPLOYED_BYTECODE)); - - // Bob will sign the authorization. - let authorization = Authorization { - chain_id: U256::ZERO, - address: log_address, - // We know Bob's nonce is 0. - nonce: 0, - }; - let signature = BOB.sign_hash_sync(&authorization.signature_hash()).unwrap(); - let signed_authorization = authorization.into_signed(signature); - assert_eq!(signed_authorization.authority().unwrap(), BOB.address()); - - let tx = TransactionRequest::default() - .from(ALICE.address()) - .to(BOB.address()) - .with_authorization_list(vec![signed_authorization]) - .with_input(bytes!("0x7b3ab2d0")); // emitHello() - - let (estimation, trevm) = - trevm.fill_cfg(&NoopCfg).fill_block(&NoopBlock).fill_tx(&tx).estimate_gas().unwrap(); - - assert!(estimation.is_success()); - - let tx = tx.with_gas_limit(estimation.limit()); - - let output = trevm.clear_tx().fill_tx(&tx).run().unwrap().accept(); - - assert!(output.0.is_success()); - assert_eq!(output.0.logs().len(), 1); - } -} - -// 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. - -// 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. diff --git a/src/evm/all_states.rs b/src/evm/all_states.rs new file mode 100644 index 0000000..5879968 --- /dev/null +++ b/src/evm/all_states.rs @@ -0,0 +1,920 @@ +use crate::{ + db::{StateAcc, TryStateAcc}, + helpers::{Ctx, Evm, Instruction}, + inspectors::Layered, + ErroredState, EvmErrored, EvmExtUnchecked, Trevm, +}; +use alloy::{ + primitives::{Address, U256}, + rpc::types::state::StateOverride, +}; +use core::convert::Infallible; +use revm::{ + bytecode::opcode::DIFFICULTY, + context::{result::EVMError, Cfg as _, ContextTr}, + handler::EthPrecompiles, + inspector::NoOpInspector, + interpreter::instructions::block_info, + primitives::hardfork::SpecId, + state::{AccountInfo, Bytecode, EvmState}, + Database, DatabaseCommit, DatabaseRef, Inspector, +}; + +impl Trevm +where + Db: Database, + Insp: Inspector>, +{ + /// Get a reference to the current [`Evm`]. + /// + /// [`Evm`]: revm::context::Evm + pub fn inner(&self) -> &Evm { + self.as_ref() + } + + /// Get a mutable reference to the current [`Evm`]. This should be used with + /// caution, as modifying the EVM may lead to inconsistent Trevmstate or + /// invalid execution. + /// + /// [`Evm`]: revm::context::Evm + pub fn inner_mut_unchecked(&mut self) -> &mut Evm { + &mut self.inner + } + + /// Destructure the [`Trevm`] into its inner EVM. + pub fn into_inner(self) -> Box> { + self.inner + } + + /// Deconstruct the [`Trevm`] into the backing DB, dropping all other types. + pub fn into_db(self) -> Db { + self.inner.ctx.journaled_state.database + } + + /// Get a reference to the inspector. + pub fn inspector(&self) -> &Insp { + &self.inner.inspector + } + + /// Get a mutable reference to the inspector. + pub fn inspector_mut(&mut self) -> &mut Insp { + &mut self.inner.inspector + } + + /// Replace the current inspector with a new inspector of the same type. + pub fn replace_inspector(mut self, inspector: Insp) -> Self { + *self.inspector_mut() = inspector; + self + } + + /// Layer a new inspector on top of the current one. + pub fn layer_inspector( + self, + inspector: Insp2, + ) -> Trevm, TrevmState> + where + Insp2: Inspector>, + { + let inner = Box::new(Evm { + ctx: self.inner.ctx, + inspector: Layered::new(inspector, self.inner.inspector), + instruction: self.inner.instruction, + precompiles: self.inner.precompiles, + frame_stack: self.inner.frame_stack, + }); + Trevm { inner, state: self.state } + } + + /// Take the inspector out of the Trevm, replacing it with a + /// [`NoOpInspector`]. + pub fn take_inspector(self) -> (Insp, Trevm) { + let inspector = self.inner.inspector; + let inner = Box::new(Evm { + ctx: self.inner.ctx, + inspector: NoOpInspector, + instruction: self.inner.instruction, + precompiles: self.inner.precompiles, + frame_stack: self.inner.frame_stack, + }); + (inspector, Trevm { inner, state: self.state }) + } + + /// Replace the current inspector with a new one, dropping the old one. + pub fn set_inspector(self, inspector: Insp2) -> Trevm + where + Insp2: Inspector>, + { + let inner = Box::new(Evm { + ctx: self.inner.ctx, + inspector, + instruction: self.inner.instruction, + precompiles: self.inner.precompiles, + frame_stack: self.inner.frame_stack, + }); + Trevm { inner, state: self.state } + } + + /// Run the closure with a different inspector, then restore the previous + /// one. + pub fn with_inspector( + self, + inspector: Insp2, + f: F, + ) -> Trevm + where + Insp2: Inspector>, + F: FnOnce(Trevm) -> Trevm, + { + let (old, this) = self.take_inspector(); + let this = f(this.set_inspector(inspector)); + this.set_inspector(old) + } + + /// Run a fallible function with the provided inspector, then restore the + /// previous inspector. If the function returns an error, it will be + /// wrapped in an [`EvmErrored`] along with the current EVM state. + pub fn try_with_inspector( + self, + inspector: Insp2, + f: F, + ) -> Result, EvmErrored> + where + Insp2: Inspector>, + F: FnOnce( + Trevm, + ) -> Result, EvmErrored>, + { + let (previous, this) = self.take_inspector(); + let this = this.set_inspector(inspector); + + match f(this) { + Ok(evm) => Ok(evm.set_inspector(previous)), + Err(evm) => Err(evm.set_inspector(previous)), + } + } + + /// Get the id of the currently running hardfork spec. + pub fn spec_id(&self) -> SpecId { + self.inner.ctx.cfg().spec() + } + + /// Set the [SpecId], modifying the EVM handlers accordingly. This function + /// should be called at hardfork boundaries when running multi-block trevm + /// flows. + pub fn set_spec_id(&mut self, spec_id: SpecId) { + self.inner.modify_cfg(|cfg| cfg.spec = spec_id); + } + + /// Run a closure with a different [SpecId], then restore the previous + /// setting. + pub fn with_spec_id(mut self, spec_id: SpecId, f: F) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let old = self.spec_id(); + self.set_spec_id(spec_id); + let mut this = f(self); + this.set_spec_id(old); + this + } + + /// Convert self into [`EvmErrored`] by supplying an error + pub fn errored(self, error: E) -> EvmErrored { + EvmErrored { inner: self.inner, state: ErroredState { error } } + } + + /// Apply [`StateOverride`]s to the current state. Errors if the overrides + /// contain invalid bytecode. + pub fn apply_state_overrides( + mut self, + overrides: &StateOverride, + ) -> Result::Error>> + where + Db: DatabaseCommit, + { + for (address, account_override) in overrides { + if let Some(balance) = account_override.balance { + self.inner.set_balance(*address, balance).map_err(EVMError::Database)?; + } + if let Some(nonce) = account_override.nonce { + self.inner.set_nonce(*address, nonce).map_err(EVMError::Database)?; + } + if let Some(code) = account_override.code.as_ref() { + self.inner + .set_bytecode( + *address, + Bytecode::new_raw_checked(code.clone()) + .map_err(|_| EVMError::Custom("Invalid bytecode".to_string()))?, + ) + .map_err(EVMError::Database)?; + } + if let Some(state) = account_override.state.as_ref() { + for (slot, value) in state { + self.inner + .set_storage( + *address, + U256::from_be_bytes((*slot).into()), + U256::from_be_bytes((*value).into()), + ) + .map_err(EVMError::Database)?; + } + } + } + Ok(self) + } + + /// Apply [`StateOverride`]s to the current state, if they are provided. + pub fn maybe_apply_state_overrides( + self, + overrides: Option<&StateOverride>, + ) -> Result::Error>> + where + Db: DatabaseCommit, + { + if let Some(overrides) = overrides { + self.apply_state_overrides(overrides) + } else { + Ok(self) + } + } + + /// Overide an opcode with a custom handler. Returns the previous + /// instruction handler for the opcode. + pub fn override_opcode(&mut self, opcode: u8, handler: Instruction) -> Instruction { + std::mem::replace(&mut self.inner.instruction.instruction_table[opcode as usize], handler) + } + + /// Disable an opcode by replacing it with unknown opcode behavior. This is + /// a shortcut for [`Self::override_opcode`] with [`crate::helpers::forbidden`]. + pub fn disable_opcode(&mut self, opcode: u8) -> Instruction { + self.override_opcode(opcode, crate::helpers::forbidden) + } + + /// Run some closure with an opcode override, then restore the previous + /// setting. + pub fn with_opcode_override( + mut self, + opcode: u8, + handler: Instruction, + f: F, + ) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let old = self.override_opcode(opcode, handler); + self.inner.instruction.insert_instruction(opcode, handler); + let mut this = f(self); + this.override_opcode(opcode, old); + this + } + + /// Disable the prevrandao opcode, by replacing it with unknown opcode + /// behavior. This is useful for block simulation, where the prevrandao + /// opcode may produce incorrect results. + pub fn disable_prevrandao(&mut self) -> Instruction { + self.disable_opcode(DIFFICULTY) + } + + /// Enable the prevrandao opcode. If the prevrandao opcode was not + /// previously disabled or replaced, this will have no effect on behavior. + pub fn enable_prevrandao(&mut self) -> Instruction { + self.override_opcode(DIFFICULTY, block_info::difficulty) + } + + /// Run some code with the prevrandao opcode disabled, then restore the + /// previous setting. This is useful for block simulation, where the + /// prevrandao opcode may produce incorrect results. + pub fn without_prevrandao(self, f: F) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + self.with_opcode_override(DIFFICULTY, crate::helpers::forbidden, f) + } + + /// Set the precompiles for the EVM. This will replace the current + /// precompiles with the provided ones. + pub fn override_precompiles(&mut self, precompiles: EthPrecompiles) -> EthPrecompiles { + std::mem::replace(&mut self.inner.precompiles, precompiles) + } + + /// Run a closure with a different set of precompiles, then restore the + /// previous setting. + pub fn with_precompiles( + mut self, + precompiles: EthPrecompiles, + f: F, + ) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let old = self.override_precompiles(precompiles); + let mut this = f(self); + this.override_precompiles(old); + this + } +} + +// Fallible DB Reads with &mut self +impl Trevm +where + Db: Database, + Insp: Inspector>, +{ + /// Get the current account info for a specific address. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn try_read_account( + &mut self, + address: Address, + ) -> Result, ::Error> { + self.inner.db_mut().basic(address) + } + + /// Get the current nonce for a specific address + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn try_read_nonce(&mut self, address: Address) -> Result::Error> { + self.try_read_account(address).map(|a| a.map(|a| a.nonce).unwrap_or_default()) + } + + /// Get the current nonce for a specific address + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn try_read_balance(&mut self, address: Address) -> Result::Error> { + self.try_read_account(address).map(|a| a.map(|a| a.balance).unwrap_or_default()) + } + + /// Get the value of a storage slot. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn try_read_storage( + &mut self, + address: Address, + slot: U256, + ) -> Result::Error> { + self.inner.db_mut().storage(address, slot) + } + + /// Get the code at the given account, if any. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn try_read_code( + &mut self, + address: Address, + ) -> Result, ::Error> { + let acct_info = self.try_read_account(address)?; + match acct_info { + Some(acct) => Ok(Some(self.inner.db_mut().code_by_hash(acct.code_hash)?)), + None => Ok(None), + } + } + + /// Get the gas allowance for a specific caller and gas price. + pub fn try_gas_allowance( + &mut self, + caller: Address, + gas_price: u128, + ) -> Result::Error> { + if gas_price == 0 { + return Ok(u64::MAX); + } + let gas_price = U256::from(gas_price); + let balance = self.try_read_balance(caller)?; + Ok((balance / gas_price).saturating_to()) + } +} + +// Fallible DB Reads with &self +impl Trevm +where + Db: Database + DatabaseRef, + Insp: Inspector>, +{ + /// Get the current account info for a specific address. + pub fn try_read_account_ref( + &self, + address: Address, + ) -> Result, ::Error> { + self.inner.db_ref().basic_ref(address) + } + + /// Get the current nonce for a specific address + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn try_read_nonce_ref(&self, address: Address) -> Result::Error> { + self.try_read_account_ref(address).map(|a| a.map(|a| a.nonce).unwrap_or_default()) + } + + /// Get the current nonce for a specific address + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn try_read_balance_ref( + &self, + address: Address, + ) -> Result::Error> { + self.try_read_account_ref(address).map(|a| a.map(|a| a.balance).unwrap_or_default()) + } + + /// Get the value of a storage slot. + pub fn try_read_storage_ref( + &self, + address: Address, + slot: U256, + ) -> Result::Error> { + self.inner.db_ref().storage_ref(address, slot) + } + + /// Get the code at the given account, if any. + pub fn try_read_code_ref( + &self, + address: Address, + ) -> Result, ::Error> { + let acct_info = self.try_read_account_ref(address)?; + match acct_info { + Some(acct) => Ok(Some(self.inner.db_ref().code_by_hash_ref(acct.code_hash)?)), + None => Ok(None), + } + } + + /// Get the gas allowance for a specific caller and gas price. + pub fn try_gas_allowance_ref( + &self, + caller: Address, + gas_price: U256, + ) -> Result::Error> { + if gas_price.is_zero() { + return Ok(u64::MAX); + } + let gas_price = U256::from(gas_price); + let balance = self.try_read_balance_ref(caller)?; + Ok((balance / gas_price).saturating_to()) + } +} + +// Infallible DB Reads with &mut self +impl Trevm +where + Db: Database, + Insp: Inspector>, +{ + /// Get the current account info for a specific address. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn read_account(&mut self, address: Address) -> Option { + self.inner.db_mut().basic(address).expect("infallible") + } + + /// Get the current nonce for a specific address + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn read_nonce(&mut self, address: Address) -> u64 { + self.read_account(address).map(|a: AccountInfo| a.nonce).unwrap_or_default() + } + + /// Get the current nonce for a specific address + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn read_balance(&mut self, address: Address) -> U256 { + self.read_account(address).map(|a: AccountInfo| a.balance).unwrap_or_default() + } + + /// Get the value of a storage slot. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn read_storage(&mut self, address: Address, slot: U256) -> U256 { + self.inner.db_mut().storage(address, slot).expect("infallible") + } + + /// Get the code at the given account, if any. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn read_code(&mut self, address: Address) -> Option { + let acct_info = self.read_account(address)?; + Some(self.inner.db_mut().code_by_hash(acct_info.code_hash).expect("infallible")) + } +} + +// Infalible DB Reads with &self +impl Trevm +where + Db: Database + DatabaseRef, + Insp: Inspector>, +{ + /// Get the current account info for a specific address. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn read_account_ref(&self, address: Address) -> Option { + self.inner.db_ref().basic_ref(address).expect("infallible") + } + + /// Get the current nonce for a specific address + pub fn read_nonce_ref(&self, address: Address) -> u64 { + self.read_account_ref(address).map(|a: AccountInfo| a.nonce).unwrap_or_default() + } + + /// Get the current nonce for a specific address + pub fn read_balance_ref(&self, address: Address) -> U256 { + self.read_account_ref(address).map(|a: AccountInfo| a.balance).unwrap_or_default() + } + + /// Get the value of a storage slot. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn read_storage_ref(&self, address: Address, slot: U256) -> U256 { + self.inner.db_ref().storage_ref(address, slot).expect("infallible") + } + + /// Get the code at the given account, if any. + /// + /// Note: due to revm's DB model, this requires a mutable pointer. + pub fn read_code_ref(&self, address: Address) -> Option { + let acct_info = self.read_account_ref(address)?; + Some(self.inner.db_ref().code_by_hash_ref(acct_info.code_hash).expect("infallible")) + } +} + +impl Trevm +where + Db: Database, + Insp: Inspector>, +{ + /// Commit a set of state changes to the database. This is a low-level API, + /// and is not intended for general use. Regular users should prefer + /// executing a transaction. + pub fn commit_unchecked(&mut self, state: EvmState) + where + Db: DatabaseCommit, + { + self.inner.db_mut().commit(state); + } + + /// Modify an account with a closure and commit the modified account. This + /// is a low-level API, and is not intended for general use. + pub fn try_modify_account_unchecked( + &mut self, + address: Address, + f: F, + ) -> Result::Error> + where + Db: DatabaseCommit, + { + self.inner.modify_account(address, f) + } + + /// Set the nonce of an account, returning the previous nonce. This is a + /// low-level API, and is not intended for general use. + pub fn try_set_nonce_unchecked( + &mut self, + address: Address, + nonce: u64, + ) -> Result::Error> + where + Db: DatabaseCommit, + { + self.inner.set_nonce(address, nonce) + } + + /// Increment the nonce of an account, returning the previous nonce. This is + /// a low-level API, and is not intended for general use. + /// + /// If the nonce is already at the maximum value, it will not be + /// incremented. + pub fn try_increment_nonce_unchecked( + &mut self, + address: Address, + ) -> Result::Error> + where + Db: DatabaseCommit, + { + self.inner.increment_nonce(address) + } + + /// Decrement the nonce of an account, returning the previous nonce. This is + /// a low-level API, and is not intended for general use. + /// + /// If the nonce is already 0, it will not be decremented. + pub fn try_decrement_nonce_unchecked( + &mut self, + address: Address, + ) -> Result::Error> + where + Db: DatabaseCommit, + { + self.inner.decrement_nonce(address) + } + + /// Set the EVM storage at a slot. This is a low-level API, and is not + /// intended for general use. + pub fn try_set_storage_unchecked( + &mut self, + address: Address, + slot: U256, + value: U256, + ) -> Result::Error> + where + Db: DatabaseCommit, + { + self.inner.set_storage(address, slot, value) + } + + /// Set the bytecode at a specific address, returning the previous bytecode + /// at that address. This is a low-level API, and is not intended for + /// general use. + pub fn try_set_bytecode_unchecked( + &mut self, + address: Address, + bytecode: Bytecode, + ) -> Result, ::Error> + where + Db: DatabaseCommit, + { + self.inner.set_bytecode(address, bytecode) + } + + /// Increase the balance of an account. Returns the previous balance. This + /// is a low-level API, and is not intended for general use. + /// + /// If this would cause an overflow, the balance will be increased to the + /// maximum value. + pub fn try_increase_balance_unchecked( + &mut self, + address: Address, + amount: U256, + ) -> Result::Error> + where + Db: DatabaseCommit, + { + self.inner.increase_balance(address, amount) + } + + /// Decrease the balance of an account. Returns the previous balance. This + /// is a low-level API, and is not intended for general use. + /// + /// If this would cause an underflow, the balance will be decreased to 0. + pub fn try_decrease_balance_unchecked( + &mut self, + address: Address, + amount: U256, + ) -> Result::Error> + where + Db: DatabaseCommit, + { + self.inner.decrease_balance(address, amount) + } + + /// Set the balance of an account. Returns the previous balance. This is a + /// low-level API, and is not intended for general use. + pub fn try_set_balance_unchecked( + &mut self, + address: Address, + amount: U256, + ) -> Result::Error> + where + Db: DatabaseCommit, + { + self.inner.set_balance(address, amount) + } +} + +impl Trevm +where + Db: Database, + Insp: Inspector>, +{ + /// Modify an account with a closure and commit the modified account. This + /// is a low-level API, and is not intended for general use. + pub fn modify_account_unchecked( + &mut self, + address: Address, + f: impl FnOnce(&mut AccountInfo), + ) -> AccountInfo + where + Db: DatabaseCommit, + { + self.try_modify_account_unchecked(address, f).expect("infallible") + } + + /// Set the nonce of an account, returning the previous nonce. This is a + /// low-level API, and is not intended for general use. + pub fn set_nonce_unchecked(&mut self, address: Address, nonce: u64) -> u64 + where + Db: DatabaseCommit, + { + self.try_set_nonce_unchecked(address, nonce).expect("infallible") + } + + /// Increment the nonce of an account, returning the previous nonce. This is + /// a low-level API, and is not intended for general use. + /// + /// If this would cause the nonce to overflow, the nonce will be set to the + /// maximum value. + pub fn increment_nonce_unchecked(&mut self, address: Address) -> u64 + where + Db: DatabaseCommit, + { + self.try_increment_nonce_unchecked(address).expect("infallible") + } + + /// Decrement the nonce of an account, returning the previous nonce. This is + /// a low-level API, and is not intended for general use. + /// + /// If this would cause the nonce to underflow, the nonce will be set to 0. + pub fn decrement_nonce_unchecked(&mut self, address: Address) -> u64 + where + Db: DatabaseCommit, + { + self.try_decrement_nonce_unchecked(address).expect("infallible") + } + + /// Set the EVM storage at a slot. This is a low-level API, and is not + /// intended for general use. + pub fn set_storage_unchecked(&mut self, address: Address, slot: U256, value: U256) -> U256 + where + Db: DatabaseCommit, + { + self.try_set_storage_unchecked(address, slot, value).expect("infallible") + } + + /// Set the bytecode at a specific address, returning the previous bytecode + /// at that address. This is a low-level API, and is not intended for + /// general use. + pub fn set_bytecode_unchecked( + &mut self, + address: Address, + bytecode: Bytecode, + ) -> Option + where + Db: DatabaseCommit, + { + self.try_set_bytecode_unchecked(address, bytecode).expect("infallible") + } + + /// Increase the balance of an account. Returns the previous balance. This + /// is a low-level API, and is not intended for general use. + pub fn increase_balance_unchecked(&mut self, address: Address, amount: U256) -> U256 + where + Db: DatabaseCommit, + { + self.try_increase_balance_unchecked(address, amount).expect("infallible") + } + + /// Decrease the balance of an account. Returns the previous balance. This + /// is a low-level API, and is not intended for general use. + pub fn decrease_balance_unchecked(&mut self, address: Address, amount: U256) -> U256 + where + Db: DatabaseCommit, + { + self.try_decrease_balance_unchecked(address, amount).expect("infallible") + } + + /// Set the balance of an account. Returns the previous balance. This is a + /// low-level API, and is not intended for general use. + pub fn set_balance_unchecked(&mut self, address: Address, amount: U256) -> U256 + where + Db: DatabaseCommit, + { + self.try_set_balance_unchecked(address, amount).expect("infallible") + } +} + +// Layered inspector +impl Trevm, TrevmState> +where + Db: Database, + Outer: Inspector>, + Inner: Inspector>, +{ + /// Remove the outer-layer inspector, leaving the inner-layer inspector in + /// place. + pub fn take_outer(self) -> (Outer, Trevm) { + let (outer, inner) = self.inner.inspector.into_parts(); + + ( + outer, + Trevm { + inner: Box::new(Evm { + ctx: self.inner.ctx, + inspector: inner, + instruction: self.inner.instruction, + precompiles: self.inner.precompiles, + frame_stack: self.inner.frame_stack, + }), + state: self.state, + }, + ) + } + + /// Remove the outer-layer inspector, leaving the inner-layer inspector in + /// place. + pub fn remove_outer(self) -> Trevm { + self.take_outer().1 + } + + /// Remove the inner-layer inspector, leaving the outer-layer inspector in + /// place. + pub fn take_inner(self) -> (Inner, Trevm) { + let (outer, inner) = self.inner.inspector.into_parts(); + + ( + inner, + Trevm { + inner: Box::new(Evm { + ctx: self.inner.ctx, + inspector: outer, + instruction: self.inner.instruction, + precompiles: self.inner.precompiles, + frame_stack: self.inner.frame_stack, + }), + state: self.state, + }, + ) + } + + /// Remove the inner-layer inspector, leaving the outer-layer inspector in + /// place. + pub fn remove_inner(self) -> Trevm { + self.take_inner().1 + } +} + +// --- ALL STATES, WITH State + +impl Trevm +where + Db: Database + StateAcc, + Insp: Inspector>, +{ + /// Set the [EIP-161] state clear flag, activated in the Spurious Dragon + /// hardfork. + pub fn set_state_clear_flag(&mut self, flag: bool) { + self.inner.db_mut().set_state_clear_flag(flag) + } +} + +impl Trevm +where + Db: Database + TryStateAcc, + Insp: Inspector>, +{ + /// Fallibly set the [EIP-161] state clear flag, activated in the Spurious + /// Dragon hardfork. This function is intended to be used by shared states, + /// where mutable access may fail, e.g. an `Arc`. + /// + /// Prefer [`Self::set_state_clear_flag`] when available. + pub fn try_set_state_clear_flag( + &mut self, + flag: bool, + ) -> Result<(), ::Error> { + self.inner.db_mut().try_set_state_clear_flag(flag) + } +} + +// 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. + +// 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. diff --git a/src/builder.rs b/src/evm/builder.rs similarity index 55% rename from src/builder.rs rename to src/evm/builder.rs index 501e6a9..f16c2a4 100644 --- a/src/builder.rs +++ b/src/evm/builder.rs @@ -1,4 +1,4 @@ -use crate::{evm::Trevm, helpers::Ctx, states::EvmNeedsCfg}; +use crate::{evm::Trevm, helpers::Ctx, EvmNeedsCfg}; use revm::{ database::in_memory_db::InMemoryDB, handler::EthPrecompiles, inspector::NoOpInspector, precompile::Precompiles, primitives::hardfork::SpecId, Database, Inspector, MainBuilder, @@ -98,3 +98,53 @@ impl TrevmBuilder { Ok(Trevm::from(evm)) } } + +// 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. + +// 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. diff --git a/src/evm/errored.rs b/src/evm/errored.rs new file mode 100644 index 0000000..9b98cae --- /dev/null +++ b/src/evm/errored.rs @@ -0,0 +1,135 @@ +use crate::{helpers::Ctx, ErroredState, EvmErrored, EvmNeedsTx, NeedsTx, Trevm}; +use revm::{ + context::result::{EVMError, InvalidTransaction}, + Database, Inspector, +}; + +impl EvmErrored +where + Db: Database, + Insp: Inspector>, +{ + /// Get a reference to the error. + pub const fn error(&self) -> &E { + &self.state.error + } + + /// Inspect the error with a closure. + pub fn inspect_err(&self, f: F) -> T + where + F: FnOnce(&E) -> T, + { + f(self.error()) + } + + /// Discard the error and return the EVM. + pub fn discard_error(self) -> EvmNeedsTx { + Trevm { inner: self.inner, state: NeedsTx::new() } + } + + /// Convert the error into an [`EVMError`]. + pub fn into_error(self) -> E { + self.state.error + } + + /// Reset the EVM, returning the error and the EVM ready for the next + /// transaction. + pub fn take_err(self) -> (E, EvmNeedsTx) { + let Self { inner, state: ErroredState { error } } = self; + (error, Trevm { inner, state: NeedsTx::new() }) + } + + /// Transform the error into a new error type. + pub fn err_into>(self) -> EvmErrored { + self.map_err(Into::into) + } + + /// Map the error to a new error type. + pub fn map_err(self, f: F) -> EvmErrored + where + F: FnOnce(E) -> NewErr, + { + Trevm { inner: self.inner, state: ErroredState { error: f(self.state.error) } } + } +} + +impl EvmErrored +where + Db: Database, + Insp: Inspector>, +{ + /// Check if the error is a transaction error. This is provided as a + /// convenience function for common cases, as Transaction errors should + /// usually be discarded. + pub const fn is_transaction_error(&self) -> bool { + matches!(self.state.error, EVMError::Transaction(_)) + } + + /// Fallible cast to a [`InvalidTransaction`]. + pub const fn as_transaction_error(&self) -> Option<&InvalidTransaction> { + match &self.state.error { + EVMError::Transaction(err) => Some(err), + _ => None, + } + } + + /// Discard the error if it is a transaction error, returning the EVM. If + /// the error is not a transaction error, return self + pub fn discard_transaction_error(self) -> Result, Self> { + if self.is_transaction_error() { + Ok(self.discard_error()) + } else { + Err(self) + } + } +} + +// 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. + +// 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. diff --git a/src/connect.rs b/src/evm/factory.rs similarity index 56% rename from src/connect.rs rename to src/evm/factory.rs index 20fa529..35e54d5 100644 --- a/src/connect.rs +++ b/src/evm/factory.rs @@ -1,48 +1,13 @@ use crate::{ - helpers::Ctx, Block, Cfg, EvmErrored, EvmNeedsBlock, EvmNeedsCfg, EvmNeedsTx, EvmReady, - EvmTransacted, Tx, + db::DbConnect, helpers::Ctx, Block, Cfg, EvmErrored, EvmNeedsBlock, EvmNeedsCfg, EvmNeedsTx, + EvmReady, EvmTransacted, Tx, }; -use core::convert::Infallible; use revm::{ context::result::{EVMError, ResultAndState}, Database, Inspector, }; use std::format; -/// Trait for types that can be used to connect to a database. -/// -/// Connectors should contain configuration information like filesystem paths. -/// They are intended to enable parallel instantiation of multiple EVMs in -/// multiple threads sharing some database configuration -/// -/// The lifetime on this trait allows the resulting DB to borrow from the -/// 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: Sync { - /// The database type returned when connecting. - type Database: Database; - - /// The error type returned when connecting to the database. - type Error: core::error::Error; - - /// Connect to the database. - fn connect(&self) -> Result; -} - -impl DbConnect for Db -where - Db: Database + Clone + Sync, -{ - type Database = Self; - - type Error = Infallible; - - fn connect(&self) -> Result { - Ok(self.clone()) - } -} - /// Trait for types that can create EVM instances. /// /// Factories should contain configuration information like `Insp` types, and @@ -150,3 +115,53 @@ pub trait EvmFactory: DbConnect { } } } + +// 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. + +// 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. diff --git a/src/evm/has_block.rs b/src/evm/has_block.rs new file mode 100644 index 0000000..24dd534 --- /dev/null +++ b/src/evm/has_block.rs @@ -0,0 +1,129 @@ +use crate::{helpers::Ctx, Block, EvmErrored, HasBlock, Trevm}; +use alloy::primitives::{Address, U256}; +use revm::{ + context::{Block as _, BlockEnv, ContextSetters, ContextTr}, + Database, Inspector, +}; + +impl Trevm +where + Db: Database, + Insp: Inspector>, + TrevmState: HasBlock, +{ + /// Get a reference to the current block environment. + pub fn block(&self) -> &BlockEnv { + self.inner.block() + } + + /// Get the current block gas limit. + pub fn block_gas_limit(&self) -> u64 { + self.block().gas_limit() + } + + /// Get the current block number. + pub fn block_number(&self) -> U256 { + self.block().number() + } + + /// Get the current block timestamp. + pub fn block_timestamp(&self) -> U256 { + self.block().timestamp() + } + + /// Get the block beneficiary address. + pub fn beneficiary(&self) -> Address { + self.block().beneficiary() + } + + /// Run a function with the provided block, then restore the previous block. + pub fn with_block(mut self, b: &B, f: F) -> Trevm + where + B: Block, + F: FnOnce(Self) -> Trevm, + NewState: HasBlock, + { + let previous = self.inner.block().clone(); + b.fill_block(&mut self.inner); + + let mut this = f(self); + this.inner.ctx.set_block(previous); + this + } + + /// Run a fallible function with the provided block, then restore the previous block. + pub fn try_with_block( + mut self, + b: &B, + f: F, + ) -> Result, EvmErrored> + where + F: FnOnce(Self) -> Result, EvmErrored>, + B: Block, + NewState: HasBlock, + { + let previous = self.inner.block().clone(); + b.fill_block(&mut self.inner); + + match f(self) { + Ok(mut evm) => { + evm.inner.ctx.set_block(previous); + Ok(evm) + } + Err(mut evm) => { + evm.inner.ctx.set_block(previous); + Err(evm) + } + } + } +} + +// 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. + +// 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. diff --git a/src/evm/has_cfg.rs b/src/evm/has_cfg.rs new file mode 100644 index 0000000..285a60d --- /dev/null +++ b/src/evm/has_cfg.rs @@ -0,0 +1,318 @@ +use crate::{helpers::Ctx, Cfg, EvmErrored, HasCfg, Trevm}; +use revm::{context::ContextTr, Database, Inspector}; + +impl Trevm +where + Db: Database, + Insp: Inspector>, + TrevmState: HasCfg, +{ + /// Set the [EIP-170] contract code size limit. By default this is set to + /// 0x6000 bytes (~25KiB). Contracts whose bytecode is larger than this + /// limit cannot be deployed and will produce a [`CreateInitCodeSizeLimit`] + /// error. + /// + /// [`CreateInitCodeSizeLimit`]: revm::context::result::InvalidTransaction::CreateInitCodeSizeLimit + /// [`Eip-170`]: https://eips.ethereum.org/EIPS/eip-170 + pub fn set_code_size_limit(&mut self, limit: usize) -> Option { + let mut csl = None; + self.inner.ctx.modify_cfg(|cfg| { + csl = cfg.limit_contract_code_size.replace(limit); + }); + csl + } + + /// Disable the [EIP-170] contract code size limit, returning the previous + /// setting. + /// + /// [`Eip-170`]: https://eips.ethereum.org/EIPS/eip-170 + pub fn disable_code_size_limit(&mut self) -> Option { + let mut csl = None; + self.inner.ctx.modify_cfg(|cfg| csl = cfg.limit_contract_code_size.take()); + csl + } + + /// Run a closure with the code size limit disabled, then restore the + /// previous setting. + pub fn without_code_size_limit(mut self, f: F) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let limit = self.disable_code_size_limit(); + let mut new = f(self); + if let Some(limit) = limit { + new.set_code_size_limit(limit); + } + new + } + + /// Set the [EIP-170] contract code size limit to the default value of + /// 0x6000 bytes (~25KiB), returning the previous setting. Contracts whose + /// bytecode is larger than this limit cannot be deployed and will produce + /// a [`CreateInitCodeSizeLimit`] error. + /// + /// [`CreateInitCodeSizeLimit`]: revm::context::result::InvalidTransaction::CreateInitCodeSizeLimit + /// [`Eip-170`]: https://eips.ethereum.org/EIPS/eip-170 + pub fn set_default_code_size_limit(&mut self) -> Option { + self.set_code_size_limit(0x6000) + } + + /// Run a function with the provided configuration, then restore the + /// previous configuration. This will not affect the block and tx, if those + /// have been filled. + pub fn with_cfg(mut self, cfg: &C, f: F) -> Trevm + where + C: Cfg, + F: FnOnce(Self) -> Trevm, + NewState: HasCfg, + { + let previous = self.inner.cfg().clone(); + cfg.fill_cfg(&mut self.inner); + + let mut this = f(self); + this.inner.ctx.modify_cfg(|cfg| *cfg = previous); + this + } + + /// Run a fallible function with the provided configuration, then restore the + /// previous configuration. This will not affect the block and tx, if those + /// have been filled. + pub fn try_with_cfg( + mut self, + cfg: &C, + f: F, + ) -> Result, EvmErrored> + where + C: Cfg, + F: FnOnce(Self) -> Result, EvmErrored>, + NewState: HasCfg, + { + let previous = self.inner.cfg().clone(); + cfg.fill_cfg(&mut self.inner); + + match f(self) { + Ok(mut evm) => { + evm.inner.modify_cfg(|cfg| *cfg = previous); + Ok(evm) + } + Err(mut evm) => { + evm.inner.modify_cfg(|cfg| *cfg = previous); + Err(evm) + } + } + } + + /// Set a limit beyond which a callframe's memory cannot be resized. + /// Attempting to resize beyond this limit will result in a + /// [OutOfGasError::Memory] error. + /// + /// In cases where the gas limit may be extraordinarily high, it is + /// recommended to set this to a sane value to prevent memory allocation + /// panics. Defaults to `2^32 - 1` bytes per EIP-1985. + /// + /// [OutOfGasError::Memory]: revm::context::result::OutOfGasError::Memory + #[cfg(feature = "memory_limit")] + pub fn set_memory_limit(&mut self, new_limit: u64) -> u64 { + let mut ml = 0; + self.inner.ctx.modify_cfg(|cfg| ml = core::mem::replace(&mut cfg.memory_limit, new_limit)); + ml + } + + /// Disable balance checks. If the sender does not have enough balance to + /// cover the transaction, the sender will be given enough ether to ensure + /// execution doesn't fail. + #[cfg(feature = "optional_balance_check")] + pub fn disable_balance_check(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_balance_check = true) + } + + /// Enable balance checks. See [`Self::disable_balance_check`]. + #[cfg(feature = "optional_balance_check")] + pub fn enable_balance_check(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_balance_check = false) + } + + /// Run a closure with balance checks disabled, then restore the previous + /// setting. + #[cfg(feature = "optional_balance_check")] + pub fn without_balance_check(mut self, f: F) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let previous = self.inner.cfg().disable_balance_check; + self.disable_balance_check(); + let mut new = f(self); + new.inner.ctx.modify_cfg(|cfg| cfg.disable_balance_check = previous); + new + } + + /// Disable block gas limits. This allows transactions to execute even if + /// they gas needs exceed the block gas limit. This is useful for + /// simulating large transactions like forge scripts. + #[cfg(feature = "optional_block_gas_limit")] + pub fn disable_block_gas_limit(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_block_gas_limit = true); + } + + /// Enable block gas limits. See [`Self::disable_block_gas_limit`]. + #[cfg(feature = "optional_block_gas_limit")] + pub fn enable_block_gas_limit(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_block_gas_limit = false); + } + + /// Run a closure with block gas limits disabled, then restore the previous + /// setting. + #[cfg(feature = "optional_block_gas_limit")] + pub fn without_block_gas_limit(mut self, f: F) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let previous = self.inner.cfg().disable_block_gas_limit; + self.disable_block_gas_limit(); + let mut new = f(self); + new.inner.ctx.modify_cfg(|cfg| cfg.disable_block_gas_limit = previous); + new + } + + /// Disable [EIP-3607]. This allows transactions to originate from accounts + /// that contain code. This is useful for simulating smart-contract calls. + /// + /// [EIP-3607]: https://eips.ethereum.org/EIPS/eip-3607 + #[cfg(feature = "optional_eip3607")] + pub fn disable_eip3607(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_eip3607 = true); + } + + /// Enable [EIP-3607]. See [`Self::disable_eip3607`]. + /// + /// [EIP-3607]: https://eips.ethereum.org/EIPS/eip-3607 + #[cfg(feature = "optional_eip3607")] + pub fn enable_eip3607(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_eip3607 = false); + } + + /// Run a closure with [EIP-3607] disabled, then restore the previous + /// setting. + #[cfg(feature = "optional_eip3607")] + pub fn without_eip3607(mut self, f: F) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let previous = self.inner.cfg().disable_eip3607; + self.disable_eip3607(); + + let mut new = f(self); + new.inner.ctx.modify_cfg(|cfg| cfg.disable_eip3607 = previous); + new + } + + /// Disables [EIP-1559] base fee checks. This is useful for testing method + /// calls with zero gas price. + /// + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + #[cfg(feature = "optional_no_base_fee")] + pub fn disable_base_fee(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_base_fee = true) + } + + /// Enable [EIP-1559] base fee checks. See [`Self::disable_base_fee`]. + /// + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + #[cfg(feature = "optional_no_base_fee")] + pub fn enable_base_fee(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_base_fee = false) + } + + /// Run a closure with [EIP-1559] base fee checks disabled, then restore the + /// previous setting. + /// + /// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559 + #[cfg(feature = "optional_no_base_fee")] + pub fn without_base_fee(mut self, f: F) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let previous = self.inner.cfg().disable_base_fee; + self.disable_base_fee(); + + let mut new = f(self); + new.inner.ctx.modify_cfg(|cfg| cfg.disable_base_fee = previous); + new + } + + /// Disable nonce checks. This allows transactions to be sent with + /// incorrect nonces, and is useful for things like system transactions. + pub fn disable_nonce_check(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_nonce_check = true) + } + + /// Enable nonce checks. See [`Self::disable_nonce_check`]. + pub fn enable_nonce_check(&mut self) { + self.inner.ctx.modify_cfg(|cfg| cfg.disable_nonce_check = false) + } + + /// Run a closure with nonce checks disabled, then restore the previous + /// setting. This will not affect the block and tx, if those have been + /// filled. + pub fn without_nonce_check(mut self, f: F) -> Trevm + where + F: FnOnce(Self) -> Trevm, + { + let previous = self.inner.cfg().disable_nonce_check; + self.disable_nonce_check(); + + let mut new = f(self); + new.inner.ctx.modify_cfg(|cfg| cfg.disable_nonce_check = previous); + new + } +} + +// 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. + +// 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. diff --git a/src/evm/has_tx.rs b/src/evm/has_tx.rs new file mode 100644 index 0000000..c2e8a21 --- /dev/null +++ b/src/evm/has_tx.rs @@ -0,0 +1,350 @@ +use crate::{helpers::Ctx, EvmErrored, HasBlock, HasCfg, HasTx, Trevm, Tx}; +use alloy::primitives::{Address, Bytes, U256}; +use revm::{ + context::{result::EVMError, ContextSetters, ContextTr, Transaction as _, TxEnv}, + primitives::TxKind, + state::AccountInfo, + Database, DatabaseRef, Inspector, +}; + +impl Trevm +where + Db: Database, + Insp: Inspector>, + TrevmState: HasTx, +{ + #[cfg(feature = "call")] + pub(crate) fn try_with_call_filler( + self, + filler: &crate::fillers::CallFiller, + f: impl FnOnce(Self) -> Result, EvmErrored>, + ) -> Result, EvmErrored> { + // override all relevant env bits + self.try_with_cfg(filler, |this| this.try_with_block(filler, f)) + } + + /// Convenience function to use the estimator to fill both Cfg and Tx, and + /// run a fallible function. + #[cfg(feature = "estimate_gas")] + pub(crate) fn try_with_estimate_gas_filler( + self, + filler: &crate::fillers::GasEstimationFiller, + f: impl FnOnce(Self) -> Result>, + ) -> Result> { + self.try_with_cfg(filler, |this| this.try_with_tx(filler, f)) + } + + /// Get a reference to the loaded tx env that will be executed. + pub fn tx(&self) -> &TxEnv { + self.inner.tx() + } + /// True if the transaction is a simple transfer. + pub fn is_transfer(&self) -> bool { + self.inner.tx().input().is_empty() && self.to().is_call() + } + + /// True if the transaction is a contract creation. + pub fn is_create(&self) -> bool { + self.to().is_create() + } + + /// Get a reference to the transaction input data, which will be used as + /// calldata or initcode during EVM execution. + pub fn input(&self) -> &Bytes { + self.tx().input() + } + + /// Read the target of the transaction. + pub fn to(&self) -> TxKind { + self.tx().kind() + } + + /// Read the value in wei of the transaction. + pub fn value(&self) -> U256 { + self.tx().value() + } + + /// Get the gas limit of the loaded transaction. + pub fn gas_limit(&self) -> u64 { + self.tx().gas_limit() + } + + /// Get the gas price of the loaded transaction. + pub fn gas_price(&self) -> u128 { + self.tx().gas_price() + } + + /// Get the address of the caller. + pub fn caller(&self) -> Address { + self.tx().caller() + } + + /// Get the account of the caller. Error if the DB errors. + pub fn caller_account(&mut self) -> Result::Error>> { + self.try_read_account(self.caller()) + .map(Option::unwrap_or_default) + .map_err(EVMError::Database) + } + + /// Get the address of the callee. `None` if `Self::is_create` is true. + pub fn callee(&self) -> Option
{ + self.to().into() + } + + /// Get the account of the callee. + /// + /// Returns as follows: + /// - if `Self::is_create` is true, `Ok(None)` + /// - if the callee account does not exist, `Ok(AccountInfo::default())` + /// - if the DB errors, `Err(EVMError::Database(err))` + pub fn callee_account( + &mut self, + ) -> Result, EVMError<::Error>> { + self.callee().map_or(Ok(None), |addr| { + self.try_read_account(addr) + .map(Option::unwrap_or_default) + .map(Some) + .map_err(EVMError::Database) + }) + } + + /// Get the account of the callee. `None` if `Self::is_create` is true, + /// error if the DB errors. + pub fn callee_account_ref(&self) -> Result, ::Error> + where + Db: DatabaseRef, + { + self.callee().map_or(Ok(None), |addr| self.try_read_account_ref(addr)) + } + + /// Run a function with the provided transaction, then restore the previous + /// transaction. + pub fn with_tx(mut self, t: &T, f: F) -> Trevm + where + T: Tx, + F: FnOnce(Self) -> Trevm, + NewState: HasTx, + { + let previous = self.inner.tx().clone(); + t.fill_tx(&mut self.inner); + let mut this = f(self); + this.inner.ctx.set_tx(previous); + this + } + + /// Run a fallible function with the provided transaction, then restore the + /// previous transaction. + pub fn try_with_tx( + mut self, + t: &T, + f: F, + ) -> Result, EvmErrored> + where + T: Tx, + F: FnOnce(Self) -> Result, EvmErrored>, + NewState: HasTx, + { + let previous = self.inner.tx().clone(); + t.fill_tx(&mut self.inner); + match f(self) { + Ok(mut evm) => { + evm.inner.ctx.set_tx(previous); + Ok(evm) + } + Err(mut evm) => { + evm.inner.ctx.set_tx(previous); + Err(evm) + } + } + } + + /// Return the maximum gas that the caller can purchase. This is the balance + /// of the caller divided by the gas price. + pub fn caller_gas_allowance(&mut self) -> Result::Error>> { + // Avoid DB read if gas price is zero + let gas_price = self.gas_price(); + self.try_gas_allowance(self.caller(), gas_price).map_err(EVMError::Database) + } + + /// This function caps the gas limit of the transaction to the allowance of + /// the caller. + /// + /// This is useful for e.g. call simulation, where the exact amount of gas + /// used is less important than ensuring that the call succeeds and returns + /// a meaningful result. + /// + /// # Returns + /// + /// The gas limit after the operation. + pub fn cap_tx_gas_to_allowance(&mut self) -> Result::Error>> { + let allowance = self.caller_gas_allowance()?; + + self.inner.modify_tx(|tx| tx.gas_limit = tx.gas_limit.min(allowance)); + + Ok(self.gas_limit()) + } + + /// Cap the gas limit of the transaction to the minimum of the block gas + /// limit and the transaction's gas limit. + /// + /// This is useful for ensuring that the transaction does not exceed the + /// block gas limit, e.g. during call simulation. + /// + /// # Returns + /// + /// The gas limit after the operation. + pub fn cap_tx_gas_to_block_limit(&mut self) -> u64 { + let block_gas_limit = self.block_gas_limit(); + + self.inner.modify_tx(|tx| tx.gas_limit = tx.gas_limit.min(block_gas_limit)); + + self.tx().gas_limit + } + + /// This function caps the gas limit of the transaction to the minimum of + /// the block limit and the caller's gas allowance. + /// + /// This is equivalent to calling [`Self::cap_tx_gas_to_block_limit`] and + /// [`Self::cap_tx_gas_to_allowance`] in sequence. + /// + /// # Returns + /// + /// The gas limit after the operation. + pub fn cap_tx_gas(&mut self) -> Result::Error>> { + self.cap_tx_gas_to_block_limit(); + self.cap_tx_gas_to_allowance() + } +} + +#[cfg(test)] +mod tests { + use crate::{ + test_utils::{test_trevm_with_funds, ALICE, BOB, LOG_DEPLOYED_BYTECODE}, + NoopBlock, NoopCfg, TrevmBuilder, + }; + use alloy::{ + consensus::constants::ETH_TO_WEI, + network::{TransactionBuilder, TransactionBuilder7702}, + primitives::{Address, U256}, + rpc::types::{Authorization, TransactionRequest}, + signers::SignerSync, + }; + use revm::{ + context::transaction::AuthorizationTr, + database::InMemoryDB, + primitives::{bytes, hardfork::SpecId}, + state::Bytecode, + }; + + #[test] + fn test_estimate_gas_simple_transfer() { + let trevm = test_trevm_with_funds(&[ + (ALICE.address(), U256::from(ETH_TO_WEI)), + (BOB.address(), U256::from(ETH_TO_WEI)), + ]); + + let tx = TransactionRequest::default() + .from(ALICE.address()) + .to(BOB.address()) + .value(U256::from(ETH_TO_WEI / 2)); + + let (estimation, _trevm) = + trevm.fill_cfg(&NoopCfg).fill_block(&NoopBlock).fill_tx(&tx).estimate_gas().unwrap(); + + assert!(estimation.is_success()); + // The gas used should correspond to a simple transfer. + assert_eq!(estimation.gas_used(), 21000); + } + + #[test] + fn test_7702_authorization_estimation() { + // Insert the LogContract code + let db = InMemoryDB::default(); + let log_address = Address::repeat_byte(0x32); + + // Set up trevm, and test balances. + let mut trevm = + TrevmBuilder::new().with_db(db).with_spec_id(SpecId::PRAGUE).build_trevm().unwrap(); + let _ = trevm.test_set_balance(ALICE.address(), U256::from(ETH_TO_WEI)); + let _ = trevm.set_bytecode_unchecked(log_address, Bytecode::new_raw(LOG_DEPLOYED_BYTECODE)); + + // Bob will sign the authorization. + let authorization = Authorization { + chain_id: U256::ZERO, + address: log_address, + // We know Bob's nonce is 0. + nonce: 0, + }; + let signature = BOB.sign_hash_sync(&authorization.signature_hash()).unwrap(); + let signed_authorization = authorization.into_signed(signature); + assert_eq!(signed_authorization.authority().unwrap(), BOB.address()); + + let tx = TransactionRequest::default() + .from(ALICE.address()) + .to(BOB.address()) + .with_authorization_list(vec![signed_authorization]) + .with_input(bytes!("0x7b3ab2d0")); // emitHello() + + let (estimation, trevm) = + trevm.fill_cfg(&NoopCfg).fill_block(&NoopBlock).fill_tx(&tx).estimate_gas().unwrap(); + + assert!(estimation.is_success()); + + let tx = tx.with_gas_limit(estimation.limit()); + + let output = trevm.clear_tx().fill_tx(&tx).run().unwrap().accept(); + + assert!(output.0.is_success()); + assert_eq!(output.0.logs().len(), 1); + } +} + +// 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. + +// 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. diff --git a/src/evm/mod.rs b/src/evm/mod.rs new file mode 100644 index 0000000..6eecb0f --- /dev/null +++ b/src/evm/mod.rs @@ -0,0 +1,22 @@ +mod builder; +pub use builder::{TrevmBuilder, TrevmBuilderError}; + +mod factory; +pub use factory::EvmFactory; + +pub(crate) mod states; + +mod r#struct; +pub use r#struct::Trevm; + +// Impl blocks for Evm states +mod all_states; +mod errored; +mod has_block; +mod has_cfg; +mod has_tx; +mod need_block; +mod need_cfg; +mod need_tx; +mod ready; +mod transacted; diff --git a/src/evm/need_block.rs b/src/evm/need_block.rs new file mode 100644 index 0000000..2d91397 --- /dev/null +++ b/src/evm/need_block.rs @@ -0,0 +1,183 @@ +use crate::{ + db::{StateAcc, TryStateAcc}, + driver::DriveBlockResult, + helpers::Ctx, + Block, BlockDriver, ChainDriver, DriveChainResult, EvmErrored, EvmNeedsBlock, EvmNeedsTx, +}; +use revm::{ + context::ContextTr, + database::{states::bundle_state::BundleRetention, BundleState}, + Database, DatabaseCommit, Inspector, +}; + +impl EvmNeedsBlock +where + Db: Database, + Insp: Inspector>, +{ + /// Open a block, apply some logic, and return the EVM ready for the next + /// block. + pub fn drive_block(self, driver: &mut D) -> DriveBlockResult + where + D: BlockDriver, + Db: DatabaseCommit, + { + let trevm = self.fill_block(driver.block()); + let trevm = driver.run_txns(trevm)?; + + let trevm = trevm.close_block(); + + match driver.post_block(&trevm) { + Ok(_) => Ok(trevm), + Err(e) => Err(trevm.errored(e)), + } + } + + /// Drive trevm through a set of blocks. + /// + /// # Panics + /// + /// If the driver contains no blocks. + pub fn drive_chain(self, driver: &mut D) -> DriveChainResult + where + D: ChainDriver, + Db: DatabaseCommit, + { + let block_count = driver.blocks().len(); + + let mut trevm = self + .drive_block(&mut driver.blocks()[0]) + .map_err(EvmErrored::err_into::<>::Error>)?; + + if let Err(e) = driver.interblock(&trevm, 0) { + return Err(trevm.errored(e)); + } + + for i in 1..block_count { + trevm = { + let trevm = trevm + .drive_block(&mut driver.blocks()[i]) + .map_err(EvmErrored::err_into::<>::Error>)?; + if let Err(e) = driver.interblock(&trevm, i) { + return Err(trevm.errored(e)); + } + trevm + }; + } + Ok(trevm) + } + + /// Fill a block and return the EVM ready for a transaction. + /// + /// This does not perform any pre- or post-block logic. To manage block + /// lifecycles, use [`Self::drive_block`] or [`Self::drive_chain`] instead. + pub fn fill_block(mut self, filler: &B) -> EvmNeedsTx { + filler.fill_block(self.inner_mut_unchecked()); + // SAFETY: Same size and repr. Only phantomdata type changes + unsafe { core::mem::transmute(self) } + } +} + +impl EvmNeedsBlock +where + Db: Database + StateAcc, + Insp: Inspector>, +{ + /// Finish execution and return the outputs. + /// + /// If the State has not been built with + /// [revm::database::StateBuilder::with_bundle_update] then the returned + /// [`BundleState`] will be meaningless. + /// + /// See [`State::merge_transitions`] and [`State::take_bundle`]. + /// + /// [`State::merge_transitions`]: revm::database::State::merge_transitions + /// [`State::take_bundle`]: revm::database::State::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 + } +} + +impl EvmNeedsBlock +where + Db: Database + TryStateAcc, + Insp: Inspector>, +{ + /// Fallibly finish execution and return the outputs. This function is + /// intended to be used by shared states, where mutable access may fail, e. + /// g. an `Arc`. Prefer [`Self::finish`] when available. + /// + /// If the State has not been built with + /// [revm::database::StateBuilder::with_bundle_update] then the returned + /// [`BundleState`] will be meaningless. + /// + /// See [`State::merge_transitions`] and [`State::take_bundle`]. + /// + /// [`State::merge_transitions`]: revm::database::State::merge_transitions + /// [`State::take_bundle`]: revm::database::State::take_bundle + pub fn try_finish( + mut self, + ) -> Result::Error>> { + let db = self.inner.db_mut(); + + trevm_try!(db.try_merge_transitions(BundleRetention::Reverts), self); + + let bundle = trevm_try!(db.try_take_bundle(), self); + + Ok(bundle) + } +} + +// 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. + +// 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. diff --git a/src/evm/need_cfg.rs b/src/evm/need_cfg.rs new file mode 100644 index 0000000..b227901 --- /dev/null +++ b/src/evm/need_cfg.rs @@ -0,0 +1,65 @@ +use crate::{helpers::Ctx, Cfg, EvmNeedsBlock, EvmNeedsCfg}; +use revm::{Database, Inspector}; + +impl EvmNeedsCfg +where + Db: Database, + Insp: Inspector>, +{ + /// Fill the configuration environment. + pub fn fill_cfg(mut self, filler: &T) -> EvmNeedsBlock { + filler.fill_cfg(&mut self.inner); + // SAFETY: Same size and repr. Only phantomdata type changes + unsafe { core::mem::transmute(self) } + } +} + +// 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. + +// 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. diff --git a/src/evm/need_tx.rs b/src/evm/need_tx.rs new file mode 100644 index 0000000..b242748 --- /dev/null +++ b/src/evm/need_tx.rs @@ -0,0 +1,224 @@ +use crate::{ + db::{StateAcc, TryStateAcc}, + helpers::Ctx, + Block, BundleDriver, DriveBundleResult, EvmErrored, EvmNeedsBlock, EvmNeedsTx, EvmReady, + EvmTransacted, Tx, +}; +use alloy::rpc::types::BlockOverrides; +use revm::{ + context::{result::ExecutionResult, ContextTr}, + Database, DatabaseCommit, Inspector, +}; + +impl EvmNeedsTx +where + Db: Database, + Insp: Inspector>, +{ + /// Close the current block, returning the EVM ready for the next block. + pub fn close_block(self) -> EvmNeedsBlock { + // SAFETY: Same size and repr. Only phantomdata type changes + unsafe { core::mem::transmute(self) } + } + + /// Drive a bundle to completion, apply some post-bundle logic, and return the + /// EVM ready for the next bundle or tx. + pub fn drive_bundle(self, driver: &mut D) -> DriveBundleResult + where + D: BundleDriver, + Db: DatabaseCommit, + { + let trevm = driver.run_bundle(self)?; + + match driver.post_bundle(&trevm) { + Ok(_) => Ok(trevm), + Err(e) => Err(trevm.errored(e)), + } + } + + /// Fill the transaction environment. + pub fn fill_tx(mut self, filler: &T) -> EvmReady { + filler.fill_tx(&mut self.inner); + // SAFETY: Same size and repr. Only phantomdata type changes + unsafe { core::mem::transmute(self) } + } + + /// Execute a transaction. Shortcut for `fill_tx(tx).run()`. + pub fn run_tx( + self, + filler: &T, + ) -> Result, EvmErrored> { + self.fill_tx(filler).run() + } + + /// Simulate the transaction, and return the [`ExecutionResult`]. The + /// following modifications are made to the environment while simulating. + /// + /// - [EIP-3607] is disabled. + /// - Base fee checks are disabled. + /// - Nonce checks are disabled. + /// + /// [EIP-3607]: https://eips.ethereum.org/EIPS/eip-3607 + #[cfg(feature = "call")] + pub fn call_tx( + self, + filler: &T, + ) -> Result<(ExecutionResult, Self), EvmErrored> { + self.fill_tx(filler).call() + } + + /// Estimate the gas cost of a transaction. Shortcut for `fill_tx(tx). + /// estimate()`. Returns an [`EstimationResult`] and the EVM populated with + /// the transaction. + /// + /// [`EstimationResult`]: crate::EstimationResult + #[cfg(feature = "estimate_gas")] + pub fn estimate_tx_gas( + self, + filler: &T, + ) -> Result<(crate::EstimationResult, EvmReady), EvmErrored> { + self.fill_tx(filler).estimate_gas() + } +} + +impl EvmNeedsTx +where + Db: Database + StateAcc, + Insp: Inspector>, +{ + /// Apply block overrides to the current block. + /// + /// Note that this is NOT reversible. The overrides are applied directly to + /// the underlying state and these changes cannot be removed. If it is + /// important that you have access to the pre-change state, you should wrap + /// the existing DB in a new [`State`] and apply the overrides to that. + /// + /// [`State`]: revm::database::State + pub fn apply_block_overrides(mut self, overrides: &BlockOverrides) -> Self { + overrides.fill_block(&mut self.inner); + + if let Some(hashes) = overrides.block_hash.as_ref() { + self.inner.db_mut().set_block_hashes(hashes) + } + + self + } + + /// Apply block overrides to the current block, if they are provided. + /// + /// Note that this is NOT reversible. The overrides are applied directly to + /// the underlying state and these changes cannot be removed. If it is + /// important that you have access to the pre-change state, you should wrap + /// the existing DB in a new [`State`] and apply the overrides to that. + /// + /// [`State`]: revm::database::State + pub fn maybe_apply_block_overrides(self, overrides: Option<&BlockOverrides>) -> Self { + if let Some(overrides) = overrides { + self.apply_block_overrides(overrides) + } else { + self + } + } +} + +impl EvmNeedsTx +where + Db: Database + TryStateAcc, + Insp: Inspector>, +{ + /// Apply block overrides to the current block. This function is + /// intended to be used by shared states, where mutable access may fail, e. + /// g. an `Arc`. Prefer [`Self::apply_block_overrides`] when + /// available. + /// + /// Note that this is NOT reversible. The overrides are applied directly to + /// the underlying state and these changes cannot be removed. If it is + /// important that you have access to the pre-change state, you should wrap + /// the existing DB in a new [`State`] and apply the overrides to that. + /// + /// [`State`]: revm::database::State + pub fn try_apply_block_overrides( + mut self, + overrides: &BlockOverrides, + ) -> Result::Error>> { + overrides.fill_block(&mut self.inner); + + if let Some(hashes) = overrides.block_hash.as_ref() { + trevm_try!(self.inner.db_mut().try_set_block_hashes(hashes), self); + } + + Ok(self) + } + + /// Apply block overrides to the current block, if they are provided. This + /// function is intended to be used by shared states, where mutable access + /// may fail, e.g. an `Arc`.Prefer + /// [`Self::maybe_apply_block_overrides`] when available. + /// + /// Note that this is NOT reversible. The overrides are applied directly to + /// the underlying state and these changes cannot be removed. If it is + /// important that you have access to the pre-change state, you should wrap + /// the existing DB in a new [`State`] and apply the overrides to that. + /// + /// [`State`]: revm::database::State + pub fn try_maybe_apply_block_overrides( + self, + overrides: Option<&BlockOverrides>, + ) -> Result::Error>> { + if let Some(overrides) = overrides { + self.try_apply_block_overrides(overrides) + } else { + Ok(self) + } + } +} + +// 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. + +// 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. diff --git a/src/evm/ready.rs b/src/evm/ready.rs new file mode 100644 index 0000000..80627b6 --- /dev/null +++ b/src/evm/ready.rs @@ -0,0 +1,337 @@ +use crate::{ + helpers::Ctx, ErroredState, EvmErrored, EvmNeedsTx, EvmReady, EvmTransacted, TransactedState, + Trevm, +}; +use revm::{ + context::result::{EVMError, ExecutionResult}, + interpreter::gas::calculate_initial_tx_gas_for_tx, + Database, InspectEvm, Inspector, +}; + +impl EvmReady +where + Db: Database, + Insp: Inspector>, +{ + /// Clear the current transaction environment. + pub fn clear_tx(self) -> EvmNeedsTx { + // NB: we do not clear the tx env here, as we may read it during post-tx + // logic in a block driver + + // SAFETY: Same size and repr. Only phantomdata type changes + unsafe { core::mem::transmute(self) } + } + + /// Execute the loaded transaction. This is a wrapper around + /// [`InspectEvm::inspect_tx`] and produces either [`EvmTransacted`] or + /// [`EvmErrored`]. + pub fn run(mut self) -> Result, EvmErrored> { + let result = self.inner.inspect_tx(self.tx().clone()); + + let Self { inner, .. } = self; + + match result { + Ok(result) => Ok(Trevm { inner, state: TransactedState { result } }), + Err(error) => Err(EvmErrored { inner, state: ErroredState { error } }), + } + } + + /// Simulate the transaction, and return the [`ExecutionResult`]. The + /// following modifications are made to the environment while simulating. + /// + /// - [EIP-3607] is disabled. + /// - Base fee checks are disabled. + /// - Nonce checks are disabled. + /// + /// [EIP-3607]: https://eips.ethereum.org/EIPS/eip-3607 + #[cfg(feature = "call")] + pub fn call(self) -> Result<(ExecutionResult, EvmNeedsTx), EvmErrored> { + let mut output = std::mem::MaybeUninit::uninit(); + + let gas_limit = self.tx().gas_limit; + + let this = + self.try_with_call_filler(&crate::fillers::CallFiller { gas_limit }, |this| { + let t = this.run()?; + + let (o, t) = t.take_result(); + + output.write(o); + + Ok(t) + })?; + Ok((unsafe { output.assume_init() }, this)) + } + + /// Calculate the minimum gas required to start EVM execution. + /// + /// This uses [`calculate_initial_tx_gas_for_tx`] to calculate the initial + /// gas. Its output is dependent on + /// - the EVM spec + /// - the input data + /// - whether the transaction is a contract creation or a call + /// - the EIP-2930 access list + /// - the number of [EIP-7702] authorizations + /// + /// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930 + /// [EIP-7702]: https://eips.ethereum.org/EIPS/eip-7702 + pub fn calculate_initial_gas(&self) -> u64 { + calculate_initial_tx_gas_for_tx(self.tx(), self.spec_id()).initial_gas + } + + /// Estimate gas for a simple transfer. This will + /// - Check that the transaction has no input data. + /// - Check that the target is not a `create`. + /// - Check that the target is not a contract. + /// - Return the minimum gas required for the transfer. + #[cfg(feature = "estimate_gas")] + fn estimate_gas_simple_transfer( + &mut self, + ) -> Result, EVMError<::Error>> { + use alloy::consensus::constants::KECCAK_EMPTY; + use tracing::trace; + + if !self.is_transfer() { + return Ok(None); + } + + // Shortcut if the tx is create, otherwise read the account + let Some(acc) = self.callee_account()? else { return Ok(None) }; + + // If the code hash is not empty, then the target is a contract + if acc.code_hash != KECCAK_EMPTY { + return Ok(None); + } + + // delegate calculation to revm. This ensures that things like bogus + // 2930 access lists don't mess up our estimates + let initial = self.calculate_initial_gas(); + trace!(initial, "using initial gas for simple transfer"); + Ok(Some(initial)) + } + + /// Convenience function to simplify nesting of [`Self::estimate_gas`]. + #[cfg(feature = "estimate_gas")] + fn run_estimate( + self, + filler: &crate::fillers::GasEstimationFiller, + ) -> Result<(crate::EstimationResult, Self), EvmErrored> { + use tracing::trace; + + let mut estimation = std::mem::MaybeUninit::uninit(); + + let this = self.try_with_estimate_gas_filler(filler, |this| match this.run() { + Ok(trevm) => { + let (e, t) = trevm.take_estimation(); + + estimation.write(e); + Ok(t) + } + Err(err) => Err(err), + })?; + + // SAFETY: if we did not shortcut return, then estimation was + // definitely written + Ok((unsafe { estimation.assume_init() }, this)) + .inspect(|(est, _)| trace!(?est, "gas estimation result",)) + } + + /// Implements gas estimation. This will output an estimate of the minimum + /// amount of gas that the transaction will consume, calculated via + /// iterated simulation. + /// + /// In the worst case this will perform a binary search, resulting in + /// `O(log(n))` simulations. + /// + /// ## Returns + /// + /// An [`EstimationResult`] and the EVM with the transaction populated. + /// Like with the remainder of the API, an EVM revert or an EVM halt is + /// NOT an error. An [`Err`] is returned only if the EVM encounters a + /// condition of use violation or a DB access fails. + /// + /// ## Estimation Algorithm + /// + /// This function is largely based on the reth RPC estimation algorithm, + /// which can be found [here]. The algorithm is as follows: + /// + /// - Disable eip-3607, allowing estimation from contract accounts. + /// - Disable base fee checks. + /// - Check if the transaction is a simple transfer + /// - Is there input data empty? If yes, proceed to regular estimation + /// - Is the callee a contract? If yes, proceed to regular estimation + /// - Otherwise, shortcut return success with [`MIN_TRANSACTION_GAS`]. + /// - Simulate the transaction with the maximum possible gas limit. + /// - If the simulation fails, shortcut return the failure. + /// - If succesful, store the gas used as the search minimum. + /// - Simulate the transaction with an "optimistic" gas limit. + /// - If the simulation fails, shortcut return the failure. + /// - If succesful, begin the binary search around that range. + /// - Binary search loop: + /// - If the search range is small enough, break the loop and return + /// the current estimate. + /// - Calculate a new gas limit based on the midpoint of the search + /// range. + /// - Simulate the transaction with the new gas limit. + /// - Adjust the search range based on the simulation result: + /// - If the result is a success, pull the search max down to the + /// limit. + /// - If the result is a revert, push the search min up to the + /// limit. + /// - If the result is a halt, check if the halt is potentially a + /// gas-dynamic halt. + /// - If it is, treat it as a revert. + /// - If it is not, shortcut return the halt. + /// - Loop. + /// + /// [here]: https://github.com/paradigmxyz/reth/blob/ad503a08fa242b28ad3c1fea9caa83df2dfcf72d/crates/rpc/rpc-eth-api/src/helpers/estimate.rs#L35-L42 + /// [`EstimationREsult`]: crate::EstimationResult + /// [`MIN_TRANSACTION_GAS`]: crate::MIN_TRANSACTION_GAS + #[cfg(feature = "estimate_gas")] + pub fn estimate_gas(mut self) -> Result<(crate::EstimationResult, Self), EvmErrored> { + use tracing::{debug, enabled, trace}; + + if let Some(est) = trevm_try!(self.estimate_gas_simple_transfer(), self) { + return Ok((crate::EstimationResult::basic_transfer_success(est), self)); + } + + // We shrink the gas limit to 64 bits, as using more than 18 quintillion + // gas in a block is unlikely. + let initial_limit = self.gas_limit(); + + // Start the search range at 21_000 gas. + let mut search_range = + crate::est::SearchRange::new(crate::MIN_TRANSACTION_GAS, initial_limit); + + let span = tracing::debug_span!( + "Trevm::estimate_gas", + start_min = search_range.min(), + start_max = search_range.max(), + ); + if enabled!(tracing::Level::TRACE) { + span.record("tx", format!("{:?}", &self.tx())); + span.record("block", format!("{:?}", &self.block())); + } else { + span.record("tx", "omitted. Use TRACE for details"); + } + let _e = span.enter(); + + // Cap the gas limit to the caller's allowance and block limit + trevm_try!(self.cap_tx_gas(), self); + search_range.maybe_lower_max(self.gas_limit()); + + // Raise the floor to the amount of gas required to initialize the EVM. + search_range.maybe_raise_min(self.calculate_initial_gas()); + + // Run an estimate with the max gas limit. + // NB: we declare these mut as we re-use the binding throughout the + // function. + debug!(gas_limit = self.gas_limit(), "running optimistic estimate"); + let (mut estimate, mut trevm) = self.run_estimate(&search_range.max().into())?; + + // If it failed, no amount of gas is likely to work, so we shortcut + // return. + if estimate.is_failure() { + debug!(%estimate, "optimistic estimate failed"); + return Ok((estimate, trevm)); + } + trace!(%estimate, "optimistic estimate succeeded"); + + // Now we know that it succeeds at _some_ gas limit. We can now binary + // search. We start by recording the initial best estimate. We'll update + // this best-estimate as we go inside the `estimate_and_adjust` macro + // invocations. + let mut best = estimate.clone(); + let mut gas_used = estimate.gas_used(); + let gas_refunded = estimate.gas_refunded().expect("checked is_failure"); + + // NB: if we've made it this far it's very unlikely that `gas_used` is + // less than 21_000, but we'll check anyway. + search_range.maybe_raise_min(gas_used - 1); + + // NB: This is a heuristic adopted from geth and reth + // The goal is to check if the first-run is actually very close to the + // real estimate, thereby cutting down on the number of iterations in + // the later search loop. + // https://github.com/ethereum/go-ethereum/blob/a5a4fa7032bb248f5a7c40f4e8df2b131c4186a4/eth/gasestimator/gasestimator.go#L132-L135 + // NB: 64 / 63 is due to Ethereum's gas-forwarding rules. Each call + // frame can forward only 63/64 of the gas it has when it makes a new + // frame. + let mut needle = (gas_used + gas_refunded + revm::interpreter::gas::CALL_STIPEND) * 64 / 63; + + // If the first search is outside the range, we don't need to try it. + if search_range.contains(needle) { + estimate_and_adjust!(best, estimate, trevm, needle, search_range); + // NB: `estimate` is rebound in the macro, so do not move this line + // up. + gas_used = estimate.gas_used(); + } + + // NB: This is a heuristic adopted from reth. + // Pick a point that's close to the estimated gas + needle = std::cmp::min(gas_used * 3, search_range.midpoint()); + + // Binary search loop. + // The second conditional is a heuristic adopted from geth and reth. + // An estimation error is allowed once the current gas limit range + // used in the binary search is small enough (less than 1.5% of the + // highest gas limit) + // 1 && search_range.ratio() > 0.015 { + estimate_and_adjust!(best, estimate, trevm, needle, search_range); + needle = search_range.midpoint(); + } + + Ok((best, trevm)) + } +} + +// 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. + +// 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. diff --git a/src/states.rs b/src/evm/states.rs similarity index 100% rename from src/states.rs rename to src/evm/states.rs diff --git a/src/evm/struct.rs b/src/evm/struct.rs new file mode 100644 index 0000000..6a35d47 --- /dev/null +++ b/src/evm/struct.rs @@ -0,0 +1,48 @@ +use crate::{ + helpers::{Ctx, Evm}, + EvmNeedsCfg, NeedsCfg, +}; +use core::fmt; +use revm::{inspector::NoOpInspector, Database, Inspector}; + +/// Trevm provides a type-safe interface to the EVM, using the typestate pattern. +/// +/// See the [crate-level documentation](crate) for more information. +pub struct Trevm +where + Db: Database, + Insp: Inspector>, +{ + pub(crate) inner: Box>, + pub(crate) state: TrevmState, +} + +impl fmt::Debug for Trevm +where + Db: Database, + Insp: Inspector>, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Trevm").finish_non_exhaustive() + } +} + +impl AsRef> for Trevm +where + Db: Database, + Insp: Inspector>, +{ + fn as_ref(&self) -> &Evm { + &self.inner + } +} + +impl From> for EvmNeedsCfg +where + Db: Database, + Insp: Inspector>, +{ + fn from(inner: Evm) -> Self { + Self { inner: Box::new(inner), state: NeedsCfg::new() } + } +} diff --git a/src/evm/transacted.rs b/src/evm/transacted.rs new file mode 100644 index 0000000..82ee8a1 --- /dev/null +++ b/src/evm/transacted.rs @@ -0,0 +1,263 @@ +use crate::{ + helpers::Ctx, EvmErrored, EvmNeedsTx, EvmReady, EvmTransacted, NeedsTx, TransactedState, Trevm, +}; +use alloy::primitives::Bytes; +use revm::{ + context::{ + result::{ExecutionResult, ResultAndState}, + ContextTr, + }, + database::TryDatabaseCommit, + state::EvmState, + Database, DatabaseCommit, Inspector, +}; + +impl AsRef for EvmTransacted +where + Db: Database, + Insp: Inspector>, +{ + fn as_ref(&self) -> &ResultAndState { + &self.state.result + } +} + +impl AsRef for EvmTransacted +where + Db: Database, + Insp: Inspector>, +{ + fn as_ref(&self) -> &ExecutionResult { + &self.state.result.result + } +} + +impl EvmTransacted +where + Db: Database, + Insp: Inspector>, +{ + /// Get a reference to the result. + pub fn result(&self) -> &ExecutionResult { + self.as_ref() + } + + /// Get a mutable reference to the result. Modification of the result is + /// discouraged, as it may lead to inconsistent state. + /// + /// This is primarily intended for use in [`SystemTx`] execution. + /// + /// [`SystemTx`]: crate::system::SystemTx + pub const fn result_mut_unchecked(&mut self) -> &mut ExecutionResult { + &mut self.state.result.result + } + + /// Get a reference to the state. + pub const fn state(&self) -> &EvmState { + &self.state.result.state + } + + /// Get a mutable reference to the state. Modification of the state is + /// discouraged, as it may lead to inconsistent state. + pub const fn state_mut_unchecked(&mut self) -> &mut EvmState { + &mut self.state.result.state + } + + /// Get a reference to the result and state. + pub fn result_and_state(&self) -> &ResultAndState { + self.as_ref() + } + + /// Get a mutable reference to the result and state. Modification of the + /// result and state is discouraged, as it may lead to inconsistent state. + /// + /// This is primarily intended for use in [`SystemTx`] execution. + /// + /// [`SystemTx`]: crate::system::SystemTx + pub const fn result_and_state_mut_unchecked(&mut self) -> &mut ResultAndState { + &mut self.state.result + } + + /// Get the output of the transaction. This is the return value of the + /// outermost callframe. + pub fn output(&self) -> Option<&Bytes> { + self.result().output() + } + + /// Get the output of the transaction, and decode it as the return value of + /// a [`SolCall`]. If `validate` is true, the output will be type- and + /// range-checked. + /// + /// [`SolCall`]: alloy::sol_types::SolCall + pub fn output_sol( + &self, + validate: bool, + ) -> Option> + where + T::Return: alloy::sol_types::SolType, + { + if validate { + return self.output().map(|output| T::abi_decode_returns_validate(output)); + } + + self.output().map(|output| T::abi_decode_returns(output)) + } + + /// Get the gas used by the transaction. + pub fn gas_used(&self) -> u64 { + self.state.result.result.gas_used() + } + + /// Discard the state changes and return the EVM. + pub fn reject(self) -> EvmNeedsTx { + Trevm { inner: self.inner, state: NeedsTx::new() } + } + + /// Take the [`ResultAndState`] and return the EVM. + pub fn into_result_and_state(self) -> ResultAndState { + self.state.result + } + + /// Take the [`ResultAndState`] and return the EVM. + pub fn take_result_and_state(self) -> (ResultAndState, EvmNeedsTx) { + let Self { inner, state: TransactedState { result } } = self; + (result, Trevm { inner, state: NeedsTx::new() }) + } + + /// Take the [`ExecutionResult`], discard the [`EvmState`] and return the + /// EVM. + pub fn take_result(self) -> (ExecutionResult, EvmNeedsTx) { + let Self { inner, state: TransactedState { result } } = self; + (result.result, Trevm { inner, state: NeedsTx::new() }) + } + + /// Accept the state changes, commiting them to the database, and return the + /// EVM with the [`ExecutionResult`]. + pub fn accept(self) -> (ExecutionResult, EvmNeedsTx) + where + Db: DatabaseCommit, + { + let Self { mut inner, state: TransactedState { result } } = self; + + inner.db_mut().commit(result.state); + + (result.result, Trevm { inner, state: NeedsTx::new() }) + } + + /// Try to accept the state changes, commiting them to the database, and + /// return the EVM with the [`ExecutionResult`]. If the commit fails, return + /// the EVM with the error, discarding the state changes. This is a fallible + /// version of [`Self::accept`], intended for use with databases that can + /// fail to commit. Prefer [`Self::accept`] when possible. + // Type alias would make it less clear I think + #[allow(clippy::type_complexity)] + pub fn try_accept( + self, + ) -> Result< + (ExecutionResult, EvmNeedsTx), + EvmErrored::Error>, + > + where + Db: TryDatabaseCommit, + { + let Self { mut inner, state: TransactedState { result } } = self; + + trevm_try!(inner.db_mut().try_commit(result.state), Trevm { inner, state: NeedsTx::new() }); + + Ok((result.result, Trevm { inner, state: NeedsTx::new() })) + } + + /// Accept the state changes, commiting them to the database, dropping the + /// [`ExecutionResult`]. + pub fn accept_state(self) -> EvmNeedsTx + where + Db: DatabaseCommit, + { + self.accept().1 + } + + /// Try to accept the state changes, commiting them to the database. If the + /// commit fails, return the EVM with the error, discarding the state + /// changes. This is a fallible version of [`Self::accept_state`], intended + /// for use with databases that can fail to commit. Prefer + /// [`Self::accept_state`] when possible. + pub fn try_accept_state( + self, + ) -> Result, EvmErrored::Error>> + where + Db: TryDatabaseCommit, + { + self.try_accept().map(|(_, evm)| evm) + } + + /// Create an [`EstimationResult`] from the transaction [`ExecutionResult`]. + /// + /// [`EstimationResult`]: crate::EstimationResult + #[cfg(feature = "estimate_gas")] + pub fn estimation(&self) -> crate::EstimationResult { + use crate::EstimationResult; + + EstimationResult::from_limit_and_execution_result(self.gas_limit(), self.result()) + } + + /// Take the [`EstimationResult`] and return it and the EVM. This discards + /// pending state changes, but leaves the EVM ready to execute the same + /// transaction again. + /// + /// [`EstimationResult`]: crate::EstimationResult + #[cfg(feature = "estimate_gas")] + pub fn take_estimation(self) -> (crate::EstimationResult, EvmReady) { + let estimation = self.estimation(); + (estimation, Trevm { inner: self.inner, state: crate::Ready::new() }) + } +} + +// 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. + +// 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. diff --git a/src/lib.rs b/src/lib.rs index e7261a7..4afe088 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -368,14 +368,9 @@ #[macro_use] mod macros; -mod builder; -pub use builder::{TrevmBuilder, TrevmBuilderError}; - -mod connect; -pub use connect::{DbConnect, EvmFactory}; - /// Contains database implementations and related pub mod db; +pub use db::DbConnect; mod driver; pub use driver::{ @@ -384,7 +379,14 @@ pub use driver::{ }; mod evm; -pub use evm::Trevm; +pub(crate) use evm::states::sealed::*; +pub use evm::{ + states::{ + EvmBlockDriverErrored, EvmBundleDriverErrored, EvmChainDriverErrored, EvmErrored, + EvmNeedsBlock, EvmNeedsCfg, EvmNeedsTx, EvmReady, EvmTransacted, + }, + EvmFactory, Trevm, TrevmBuilder, TrevmBuilderError, +}; #[cfg(feature = "estimate_gas")] mod est; @@ -408,13 +410,6 @@ pub mod journal; mod lifecycle; pub use lifecycle::{ethereum_receipt, BlockOutput, PostTx, PostflightResult}; -mod states; -pub(crate) use states::sealed::*; -pub use states::{ - EvmBlockDriverErrored, EvmBundleDriverErrored, EvmChainDriverErrored, EvmErrored, - EvmNeedsBlock, EvmNeedsCfg, EvmNeedsTx, EvmReady, EvmTransacted, -}; - pub mod system; pub use revm; diff --git a/src/test_utils.rs b/src/test_utils.rs index 39b53a7..68ca95c 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -1,5 +1,3 @@ -use std::sync::LazyLock; - use crate::{helpers::Ctx, EvmNeedsCfg, Trevm}; use alloy::{ primitives::{Address, U256}, @@ -16,6 +14,7 @@ use revm::{ state::AccountInfo, Context, Inspector, MainBuilder, }; +use std::sync::LazyLock; /// LogContract bytecode /// This is the runtime bytecode. This should be set directly with ``set_bytecode_unchecked`` From bbe516c829394bd2b8db500c6b1ea7edc5d436bc Mon Sep 17 00:00:00 2001 From: James Date: Fri, 26 Sep 2025 14:44:23 -0400 Subject: [PATCH 2/3] docs: update DbConnect docs --- src/db/traits.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/db/traits.rs b/src/db/traits.rs index 232fe75..c130ab9 100644 --- a/src/db/traits.rs +++ b/src/db/traits.rs @@ -11,10 +11,12 @@ use std::{collections::BTreeMap, convert::Infallible, sync::Arc}; /// They are intended to enable parallel instantiation of multiple EVMs in /// multiple threads sharing some database configuration /// -/// The lifetime on this trait allows the resulting DB to borrow from the -/// 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. +/// `DbConnect` is blanket implemented for clonable [`Database`] types by +/// simply cloning the database instance. This allows already-instantiated DBs +/// to be used as connectors, however, if the [`Database`] uses a shared +/// resource like a file or network connection, care should be taken to ensure +/// that the implementation does not share uintended state between EVM +/// instances. pub trait DbConnect: Sync { /// The database type returned when connecting. type Database: Database; From e3c3b4d1aae0290888f98e7cb53e5bc719a4dd9f Mon Sep 17 00:00:00 2001 From: James Date: Fri, 26 Sep 2025 15:00:55 -0400 Subject: [PATCH 3/3] fix: update revm-inspectors --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d095f77..ace3a8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ alloy = { version = "1.0.25", default-features = false, features = [ ] } revm = { version = "27.1", default-features = false } -revm-inspectors = { version = "0.27.1", optional = true } +revm-inspectors = { version = "0.27.3", optional = true } dashmap = { version = "6.1.0", optional = true } tracing = { version = "0.1.41", optional = true }