VM

w3vm.VM is an easy-to-use Ethereum Virtual Machine (EVM), built on top of go-ethereum’s vm.EVM. It supports tracing, state forking via RPC, and can be used for simulation, debugging EVM execution, or testing Smart Contracts.

  • State forking via RPC or custom state fetchers enables transaction simulations or Smart Contract tests on live, or historical chain state.
  • Tracing of EVM execution is supported via go-ethereum/core/tracing.Hooks.

Get Started

Create a VM Instance

Create a VM instance, that forks the latest Mainnet state.

client, err := w3.Dial("https://rpc.ankr.com/eth")
if err != nil {
    // ...
}
defer client.Close()
 
vm, err := w3vm.New(
    w3vm.WithFork(client, nil),
    w3vm.WithNoBaseFee(),
)
if err != nil {
    // ...
}

Simulate a Simple Message

Transfer ETH from the zero address to a random recipient.

recipient := w3vm.RandA()
 
receipt, err := vm.Apply(&w3types.Message{
    From:  common.Address{},
    To:    &recipient,
    Value: w3.I("1 ether"),
})
if err != nil {
    // ...
}

Verify the Recipient’s Balance

Verify the recipient’s balance after the applied message.

balance, err := vm.Balance(recipient)
if err != nil {
    // ...
}
 
fmt.Printf("Balance: %s ETH\n", w3.FromWei(balance, 18))
// Output: Balance: 1 ETH

Setup

A new VM instance is created using the w3vm.New function, which accepts various options to customize the VM behavior:

  • WithChainConfig(cfg *params.ChainConfig): Sets the chain configuration. If not provided, the VM defaults to the Mainnet configuration.
  • WithNoBaseFee(): Forces the EIP-1559 base fee to 0.
  • WithBlockContext(ctx *vm.BlockContext): Sets the block context for the VM.
  • WithPrecompile(addr common.Address, contract vm.PrecompiledContract): Registers a precompile contract at the given address in the VM.
  • WithHeader(header *types.Header): Configures the block context for the VM using the provided header.
  • WithState(state w3types.State): Sets the pre-state of the VM. When used with WithFork, the pre-state overrides the forked state.
  • WithStateDB(db *state.StateDB): Specifies the state database for the VM, typically a snapshot from VM.Snapshot.
  • WithFork(client *w3.Client, blockNumber *big.Int): Forks state from a live Ethereum client at the specified block number.
  • WithFetcher(fetcher Fetcher): Assigns a fetcher to the VM.
  • WithTB(tb testing.TB): Enables persistent state caching when used in conjunction with WithFork.

Execution

Messages represent transactions or contract calls that can be executed by the VM.

All execution methods support tracing via go-ethereum/core/tracing.Hooks. Learn more ➔

Apply Method

Apply applies a w3types.Message to the VM and returns a Receipt. If the execution doesn’t revert, the VM’s underlying state may change.

Example: Apply a Message

msg := &w3types.Message{
    From:  addrSender,
    To:    &addrRecipient,
    Value: w3.I("1 ether"),
    Gas:   21000,
}
 
receipt, err := vm.Apply(msg)
if err != nil {
    // ...
}
fmt.Printf("Gas Used: %d\n", receipt.GasUsed)

ApplyTx Method

ApplyTx is like Apply, but takes a types.Transaction instead of a message. The given transaction is converted to a message internally, using a signer, that is derived from the VM’s chain configuration and fork block.

Call Method

Call is like Apply, but any state changes during execution are reverted in the end, so the VM’s state is never modified.

Example: Call balanceOf

funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")
 
msg := &w3types.Message{
    To:    &addrToken,
    Func:  funcBalanceOf,
    Args:  []any{addrOwner},
}
 
receipt, err := vm.Call(msg)
if err != nil {
    // handle error
}
 
var balance *big.Int
if err := receipt.DecodeReturns(&balance); err != nil {
    // handle error
}
fmt.Printf("Balance: %s\n", balance)

CallFunc Method

CallFunc is a helper, that greatly simplifies common usage of Call. It is designed analogues to the eth.CallFunc RPC client method.

Example: Call balanceOf with CallFunc

This is a simplified version of the Call balanceOf example.

funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")
 
var balance *big.Int
err := vm.CallFunc(addrToken, funcBalanceOf, addrOwner).Returns(&balance)
if err != nil {
    // handle error
}
fmt.Printf("Balance: %s\n", balance)

Receipt Type

The Receipt struct contains the result of an executed message.

Fields

  • GasUsed uint64: Gas used for executing the message.
  • GasRefund uint64: Gas refunded after executing the message.
  • Logs []*types.Log: Logs emitted while executing the message.
  • Output []byte: Output of the executed message.
  • ContractAddress *common.Address: Address of the created contract, if any.
  • Err error: Execution error, if any.

Methods

  • DecodeReturns(returns ...any) error: Decodes the return values. This method only works, if the executed message had w3types.Message.Func set.

State

The VM provides methods to read, and write account state.

Reading State

  • vm.Balance(addr common.Address) (*big.Int, error): Returns the balance of the given address.
  • vm.Nonce(addr common.Address) (uint64, error): Returns the nonce of the given address.
  • vm.Code(addr common.Address) ([]byte, error): Returns the code of the given address.
  • vm.StorageAt(addr common.Address, slot common.Hash) (common.Hash, error): Returns the state of the given address at the given storage slot.

An error only can only occur, if the VM fails to fetch state via a w3vm.Fetcher. Thus, it is safe to ignore the error, if no state fetcher is used by the VM.

Writing State

  • vm.SetBalance(addr common.Address, balance *big.Int): Sets the balance of the given address.
  • vm.SetNonce(addr common.Address, nonce uint64): Sets the nonce of the given address.
  • vm.SetCode(addr common.Address, code []byte): Sets the code of the given address.
  • vm.SetStorageAt(addr common.Address, slot common.Hash, value common.Hash): Sets the state of the given address at the give storage slot.

Helper

  • w3vm.RandA() common.Address: Returns a random address.
  • WETHBalanceSlot(addr common.Address) common.Hash: Returns the storage slot that stores the WETH balance of the given address.
  • WETHAllowanceSlot(owner, spender common.Address) common.Hash: Returns the storage slot that stores the WETH allowance of the given owner to the spender.
  • Slot(pos, key common.Hash) common.Hash: Returns the storage slot of a mapping with the given position and key.
  • Slot2(pos, key, key2 common.Hash) common.Hash: Returns the storage slot of a double mapping with the given position and keys.