Expand description
Trevm
- a typestate interface to revm
.
Trevm provides a safe and low-overhead way to interact with the EVM. It is based on the typestate pattern, which allows the compiler to enforce correct usage of the EVM.
Trevm is NOT an EVM implementation. It is a thin wrapper around the EVM
provided by revm
.
Trevm
models the EVM as a state machine with the following states:
EvmNeedsCfg
: The EVM needs to be configured.EvmNeedsBlock
: The EVM is configured and needs a block environment.EvmNeedsTx
: The EVM is configured and has a block environment, and now needs a transaction to execute.EvmReady
: The EVM has a transaction loaded and is ready to execute it.EvmErrored
: The EVM has executed a transaction and encountered an error.EvmTransacted
: The EVM has executed a transaction successfully.
§Quickstart
To get started, use TrevmBuilder
to configure and construct the EVM.
From there, you should do the following:
- Fill a Cfg by calling
Trevm::fill_cfg
with aCfg
. - Open a block by calling
Trevm::fill_block
with aBlock
. - Fill a Tx by calling
Trevm::fill_tx
with aTx
. - Run the transaction by calling
Trevm::run_tx
. - Handle the result by calling
EvmTransacted::accept
orEvmTransacted::reject
. - Call
Trevm::close_block
to close the block. - Then call
Trevm::finish
to get the outputs.
§Running a transaction
Running transactions is simple with Trevm. Here’s a basic example:
use revm::{database::in_memory_db::InMemoryDB};
use trevm::{TrevmBuilder, EvmErrored, Cfg, Block, Tx};
TrevmBuilder::new()
.with_db(InMemoryDB::default())
.build_trevm()?
.fill_cfg(cfg)
.fill_block(block)
.run_tx(tx);
If you get stuck, don’t worry! You cannot invoke the wrong function or
mess up the inner state unless you access a method marked _unchecked
.
While the states and generics may seem intimidating at first, they fade
into the background when you start writing your application.
§Writing an application
When writing your code, we strongly recommend using the Evm____
type
aliases to simplify your code. These types come with 2 generics: Db
and
Insp
. Db
is the database type, and Insp
is the inspector type. Most
users will want to use ()
for Insp
, unless specifically using an
inspector or a customized EVM.
We also recommend defining concrete types for Insp
and Db
whenever
possible, to simplify your code and remove bounds. Most users will want
()
for Insp
, unless specifically using an inspector or a customized EVM.
To help you use concrete types, we provide the trevm_aliases
macro. This
macro generates type aliases for the Trevm states with concrete Insp
and
Db
types.
use trevm::trevm_aliases;
use revm::database::in_memory_db::InMemoryDB;
// produces types that look like this:
// type EvmNeedsCfg = trevm::EvmNeedsCfg<InMemoryDB>, ();
trevm_aliases!(revm::database::in_memory_db::InMemoryDB);
§BlockDriver
and ChainDriver
Trevm handles transaction application, receipts, and pre- and post-block
logic through the BlockDriver
and ChainDriver
traits. These
traits invoked by Trevm
via EvmNeedsBlock::drive_block
and
EvmNeedsCfg::drive_chain
, respectively.
To aid in the creation of drivers, Trevm offers helper functions for common eips:
eip2935
- Prague’s EIP-2935, which updates historical block hashes in a special system contract.eip4788
- Cancun’s EIP-4788, which updates historical beacon roots in a special system contract.eip4895
- Cancun’s EIP-4895, which processes withdrawal requests by crediting accounts.eip6110
- Prague’s EIP-6110, which extracts deposit requests from the withdrawal contract events.eip7002
- Prague’s EIP-7002, which extracts withdrawal requests from the system withdrawal contract state.eip7251
- Prague’s EIP-7251, which extracts consolidation requests from the system consolidation contract state.
The BlockDriver
and ChainDriver
trait methods take a mutable
reference to allow the driver to accumulate information about the
execution. This is useful for accumulating receipts, senders, or other
information that is more readily available during execution. It is also
useful for pre-block logic, where the lifecycle may need to accumulate
information about the block before the first transaction is executed, and
re-use that information to close the block. It may also be used to compute
statistics or indices that are only available after the block is closed.
let trevm = TrevmBuilder::new()
.with_db(InMemoryDB::default())
.build_trevm()?
.fill_cfg(cfg)
.drive_block(&mut driver);
BlockDriver
and ChainDriver
implementations are generic over the
Insp
type. This means that you can use customized revm inspectors in your
driver logic.
§Handling execution errors
Trevm uses the EvmErrored
state to handle errors during transaction
execution. This type is a wrapper around the error that occurred, and
provides a method to discard the error and continue execution. This is
useful when you want to continue executing transactions even if one fails.
Usually, errors will be EVMError<Db>
, but BlockDriver
and
ChainDriver
implementations may return other errors. The EvmErrored
type is generic over the error type, so you can use it with any error type.
let trevm = match TrevmBuilder::new()
.with_db(InMemoryDB::default())
.build_trevm()?
.fill_cfg(cfg)
.fill_block(block)
.fill_tx(tx)
.run() {
Ok(trevm) => trevm.accept_state(),
Err(e) => e.discard_error(),
};
§Extending Trevm
Trevm has a few extension points:
- Build trevm with a
revm::Inspector
and use it in yourBlockDriver
implementation. - Implement the
PostTx
trait to apply post-transaction logic/changes. - Implement your own
Cfg
,Block
, andTx
to fill the EVM from your own data structures. - Implement your own
BlockDriver
to apply pre- and post-block logic, use custom receipt types, or more.
// You can implement your own Tx to fill the EVM environment with your own
// data.
pub struct MyTx;
impl Tx for MyTx {
fn fill_tx_env(&self, tx_env: &mut revm::context::TxEnv) {
// fill the tx_env with your data
// we recommend destructuring here to safeguard against future changes
// to the TxEnv struct
let revm::context::TxEnv {
caller,
..
} = tx_env;
*caller = Address::repeat_byte(0xde);
}
}
§Trevm feature flags
Trevm passes through most feature flags from revm, the following are on by default:
c-kzg
- Enable KZG precompiles as specced for EIP-4844.blst
- Enable BLST precompiles as speced for EIP-2537.portable
- Compiles BLST in portable mode.secp256k1
- Use libsecp256k1 for ecrecover (default is k256).
Cfg features:
-
memory_limit
- Allow users to limit callframe memory usage. -
optional_balance_check
- Allow transacting with insufficient balance. -
optional_block_gas_limit
- Allow transactions that exceed the block gas limit. -
optional_eip3607
- Allow transactions whose sender account has contract code. -
optional_gas_refund
- Allow disabling gas refunds, as in Avalanche. -
optional_no_base_fee
- Allow disabling basefee checks. -
optional_beneficiary_reward
- Allow disabling fees and rewards paid to the block beneficiary. -
dev
- Enable all Cfg features.
Trevm also provides the following:
test-utils
- activates revm’stest-utils
feature, and provides convenience functions for instantiatingTrevm
with an in-memory DB.
§Testing using Trevm
Trevm provides a test-utils
module for easy testing. The test-utils gives
you access to an in-memory clean-slate Trevm
instance, as well as tools
for directly modifying the state of the EVM.
During testing you can use
- set balance, nonce, codehash for any account
- a single-function setup for a blank EVM
- pre-funding for any number of accounts
§Understanding the state machine
Here’s an example, with states written out:
let state = StateBuilder::new_with_database(InMemoryDB::default()).build();
// Trevm starts in `EvmNeedsCfg`.
let trevm: EvmNeedsCfg<_, _> = TrevmBuilder::new()
.with_db(state)
.build_trevm()?;
// Once the cfg is filled, we move to `EvmNeedsBlock`.
let trevm: EvmNeedsBlock<_, _> = trevm.fill_cfg(cfg);
// Filling the block gets us to `EvmNeedsTx`.
let trevm: EvmNeedsTx<_, _> = trevm.fill_block(
block,
);
// Filling the tx gets us to `EvmReady`.
let trevm: EvmReady<_, _> = trevm.fill_tx(tx);
let res: Result<
EvmTransacted<_, _>,
EvmErrored<_, _, _>,
> = trevm.run();
// Applying the tx or ignoring the error gets us back to `EvmNeedsTx`.
// You could also make additional checks and discard the success result here
let trevm: EvmNeedsTx<_, _> = match res {
Ok(trevm) => trevm.accept_state(),
Err(e) => e.discard_error(),
};
// Clearing or closing a block gets us to `EvmNeedsBlock`, ready for the
// next block.
let trevm: EvmNeedsBlock<_, _> = trevm
.close_block();
// Finishing the EVM gets us the final changes and a list of block outputs
// that includes the transaction receipts.
let bundle: BundleState = trevm.finish();
§Happy Path Loop
The most simple, straightforward application of Trevm is applying a set of transaction to the EVM. This is done by following :
+-----+ +-----------+
|Start| ---> |EvmNeedsCfg|
+-----+ +-----------+
|
+-- fill_cfg() --+
|
V
+------+ +-------------+
|Finish|<- finish() -|EvmNeedsBlock|---+
+------+ +-------------+ |
^ |
| |
+-----------+ |
| |
close_block() |
| |
+----------+ |
|EvmNeedsTx| <---- fill_block() -----+
+----------+
^ | +--------+
| +------- fill_tx() -------> |EvmReady|--+
| +--------+ |
| +-------------+ |
+- accept() --|EvmTransacted| <-- run_tx() -----+
or reject() +-------------+
A basic block loop should look like this:
let mut evm = evm
.fill_cfg(cfg);
.fill_block(block, pre_block_logic);
for tx in txs {
// apply the transaction, discard the error if any
evm = match evm.run_tx(tx);
}
// close out the EVM, getting the final state
let (bundle, outputs) = evm.close_block(block, post_block_logic).finish();
Re-exports§
pub use revm;
Modules§
- db
- Contains database implementations and related
- fillers
- Provided fillers that adjust the
Cfg
,Block
orTx
environment. - helpers
- Type aliases for constraining revm context.
- inspectors
- Utility inspectors.
- journal
- Utilities for serializing revm’s
BundleState
into a canonical format. - system
- Helpers for system actions including EIP-4788, EIP-6110, EIP-7002 and EIP-7251.
Macros§
- discard_
if - Discard the transaction if the condition is true, providing a discard reason.
- trevm_
aliases - Declare type aliases for trevm with a concrete
Ext
andDb
type. - trevm_
bail - Returns an errored
Trevm
with the provided error. - trevm_
ensure - Executes a condition, returning an errored
Trevm
if not successful. - trevm_
try - Unwraps a Result, returning the value if successful, or returning an errored
Trevm
if not. - unwrap_
or_ trevm_ err Deprecated - Unwraps a Result, returning the value if successful, or returning an errored
Trevm
if not.
Structs§
- Block
Output - Information externalized during block execution.
- Bundle
Processor - A bundle processor which can be used to drive a bundle with a BundleDriver, accumulate the results of the bundle and dispatch a response.
- Noop
Block - A no-op block filler.
- NoopCfg
- A no-op configuration filler.
- Trevm
- Trevm provides a type-safe interface to the EVM, using the typestate pattern.
- Trevm
Builder - A builder for
Trevm
that allows configuring the EVM.
Enums§
- Bundle
Error - Possible errors that can occur while driving a bundle.
- Estimation
Result estimate_gas
- The result of gas estimation.
- Postflight
Result - Control flow for transaction execution.
- Trevm
Builder Error - Error that can occur when building a Trevm instance.
Constants§
- MIN_
TRANSACTION_ GAS - The minimum gas required for a transaction.
Traits§
- Block
- Types that can fill the EVM block environment
BlockEnv
. - Block
Driver - Driver for a single trevm block. This trait allows a type to specify the entire lifecycle of a trevm block, from opening the block to driving the trevm to completion.
- Bundle
Driver - Driver for a bundle of transactions. This trait allows a type to specify the entire lifecycle of a bundle, simulating the entire list of transactions.
- Cfg
- Types that can fill the EVM configuration environment
CfgEnv
. - Chain
Driver - Driver for a chain of blocks.
- DbConnect
- Trait for types that can be used to connect to a database.
- EvmExt
Unchecked - Extension trait for
revm::context::Evm
with convenience functions for reading and modifying state. - EvmFactory
- Trait for types that can create EVM instances.
- PostTx
- Inspect the outcome of a transaction execution, and determine whether to apply or discard the state changes.
- Tx
- Types that can fill the EVM transaction environment
TxEnv
.
Functions§
- ethereum_
receipt - Create an Ethereum
ReceiptEnvelope
from an execution result.
Type Aliases§
- Drive
Block Result - The result of driving a block to completion.
- Drive
Bundle Result - The result of driving a bundle to completion.
- Drive
Chain Result - The result of driving a chain to completion.
- EvmBlock
Driver Errored - A
Trevm
that encountered an error duringBlockDriver
execution. - EvmBundle
Driver Errored - A
Trevm
that encountered an error duringBundleDriver
execution. - EvmChain
Driver Errored - A
Trevm
that encountered an error duringChainDriver
execution. - EvmErrored
- A
Trevm
that encountered an error during transaction execution. - EvmNeeds
Block - A
Trevm
that requires aBlock
and contains no outputs. This EVM has not yet executed any transactions or state changes. - EvmNeeds
Cfg - A
Trevm
that requires aCfg
. - EvmNeeds
Tx - A
Trevm
that requires aTx
. - EvmReady
- A
Trevm
that is ready to execute a transaction. - EvmTransacted
- A
Trevm
that run a transaction, and contains the resulting execution details and state. - RunTx
Result - The result of running transactions for a block driver.