VM
w3vm.VM is a super simple to use Ethereum Virtual Machine (EVM), built on top of go-ethereum/core/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 enable transaction simulations or Smart Contract tests on live, or historic 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 ETHSetup
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 set, the VM falls back to the Mainnet configuration.WithNoBaseFee(): Forces the EIP-1559 base fee to 0.WithBlockContext(ctx *vm.BlockContext): Sets the block context for the VM.WithHeader(header *types.Header): Sets the block context for the VM based on the given header.WithState(state w3types.State): Sets the pre state of the VM. If used withWithFork, the pre state overrides the forked state.WithStateDB(db *state.StateDB): Sets the state database for the VM, that is usually a snapshot obtained fromVM.Snapshot.WithFork(client *w3.Client, blockNumber *big.Int): Forks state from a live Ethereum client at a specific block number.WithFetcher(fetcher Fetcher): Sets the fetcher for the VM.WithTB(tb testing.TB): Enables persistent state caching when used together 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.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 hadw3types.Message.Funcset.
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.