diff --git a/crates/revm/src/evm.rs b/crates/revm/src/evm.rs index 24b62f51b4..526b73fb2a 100644 --- a/crates/revm/src/evm.rs +++ b/crates/revm/src/evm.rs @@ -72,6 +72,29 @@ impl EVM { } impl EVM { + /// Do checks that could make transaction fail before call/create + pub fn preverify_transaction(&mut self) -> Result<(), EVMError> { + if let Some(db) = self.db.as_mut() { + let mut noop = NoOpInspector {}; + let out = evm_inner::(&mut self.env, db, &mut noop).preverify_transaction(); + out + } else { + panic!("Database needs to be set"); + } + } + + /// Skip preverification steps and execute transaction + /// without writing to DB, return change state. + pub fn transact_preverified(&mut self) -> EVMResult { + if let Some(db) = self.db.as_mut() { + let mut noop = NoOpInspector {}; + let out = evm_inner::(&mut self.env, db, &mut noop).transact_preverified(); + out + } else { + panic!("Database needs to be set"); + } + } + /// Execute transaction without writing to DB, return change state. pub fn transact(&mut self) -> EVMResult { if let Some(db) = self.db.as_mut() { @@ -94,6 +117,37 @@ impl EVM { } impl<'a, DB: DatabaseRef> EVM { + /// Do checks that could make transaction fail before call/create + pub fn preverify_transaction_ref(&self) -> Result<(), EVMError> { + if let Some(db) = self.db.as_ref() { + let mut noop = NoOpInspector {}; + let mut db = RefDBWrapper::new(db); + let db = &mut db; + let out = + evm_inner::, false>(&mut self.env.clone(), db, &mut noop) + .preverify_transaction(); + out + } else { + panic!("Database needs to be set"); + } + } + + /// Skip preverification steps and execute transaction + /// without writing to DB, return change state. + pub fn transact_preverified_ref(&self) -> EVMResult { + if let Some(db) = self.db.as_ref() { + let mut noop = NoOpInspector {}; + let mut db = RefDBWrapper::new(db); + let db = &mut db; + let out = + evm_inner::, false>(&mut self.env.clone(), db, &mut noop) + .transact_preverified(); + out + } else { + panic!("Database needs to be set"); + } + } + /// Execute transaction without writing to DB, return change state. pub fn transact_ref(&self) -> EVMResult { if let Some(db) = self.db.as_ref() { diff --git a/crates/revm/src/evm_impl.rs b/crates/revm/src/evm_impl.rs index 55d2026f06..b8f57d805b 100644 --- a/crates/revm/src/evm_impl.rs +++ b/crates/revm/src/evm_impl.rs @@ -60,6 +60,12 @@ struct CallResult { } pub trait Transact { + /// Do checks that could make transaction fail before call/create + fn preverify_transaction(&mut self) -> Result<(), EVMError>; + + /// Skip preverification steps and do transaction + fn transact_preverified(&mut self) -> EVMResult; + /// Do transaction. /// InstructionResult InstructionResult, Output for call or Address if we are creating /// contract, gas spend, gas refunded, State that needs to be applied. @@ -85,10 +91,35 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact for EVMImpl<'a, GSPEC, DB, INSPECT> { - fn transact(&mut self) -> EVMResult { - self.env().validate_block_env::()?; - self.env().validate_tx::()?; + fn preverify_transaction(&mut self) -> Result<(), EVMError> { + let env = self.env(); + + env.validate_block_env::()?; + env.validate_tx::()?; + + let tx_caller = env.tx.caller; + let tx_data = &env.tx.data; + let tx_is_create = env.tx.transact_to.is_create(); + + let initial_gas_spend = initial_tx_gas::(tx_data, tx_is_create, &env.tx.access_list); + + // Additonal check to see if limit is big enought to cover initial gas. + if env.tx.gas_limit < initial_gas_spend { + return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into()); + } + + // load acc + let journal = &mut self.data.journaled_state; + let (caller_account, _) = journal + .load_account(tx_caller, self.data.db) + .map_err(EVMError::Database)?; + + self.data.env.validate_tx_against_state(caller_account)?; + Ok(()) + } + + fn transact_preverified(&mut self) -> EVMResult { let env = &self.data.env; let tx_caller = env.tx.caller; let tx_value = env.tx.value; @@ -100,11 +131,6 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact let initial_gas_spend = initial_tx_gas::(&tx_data, tx_is_create, &env.tx.access_list); - // Additional check to see if limit is big enough to cover initial gas. - if env.tx.gas_limit < initial_gas_spend { - return Err(InvalidTransaction::CallGasCostMoreThanGasLimit.into()); - } - // load coinbase // EIP-3651: Warm COINBASE. Starts the `COINBASE` address warm if GSPEC::enabled(SHANGHAI) { @@ -121,8 +147,6 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact .load_account(tx_caller, self.data.db) .map_err(EVMError::Database)?; - self.data.env.validate_tx_against_state(caller_account)?; - // Reduce gas_limit*gas_price amount of caller account. // unwrap_or can only occur if disable_balance_check is enabled caller_account.info.balance = caller_account @@ -221,6 +245,11 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact Ok(ResultAndState { result, state }) } + + fn transact(&mut self) -> EVMResult { + self.preverify_transaction() + .and_then(|_| self.transact_preverified()) + } } impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, INSPECT> { diff --git a/documentation/src/crates/revm/evm.md b/documentation/src/crates/revm/evm.md index c63d6f70e2..b314ead3c1 100644 --- a/documentation/src/crates/revm/evm.md +++ b/documentation/src/crates/revm/evm.md @@ -6,14 +6,12 @@ This document provides the documentation for the `EVM` module. The primary struct in this module is `EVM`. The EVM struct is generic over a type DB. This means that when you use the EVM struct, you can specify the type that DB should represent. It adds flexibility to the struct, allowing it to store different types of databases or data structures in the db field depending on the use case. The `EVM` struct enables `transact` to update the state directly to the database. Additionally, it allows the user to set all environment parameters. - The parameters that can be set are divided between `Config`, `Block`, and `Transaction` (tx). For transacting on the EVM, you can call `transact_commit` that will automatically apply changes to the database. ## Database Abstractions You can implement the traits Database, DatabaseRef or Database + DatabaseCommit depending on the desired handling of the struct. -- `Database`: Has mutable `self` in its functions. It's useful if you want to modify your cache or update some statistics on `get` calls. This trait enables `transact` and `inspect` functions. -- `DatabaseRef`: Takes a reference on the object, this is useful if you only have a reference on the state and don't want to update anything on it. It enables `transact_ref` and `inspect_ref` functions. +- `Database`: Has mutable `self` in its functions. It's useful if you want to modify your cache or update some statistics on `get` calls. This trait enables `preverify_transaction`, `transact_preverified`, `transact` and `inspect` functions. +- `DatabaseRef`: Takes a reference on the object, this is useful if you only have a reference on the state and don't want to update anything on it. It enables `previerify_transaction`, `transact_preverified_ref`, `transact_ref` and `inspect_ref` functions. - `Database + DatabaseCommit`: Allows directly committing changes of a transaction. It enables `transact_commit` and `inspect_commit` functions. -