Crate trevm

Source
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:

§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 your BlockDriver implementation.
  • Implement the PostTx trait to apply post-transaction logic/changes.
  • Implement your own Cfg, Block, and Tx 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’s test-utils feature, and provides convenience functions for instantiating Trevm 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 or Tx 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 and Db 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_errDeprecated
Unwraps a Result, returning the value if successful, or returning an errored Trevm if not.

Structs§

BlockOutput
Information externalized during block execution.
BundleProcessor
A bundle processor which can be used to drive a bundle with a BundleDriver, accumulate the results of the bundle and dispatch a response.
NoopBlock
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.
TrevmBuilder
A builder for Trevm that allows configuring the EVM.

Enums§

BundleError
Possible errors that can occur while driving a bundle.
EstimationResultestimate_gas
The result of gas estimation.
PostflightResult
Control flow for transaction execution.
TrevmBuilderError
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.
BlockDriver
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.
BundleDriver
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.
ChainDriver
Driver for a chain of blocks.
DbConnect
Trait for types that can be used to connect to a database.
EvmExtUnchecked
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§

DriveBlockResult
The result of driving a block to completion.
DriveBundleResult
The result of driving a bundle to completion.
DriveChainResult
The result of driving a chain to completion.
EvmBlockDriverErrored
A Trevm that encountered an error during BlockDriver execution.
EvmBundleDriverErrored
A Trevm that encountered an error during BundleDriver execution.
EvmChainDriverErrored
A Trevm that encountered an error during ChainDriver execution.
EvmErrored
A Trevm that encountered an error during transaction execution.
EvmNeedsBlock
A Trevm that requires a Block and contains no outputs. This EVM has not yet executed any transactions or state changes.
EvmNeedsCfg
A Trevm that requires a Cfg.
EvmNeedsTx
A Trevm that requires a Tx.
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.
RunTxResult
The result of running transactions for a block driver.