Documentation
¶
Overview ¶
Package w3vm provides a VM for executing EVM messages.
Index ¶
- Variables
- func RandA() (addr common.Address)
- func Slot(pos, key common.Hash) common.Hash
- func Slot2(pos, key0, key1 common.Hash) common.Hash
- func Slot3(pos, key0, key1, key2 common.Hash) common.Hash
- func WETHAllowanceSlot(owner, spender common.Address) common.Hash
- func WETHBalanceSlot(addr common.Address) common.Hash
- type CallFuncFactory
- type Fetcher
- type Option
- func WithBlockContext(ctx *vm.BlockContext) Option
- func WithChainConfig(cfg *params.ChainConfig) Option
- func WithFetcher(fetcher Fetcher) Option
- func WithFork(client *w3.Client, blockNumber *big.Int) Option
- func WithHeader(header *types.Header) Option
- func WithNoBaseFee() Option
- func WithPrecompile(addr common.Address, contract vm.PrecompiledContract) Option
- func WithState(state w3types.State) Option
- func WithStateDB(db *state.StateDB) Option
- func WithTB(tb testing.TB) Option
- type Receipt
- type VM
- func (vm *VM) Apply(msg *w3types.Message, hooks ...*tracing.Hooks) (*Receipt, error)
- func (vm *VM) ApplyTx(tx *types.Transaction, hooks ...*tracing.Hooks) (*Receipt, error)
- func (vm *VM) Balance(addr common.Address) (*big.Int, error)
- func (vm *VM) Call(msg *w3types.Message, hooks ...*tracing.Hooks) (*Receipt, error)
- func (vm *VM) CallFunc(contract common.Address, f w3types.Func, args ...any) *CallFuncFactory
- func (vm *VM) Code(addr common.Address) ([]byte, error)
- func (vm *VM) Nonce(addr common.Address) (uint64, error)
- func (vm *VM) Rollback(snapshot *state.StateDB)
- func (vm *VM) SetBalance(addr common.Address, balance *big.Int)
- func (vm *VM) SetCode(addr common.Address, code []byte)
- func (vm *VM) SetNonce(addr common.Address, nonce uint64)
- func (vm *VM) SetStorageAt(addr common.Address, slot, val common.Hash)
- func (vm *VM) Snapshot() *state.StateDB
- func (vm *VM) StorageAt(addr common.Address, slot common.Hash) (common.Hash, error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrFetch = errors.New("fetching failed") ErrRevert = errors.New("execution reverted") )
var ErrMissingFunc = errors.New("missing function")
Functions ¶
func Slot ¶
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 ¶
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
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 ¶
WETHAllowanceSlot returns the storage slot that stores the WETH allowance of the given owner to the spender.
func WETHBalanceSlot ¶
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 ¶
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.
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 ¶
WithFetcher sets the fetcher for the VM.
func WithFork ¶
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 ¶
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 ¶
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
WithStateDB sets the state DB for the VM, that is usually a snapshot obtained from VM.Snapshot.
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 ¶
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 (*VM) Apply ¶
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
ApplyTx is like VM.Apply, but takes a transaction instead of a message.
func (*VM) Call ¶
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 ¶
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) SetBalance ¶ added in v0.16.8
SetBalance sets the balance of the given address.
func (*VM) SetStorageAt ¶ added in v0.16.8
SetStorageAt sets the state of the given address at the given storage slot.
func (*VM) Snapshot ¶ added in v0.16.2
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.