w3vm

package
v0.17.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 6, 2025 License: MIT Imports: 39 Imported by: 2

Documentation

Overview

Package w3vm provides a VM for executing EVM messages.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrFetch  = errors.New("fetching failed")
	ErrRevert = errors.New("execution reverted")
)
View Source
var ErrMissingFunc = errors.New("missing function")

Functions

func RandA

func RandA() (addr common.Address)

RandA returns a random address.

func Slot

func Slot(pos, key common.Hash) common.Hash

Slot returns the storage slot of a mapping with the given position and key.

Slot follows the Solidity storage layout for:

mapping(bytes32 => bytes32)

func Slot2

func Slot2(pos, key0, key1 common.Hash) common.Hash

Slot2 returns the storage slot of a double mapping with the given position and keys.

Slot2 follows the Solidity storage layout for:

mapping(bytes32 => mapping(bytes32 => bytes32))

func Slot3 added in v0.17.6

func Slot3(pos, key0, key1, key2 common.Hash) common.Hash

Slot3 returns the storage slot of a triple mapping with the given position and keys.

Slot3 follows the Solidity storage layout for:

mapping(bytes32 => mapping(bytes32 => mapping(bytes32 => bytes32)))

func WETHAllowanceSlot

func WETHAllowanceSlot(owner, spender common.Address) common.Hash

WETHAllowanceSlot returns the storage slot that stores the WETH allowance of the given owner to the spender.

func WETHBalanceSlot

func WETHBalanceSlot(addr common.Address) common.Hash

WETHBalanceSlot returns the storage slot that stores the WETH balance of the given addr.

Example
package main

import (
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/w3types"
	"github.com/lmittmann/w3/w3vm"
)

func main() {
	client := w3.MustDial("https://rpc.ankr.com/eth")
	defer client.Close()

	addrC0fe := w3.A("0x000000000000000000000000000000000000c0Fe")
	addrWETH := w3.A("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
	funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")

	vm, err := w3vm.New(
		w3vm.WithFork(client, nil),
		w3vm.WithState(w3types.State{
			addrWETH: {
				Storage: w3types.Storage{
					w3vm.WETHBalanceSlot(addrC0fe): common.BigToHash(w3.I("100 ether")),
				},
			},
		}),
	)
	if err != nil {
		// ...
	}

	var balance *big.Int
	err = vm.CallFunc(addrWETH, funcBalanceOf, addrC0fe).Returns(&balance)
	if err != nil {
		// ...
	}
	fmt.Printf("%s: %s WETH", addrC0fe, w3.FromWei(balance, 18))
}
Output:

0x000000000000000000000000000000000000c0Fe: 100 WETH

Types

type CallFuncFactory

type CallFuncFactory struct {
	// contains filtered or unexported fields
}

func (*CallFuncFactory) Returns

func (cff *CallFuncFactory) Returns(returns ...any) error

type Fetcher

type Fetcher interface {
	// Account fetches the account of the given address.
	Account(common.Address) (*types.StateAccount, error)

	// Code fetches the code of the given code hash.
	Code(common.Hash) ([]byte, error)

	// StorageAt fetches the state of the given address and storage slot.
	StorageAt(common.Address, common.Hash) (common.Hash, error)

	// HeaderHash fetches the hash of the header with the given number.
	HeaderHash(uint64) (common.Hash, error)
}

Fetcher is the interface to access account state of a blockchain.

func NewRPCFetcher

func NewRPCFetcher(client *w3.Client, blockNumber *big.Int) Fetcher

NewRPCFetcher returns a new Fetcher that fetches account state from the given RPC client for the given block number.

Note, that the returned state for a given block number is the state after the execution of that block.

func NewTestingRPCFetcher

func NewTestingRPCFetcher(tb testing.TB, chainID uint64, client *w3.Client, blockNumber *big.Int) Fetcher

NewTestingRPCFetcher returns a new Fetcher like NewRPCFetcher, but caches the fetched state on disk in the testdata directory of the tests package.

type Option

type Option func(*VM)

An Option configures a VM.

func WithBlockContext

func WithBlockContext(ctx *vm.BlockContext) Option

WithBlockContext sets the block context for the VM.

func WithChainConfig

func WithChainConfig(cfg *params.ChainConfig) Option

WithChainConfig sets the chain config for the VM.

If not provided, the chain config defaults to params.MainnetChainConfig.

func WithFetcher

func WithFetcher(fetcher Fetcher) Option

WithFetcher sets the fetcher for the VM.

func WithFork

func WithFork(client *w3.Client, blockNumber *big.Int) Option

WithFork sets the client and block number to fetch state from and sets the block context for the VM. If the block number is nil, the latest state is fetched and the pending block is used for constructing the block context.

If used together with WithTB, fetched state is stored in the testdata directory of the tests package.

func WithHeader

func WithHeader(header *types.Header) Option

WithHeader sets the block context for the VM based on the given header.

func WithNoBaseFee

func WithNoBaseFee() Option

WithNoBaseFee forces the EIP-1559 base fee to 0 for the VM.

func WithPrecompile added in v0.17.2

func WithPrecompile(addr common.Address, contract vm.PrecompiledContract) Option

WithPrecompile registers a precompile contract at the given address in the VM.

func WithState

func WithState(state w3types.State) Option

WithState sets the pre state of the VM.

WithState can be used together with WithFork to only set the state of some accounts, or partially overwrite the storage of an account.

func WithStateDB added in v0.16.8

func WithStateDB(db *state.StateDB) Option

WithStateDB sets the state DB for the VM, that is usually a snapshot obtained from VM.Snapshot.

func WithTB

func WithTB(tb testing.TB) Option

WithTB enables persistent state caching when used together with WithFork. State is stored in the testdata directory of the tests package.

type Receipt

type Receipt struct {
	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
	// contains filtered or unexported fields
}

Receipt represents the result of an applied w3types.Message.

func (Receipt) DecodeReturns

func (r Receipt) DecodeReturns(returns ...any) error

DecodeReturns is like w3types.Func.DecodeReturns, but returns ErrMissingFunc if the underlying w3types.Message.Func is nil.

type VM

type VM struct {
	// contains filtered or unexported fields
}
Example (Call)

Execute an ERC20 balanceOf call with raw a w3types.Message using the messages Func and Args helper.

vm, err := w3vm.New(
	w3vm.WithFork(client, nil),
	w3vm.WithState(w3types.State{
		addrWETH: {Storage: w3types.Storage{
			w3vm.WETHBalanceSlot(addrA): common.BigToHash(w3.I("100 ether")),
		}},
	}),
)
if err != nil {
	// ...
}

receipt, err := vm.Call(&w3types.Message{
	To:   &addrWETH,
	Func: funcBalanceOf,
	Args: []any{addrA},
})
if err != nil {
	// ...
}

var balance *big.Int
if err := receipt.DecodeReturns(&balance); err != nil {
	// ...
}
fmt.Printf("Balance: %s WETH\n", w3.FromWei(balance, 18))
Output:

Balance: 100 WETH
Example (CallFunc)

Execute an ERC20 balanceOf call using the VM.CallFunc helper.

vm, err := w3vm.New(
	w3vm.WithFork(client, nil),
	w3vm.WithState(w3types.State{
		addrWETH: {Storage: w3types.Storage{
			w3vm.WETHBalanceSlot(addrA): common.BigToHash(w3.I("100 ether")),
		}},
	}),
)
if err != nil {
	// ...
}

var balance *big.Int
if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&balance); err != nil {
	// ...
}
fmt.Printf("Balance: %s WETH\n", w3.FromWei(balance, 18))
Output:

Balance: 100 WETH
Example (FakeTokenBalance)

Execute an ERC20 token transfer with faked token balance (Wrapped Ether).

vm, err := w3vm.New(
	w3vm.WithFork(client, nil),
	w3vm.WithNoBaseFee(),
	w3vm.WithState(w3types.State{
		addrWETH: {Storage: w3types.Storage{
			w3vm.WETHBalanceSlot(addrA): common.BigToHash(w3.I("100 ether")),
		}},
	}),
)
if err != nil {
	// ...
}

// Print WETH balance
var balA, balB *big.Int
if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&balA); err != nil {
	// ...
}
if err := vm.CallFunc(addrWETH, funcBalanceOf, addrB).Returns(&balB); err != nil {
	// ...
}
fmt.Printf("Before transfer:\nA: %s WETH, B: %s WETH\n", w3.FromWei(balA, 18), w3.FromWei(balB, 18))

// Transfer 10 WETH from A to B
if _, err := vm.Apply(&w3types.Message{
	From: addrA,
	To:   &addrWETH,
	Func: funcTransfer,
	Args: []any{addrB, w3.I("10 ether")},
}); err != nil {
	// ...
}

// Print WETH balance
if err := vm.CallFunc(addrWETH, funcBalanceOf, addrA).Returns(&balA); err != nil {
	// ...
}
if err := vm.CallFunc(addrWETH, funcBalanceOf, addrB).Returns(&balB); err != nil {
	// ...
}
fmt.Printf("After transfer:\nA: %s WETH, B: %s WETH\n", w3.FromWei(balA, 18), w3.FromWei(balB, 18))
Output:

Before transfer:
A: 100 WETH, B: 0 WETH
After transfer:
A: 90 WETH, B: 10 WETH
Example (PrankZeroAddress)

Execute a message sent from the zero address. The w3types.Message sender can be freely chosen, making it possible to execute a message from any address.

vm, err := w3vm.New(
	w3vm.WithFork(client, big.NewInt(20_000_000)),
	w3vm.WithNoBaseFee(),
)
if err != nil {
	// ...
}

balZero, err := vm.Balance(w3.Addr0)
if err != nil {
	// ...
}

_, err = vm.Apply(&w3types.Message{
	From:  w3.Addr0,
	To:    &addrA,
	Value: balZero,
})
if err != nil {
	// ...
}

balance, err := vm.Balance(addrA)
if err != nil {
	// ...
}

fmt.Printf("Received %s ETH from zero address\n", w3.FromWei(balance, 18))
Output:

Received 13365.401185473565028721 ETH from zero address
Example (SimpleTransfer)

Execute an Ether transfer.

package main

import (
	"fmt"

	"github.com/ethereum/go-ethereum/common"
	"github.com/lmittmann/w3"
	"github.com/lmittmann/w3/w3types"
	"github.com/lmittmann/w3/w3vm"
)

var (
	addrA = common.Address{0x0a}
	addrB = common.Address{0x0b}
)

func main() {
	vm, _ := w3vm.New(
		w3vm.WithState(w3types.State{
			addrA: {Balance: w3.I("100 ether")},
		}),
	)

	// Print balances
	balA, _ := vm.Balance(addrA)
	balB, _ := vm.Balance(addrB)
	fmt.Printf("Before transfer:\nA: %s ETH, B: %s ETH\n", w3.FromWei(balA, 18), w3.FromWei(balB, 18))

	// Transfer 10 ETH from A to B
	vm.Apply(&w3types.Message{
		From:  addrA,
		To:    &addrB,
		Value: w3.I("10 ether"),
	})

	// Print balances
	balA, _ = vm.Balance(addrA)
	balB, _ = vm.Balance(addrB)
	fmt.Printf("After transfer:\nA: %s ETH, B: %s ETH\n", w3.FromWei(balA, 18), w3.FromWei(balB, 18))
}
Output:

Before transfer:
A: 100 ETH, B: 0 ETH
After transfer:
A: 90 ETH, B: 10 ETH
Example (TraceAccessList)

Trace a message execution to obtain the access list.

txHash := w3.H("0xbb4b3fc2b746877dce70862850602f1d19bd890ab4db47e6b7ee1da1fe578a0d")

var (
	tx      *types.Transaction
	receipt *types.Receipt
)
if err := client.Call(
	eth.Tx(txHash).Returns(&tx),
	eth.TxReceipt(txHash).Returns(&receipt),
); err != nil {
	// ...
}

var header *types.Header
if err := client.Call(eth.HeaderByNumber(receipt.BlockNumber).Returns(&header)); err != nil {
	// ...
}

vm, err := w3vm.New(
	w3vm.WithFork(client, receipt.BlockNumber),
)
if err != nil {
	// ...
}

// setup access list tracer
signer := types.MakeSigner(params.MainnetChainConfig, header.Number, header.Time)
from, _ := signer.Sender(tx)
accessListTracer := logger.NewAccessListTracer(
	nil,
	from, *tx.To(),
	gethVm.ActivePrecompiles(params.MainnetChainConfig.Rules(header.Number, header.Difficulty.Sign() == 0, header.Time)),
)

if _, err := vm.ApplyTx(tx, accessListTracer.Hooks()); err != nil {
	// ...
}
fmt.Println("Access List:", accessListTracer.AccessList())
Output:

Example (TraceBlock)

Trace the execution of all op's in a block.

blockNumber := big.NewInt(20_000_000)

var block *types.Block
if err := client.Call(eth.BlockByNumber(blockNumber).Returns(&block)); err != nil {
	// ...
}

vm, err := w3vm.New(
	w3vm.WithFork(client, blockNumber),
)
if err != nil {
	// ...
}

var opCount [256]uint64
tracer := &tracing.Hooks{
	OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) {
		opCount[op]++
	},
}

for _, tx := range block.Transactions() {
	vm.ApplyTx(tx, tracer)
}

for op, count := range opCount {
	if count > 0 {
		fmt.Printf("0x%02x %-14s %d\n", op, gethVm.OpCode(op), count)
	}
}
Output:

Example (TraceCalls)

Trace calls (and opcodes) of a transaction.

txHash := w3.H("0xc0679fedfe8d7c376d599cbab03de7b527347a3d135d7d8d698047f34a6611f8")

var (
	tx      *types.Transaction
	receipt *types.Receipt
)
if err := client.Call(
	eth.Tx(txHash).Returns(&tx),
	eth.TxReceipt(txHash).Returns(&receipt),
); err != nil {
	// ...
}

vm, err := w3vm.New(
	w3vm.WithFork(client, receipt.BlockNumber),
)
if err != nil {
	// ...
}

callTracer := hooks.NewCallTracer(os.Stdout, &hooks.CallTracerOptions{
	ShowStaticcall: true,
	DecodeABI:      true,
})
vm.ApplyTx(tx, callTracer)
Output:

Example (UniswapV3Swap)

Execute an Uniswap V3 swap.

var (
	addrRouter = w3.A("0xE592427A0AEce92De3Edee1F18E0157C05861564")
	addrUNI    = w3.A("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984")

	funcExactInput = w3.MustNewFunc(`exactInput(
			(
				bytes path,
				address recipient,
				uint256 deadline,
				uint256 amountIn,
				uint256 amountOutMinimum
			) params
		)`, "uint256 amountOut")
)

// mapping for the exactInput-function params-tuple
type ExactInputParams struct {
	Path             []byte
	Recipient        common.Address
	Deadline         *big.Int
	AmountIn         *big.Int
	AmountOutMinimum *big.Int
}

encodePath := func(tokenA, tokenB common.Address, fee uint32) []byte {
	path := make([]byte, 43)
	copy(path, tokenA[:])
	path[20], path[21], path[22] = byte(fee>>16), byte(fee>>8), byte(fee)
	copy(path[23:], tokenB[:])
	return path
}

// 1. Create a VM that forks the Mainnet state from the latest block,
// disables the base fee, and has a fake WETH balance and approval for the router
vm, err := w3vm.New(
	w3vm.WithFork(client, big.NewInt(20_000_000)),
	w3vm.WithNoBaseFee(),
	w3vm.WithState(w3types.State{
		addrWETH: {Storage: w3types.Storage{
			w3vm.WETHBalanceSlot(addrA):               common.BigToHash(w3.I("1 ether")),
			w3vm.WETHAllowanceSlot(addrA, addrRouter): common.BigToHash(w3.I("1 ether")),
		}},
	}),
)
if err != nil {
	// ...
}

// 2. Simulate a Uniswap v3 swap
receipt, err := vm.Apply(&w3types.Message{
	From: addrA,
	To:   &addrRouter,
	Func: funcExactInput,
	Args: []any{&ExactInputParams{
		Path:             encodePath(addrWETH, addrUNI, 500),
		Recipient:        addrA,
		Deadline:         big.NewInt(time.Now().Unix()),
		AmountIn:         w3.I("1 ether"),
		AmountOutMinimum: w3.Big0,
	}},
})
if err != nil {
	// ...
}

// 3. Decode output amount
var amountOut *big.Int
if err := receipt.DecodeReturns(&amountOut); err != nil {
	// ...
}

fmt.Printf("AmountOut: %s UNI\n", w3.FromWei(amountOut, 18))
Output:

AmountOut: 278.327327986946583271 UNI

func New

func New(opts ...Option) (*VM, error)

New creates a new VM, that is configured with the given options.

func (*VM) Apply

func (vm *VM) Apply(msg *w3types.Message, hooks ...*tracing.Hooks) (*Receipt, error)

Apply the given message to the VM, and return its receipt. Multiple tracing hooks may be given to trace the execution of the message.

func (*VM) ApplyTx added in v0.14.7

func (vm *VM) ApplyTx(tx *types.Transaction, hooks ...*tracing.Hooks) (*Receipt, error)

ApplyTx is like VM.Apply, but takes a transaction instead of a message.

func (*VM) Balance

func (vm *VM) Balance(addr common.Address) (*big.Int, error)

Balance returns the balance of the given address.

func (*VM) Call

func (vm *VM) Call(msg *w3types.Message, hooks ...*tracing.Hooks) (*Receipt, error)

Call the given message on the VM, and returns its receipt. Any state changes of a call are reverted. Multiple tracing hooks may be given to trace the execution of the message.

func (*VM) CallFunc

func (vm *VM) CallFunc(contract common.Address, f w3types.Func, args ...any) *CallFuncFactory

CallFunc is a utility function for VM.Call that calls the given function on the given contract address with the given arguments and decodes the output into the given returns.

Example:

funcBalanceOf := w3.MustNewFunc("balanceOf(address)", "uint256")

var balance *big.Int
err := vm.CallFunc(contractAddr, funcBalanceOf, addr).Returns(&balance)
if err != nil {
	// ...
}

func (*VM) Code

func (vm *VM) Code(addr common.Address) ([]byte, error)

Code returns the code of the given address.

func (*VM) Nonce

func (vm *VM) Nonce(addr common.Address) (uint64, error)

Nonce returns the nonce of the given address.

func (*VM) Rollback added in v0.16.2

func (vm *VM) Rollback(snapshot *state.StateDB)

Rollback the state of the VM to the given snapshot.

func (*VM) SetBalance added in v0.16.8

func (vm *VM) SetBalance(addr common.Address, balance *big.Int)

SetBalance sets the balance of the given address.

func (*VM) SetCode added in v0.16.8

func (vm *VM) SetCode(addr common.Address, code []byte)

SetCode sets the code of the given address.

func (*VM) SetNonce added in v0.16.8

func (vm *VM) SetNonce(addr common.Address, nonce uint64)

SetNonce sets the nonce of the given address.

func (*VM) SetStorageAt added in v0.16.8

func (vm *VM) SetStorageAt(addr common.Address, slot, val common.Hash)

SetStorageAt sets the state of the given address at the given storage slot.

func (*VM) Snapshot added in v0.16.2

func (vm *VM) Snapshot() *state.StateDB

Snapshot the current state of the VM. The returned state can only be rolled back to once. Use state.StateDB.Copy if you need to rollback multiple times.

func (*VM) StorageAt

func (vm *VM) StorageAt(addr common.Address, slot common.Hash) (common.Hash, error)

StorageAt returns the state of the given address at the give storage slot.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL