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://eth.llamarpc.com")
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 withWithFork
, the pre-state overrides the forked state.WithStateDB(db *state.StateDB)
: Specifies the state database for the VM, typically a snapshot fromVM.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 withWithFork
.
Execution
Messages represent transactions or contract calls that can be executed by the VM.
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 (including refunds).MaxGasUsed uint64
: Maximum gas used during executing the message (excluding refunds).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 hadw3types.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.
Storage Slot Calculation
Calculate storage slots for mappings. Solidity and Vyper use different parameter ordering for keccak256 hash calculation.
SoliditySlot(pos, key common.Hash) common.Hash
: Single mapping storage slot.SoliditySlot2(pos, key0, key1 common.Hash) common.Hash
: Double mapping storage slot.SoliditySlot3(pos, key0, key1, key2 common.Hash) common.Hash
: Triple mapping storage slot.VyperSlot(pos, key common.Hash) common.Hash
: Single HashMap storage slot.VyperSlot2(pos, key0, key1 common.Hash) common.Hash
: Double HashMap storage slot.VyperSlot3(pos, key0, key1, key2 common.Hash) common.Hash
: Triple HashMap storage slot.