core, build: reintroduce evmone

Reintroduce evmone as an EVM interpreter option behind the 'evmone' build tag.
Includes gas refund propagation from evmone to Go StateDB, and C memory
allocation for all parameters passed across the CGO boundary.
This commit is contained in:
Khâny 2026-02-13 20:44:30 +01:00
parent 31d5d82ce5
commit 73f4ec86e7
12 changed files with 1268 additions and 178 deletions

4
.gitignore vendored
View file

@ -57,3 +57,7 @@ cmd/geth/geth
cmd/rlpdump/rlpdump
cmd/workload/workload
cmd/keeper/keeper
geth
cmd/keeper/keeper-zisk
keeper-zisk.elf

3
.gitmodules vendored
View file

@ -6,3 +6,6 @@
path = tests/evm-benchmarks
url = https://github.com/ipsilon/evm-benchmarks
shallow = true
[submodule "evmone"]
path = evmone
url = https://github.com/ethereum/evmone

View file

@ -123,6 +123,34 @@ var (
Name: "example",
Tags: "example",
},
{
Name: "example-evmone",
Tags: "example,evmone",
},
{
Name: "ziren-evmone",
GOOS: "linux",
GOARCH: "mipsle",
CC: "mipsel-linux-gnu-gcc",
Tags: "ziren,evmone",
Env: map[string]string{"GOMIPS": "softfloat", "CGO_ENABLED": "1"},
},
{
// WASM targets cannot use CGO, so evmone falls back to Go interpreter.
// The evmone tag is included for uniformity with other evmone targets.
Name: "wasm-js-evmone",
GOOS: "js",
GOARCH: "wasm",
Tags: "example,evmone",
},
{
// WASM targets cannot use CGO, so evmone falls back to Go interpreter.
// The evmone tag is included for uniformity with other evmone targets.
Name: "wasm-wasi-evmone",
GOOS: "wasip1",
GOARCH: "wasm",
Tags: "example,evmone",
},
}
// A debian package is created for all executables listed here.
@ -288,6 +316,28 @@ func doInstallKeeper(cmdline []string) {
tc.Root = build.DownloadGo(csdb)
}
// Pre-build evmone static libraries for targets that need them.
builtEvmone := make(map[string]bool)
for _, target := range keeperTargets {
if !strings.Contains(target.Tags, "evmone") {
continue
}
// Skip targets where CGO is disabled or unavailable.
if target.GOARCH == "wasm" || target.Env["CGO_ENABLED"] == "0" {
continue
}
evmoneTarget := "native"
if target.GOARCH != "" {
evmoneTarget = target.GOARCH
}
if builtEvmone[evmoneTarget] {
continue
}
log.Printf("Building evmone library for %s", evmoneTarget)
build.MustRun(exec.Command("./evmone/build.sh", evmoneTarget))
builtEvmone[evmoneTarget] = true
}
for _, target := range keeperTargets {
log.Printf("Building keeper-%s", target.Name)

45
cmd/keeper/zisk.ld Normal file
View file

@ -0,0 +1,45 @@
OUTPUT_FORMAT("elf64-littleriscv")
OUTPUT_ARCH("riscv")
ENTRY(_rt0_riscv64_tamago)
MEMORY {
rom (xa) : ORIGIN = 0x80000000, LENGTH = 0x08000000
ram (wxa) : ORIGIN = 0xa0000000, LENGTH = 0x20000000
}
PHDRS {
text PT_LOAD FLAGS(5);
rodata PT_LOAD FLAGS(4);
data PT_LOAD FLAGS(6);
bss PT_LOAD FLAGS(6);
}
SECTIONS
{
.text : { *(.text.init) *(.text .text.*) } >rom AT>rom :text
. = ALIGN(8);
PROVIDE(_global_pointer = .);
.rodata : { *(.rodata .rodata.*) } >rom AT>rom :rodata
.data : { *(.data .data.* .sdata .sdata.*) } >ram AT>ram :data
. = ALIGN(8);
.bss : {
PROVIDE(_bss_start = .);
*(.bss .bss.* .sbss .sbss.*);
PROVIDE(_bss_end = .);
} >ram AT>ram :bss
. = ALIGN(8);
.noptrbss : { *(.noptrbss) } >ram AT>ram :bss
. = ALIGN(8);
PROVIDE(_init_stack_top = . + 0x800000);
PROVIDE(_kernel_heap_bottom = _init_stack_top);
PROVIDE(_kernel_heap_top = ORIGIN(ram) + LENGTH(ram));
PROVIDE(_kernel_heap_size = _kernel_heap_top - _kernel_heap_bottom);
_end = .;
}

329
core/vm/evmone.go Normal file
View file

@ -0,0 +1,329 @@
//go:build evmone && cgo
package vm
/*
#cgo CFLAGS: -I${SRCDIR}/../../evmone/evmc/include
#cgo !mipsle LDFLAGS: -L${SRCDIR}/../../evmone/build/lib -L${SRCDIR}/../../evmone/build/lib/evmone_precompiles -L${SRCDIR}/../../evmone/build/deps/src/blst -levmone -levmone_precompiles -lblst -lstdc++ -lm
#cgo mipsle LDFLAGS: -L${SRCDIR}/../../evmone/build-mipsle/lib -L${SRCDIR}/../../evmone/build-mipsle/lib/evmone_precompiles -L${SRCDIR}/../../evmone/build-mipsle/deps/src/blst -levmone -levmone_precompiles -lblst -lstdc++ -lm
#include <evmc/evmc.h>
#include <stdlib.h>
#include <string.h>
// Forward declarations for Go-exported host callbacks.
extern bool goAccountExists(uintptr_t handle, const evmc_address* addr);
extern evmc_bytes32 goGetStorage(uintptr_t handle, const evmc_address* addr, const evmc_bytes32* key);
extern enum evmc_storage_status goSetStorage(uintptr_t handle, const evmc_address* addr, const evmc_bytes32* key, const evmc_bytes32* value);
extern evmc_uint256be goGetBalance(uintptr_t handle, const evmc_address* addr);
extern size_t goGetCodeSize(uintptr_t handle, const evmc_address* addr);
extern evmc_bytes32 goGetCodeHash(uintptr_t handle, const evmc_address* addr);
extern size_t goCopyCode(uintptr_t handle, const evmc_address* addr, size_t code_offset, uint8_t* buffer_data, size_t buffer_size);
extern bool goSelfdestruct(uintptr_t handle, const evmc_address* addr, const evmc_address* beneficiary);
extern struct evmc_result goCall(uintptr_t handle, const struct evmc_message* msg);
extern struct evmc_tx_context goGetTxContext(uintptr_t handle);
extern evmc_bytes32 goGetBlockHash(uintptr_t handle, int64_t number);
extern void goEmitLog(uintptr_t handle, const evmc_address* addr, const uint8_t* data, size_t data_size, const evmc_bytes32 topics[], size_t topics_count);
extern enum evmc_access_status goAccessAccount(uintptr_t handle, const evmc_address* addr);
extern enum evmc_access_status goAccessStorage(uintptr_t handle, const evmc_address* addr, const evmc_bytes32* key);
extern evmc_bytes32 goGetTransientStorage(uintptr_t handle, const evmc_address* addr, const evmc_bytes32* key);
extern void goSetTransientStorage(uintptr_t handle, const evmc_address* addr, const evmc_bytes32* key, const evmc_bytes32* value);
// C wrapper functions that bridge EVMC host interface to Go.
// These are needed because CGo cannot directly use Go function pointers as C callbacks.
// The handle is stored as the evmc_host_context* pointer value (cast to uintptr_t on Go side).
static bool c_account_exists(struct evmc_host_context* ctx, const evmc_address* addr) {
return goAccountExists((uintptr_t)ctx, addr);
}
static evmc_bytes32 c_get_storage(struct evmc_host_context* ctx, const evmc_address* addr, const evmc_bytes32* key) {
return goGetStorage((uintptr_t)ctx, addr, key);
}
static enum evmc_storage_status c_set_storage(struct evmc_host_context* ctx, const evmc_address* addr, const evmc_bytes32* key, const evmc_bytes32* value) {
return goSetStorage((uintptr_t)ctx, addr, key, value);
}
static evmc_uint256be c_get_balance(struct evmc_host_context* ctx, const evmc_address* addr) {
return goGetBalance((uintptr_t)ctx, addr);
}
static size_t c_get_code_size(struct evmc_host_context* ctx, const evmc_address* addr) {
return goGetCodeSize((uintptr_t)ctx, addr);
}
static evmc_bytes32 c_get_code_hash(struct evmc_host_context* ctx, const evmc_address* addr) {
return goGetCodeHash((uintptr_t)ctx, addr);
}
static size_t c_copy_code(struct evmc_host_context* ctx, const evmc_address* addr, size_t code_offset, uint8_t* buffer_data, size_t buffer_size) {
return goCopyCode((uintptr_t)ctx, addr, code_offset, buffer_data, buffer_size);
}
static bool c_selfdestruct(struct evmc_host_context* ctx, const evmc_address* addr, const evmc_address* beneficiary) {
return goSelfdestruct((uintptr_t)ctx, addr, beneficiary);
}
static struct evmc_result c_call(struct evmc_host_context* ctx, const struct evmc_message* msg) {
return goCall((uintptr_t)ctx, msg);
}
static struct evmc_tx_context c_get_tx_context(struct evmc_host_context* ctx) {
return goGetTxContext((uintptr_t)ctx);
}
static evmc_bytes32 c_get_block_hash(struct evmc_host_context* ctx, int64_t number) {
return goGetBlockHash((uintptr_t)ctx, number);
}
static void c_emit_log(struct evmc_host_context* ctx, const evmc_address* addr, const uint8_t* data, size_t data_size, const evmc_bytes32 topics[], size_t topics_count) {
goEmitLog((uintptr_t)ctx, addr, data, data_size, topics, topics_count);
}
static enum evmc_access_status c_access_account(struct evmc_host_context* ctx, const evmc_address* addr) {
return goAccessAccount((uintptr_t)ctx, addr);
}
static enum evmc_access_status c_access_storage(struct evmc_host_context* ctx, const evmc_address* addr, const evmc_bytes32* key) {
return goAccessStorage((uintptr_t)ctx, addr, key);
}
static evmc_bytes32 c_get_transient_storage(struct evmc_host_context* ctx, const evmc_address* addr, const evmc_bytes32* key) {
return goGetTransientStorage((uintptr_t)ctx, addr, key);
}
static void c_set_transient_storage(struct evmc_host_context* ctx, const evmc_address* addr, const evmc_bytes32* key, const evmc_bytes32* value) {
goSetTransientStorage((uintptr_t)ctx, addr, key, value);
}
// The singleton host interface.
static const struct evmc_host_interface go_host = {
.account_exists = c_account_exists,
.get_storage = c_get_storage,
.set_storage = c_set_storage,
.get_balance = c_get_balance,
.get_code_size = c_get_code_size,
.get_code_hash = c_get_code_hash,
.copy_code = c_copy_code,
.selfdestruct = c_selfdestruct,
.call = c_call,
.get_tx_context = c_get_tx_context,
.get_block_hash = c_get_block_hash,
.emit_log = c_emit_log,
.access_account = c_access_account,
.access_storage = c_access_storage,
.get_transient_storage = c_get_transient_storage,
.set_transient_storage = c_set_transient_storage,
};
// evmc_create_evmone is provided by libevmone.a.
extern struct evmc_vm* evmc_create_evmone(void);
// execute_evmone calls evmc_execute on the given VM with our host interface.
static struct evmc_result execute_evmone(
struct evmc_vm* vm,
uintptr_t handle,
enum evmc_revision rev,
int64_t gas,
const evmc_address* recipient,
const evmc_address* sender,
const uint8_t* input_data,
size_t input_size,
const evmc_uint256be* value,
const uint8_t* code,
size_t code_size,
int32_t depth,
uint32_t flags
) {
struct evmc_message msg;
memset(&msg, 0, sizeof(msg));
msg.kind = EVMC_CALL;
msg.flags = flags;
msg.depth = depth;
msg.gas = gas;
msg.recipient = *recipient;
msg.sender = *sender;
msg.input_data = input_data;
msg.input_size = input_size;
msg.value = *value;
return vm->execute(vm, &go_host, (struct evmc_host_context*)(void*)handle, rev, &msg, code, code_size);
}
// create_vm creates the evmone VM instance.
static struct evmc_vm* create_vm(void) {
return evmc_create_evmone();
}
// release_result calls the release function pointer on an evmc_result.
static void release_result(struct evmc_result* result) {
if (result->release) {
result->release(result);
}
}
// free_result_output frees the output data of an evmc_result (used as release callback).
void free_result_output(const struct evmc_result* result) {
free((void*)result->output_data);
}
*/
import "C"
import (
"runtime/cgo"
"sync"
"unsafe"
"github.com/ethereum/go-ethereum/common"
"github.com/holiman/uint256"
)
var (
evmoneVM *C.struct_evmc_vm
evmoneOnce sync.Once
)
// initEvmone creates the singleton evmone VM instance.
func initEvmone() {
evmoneOnce.Do(func() {
evmoneVM = C.create_vm()
if evmoneVM == nil {
panic("evmone: failed to create VM instance")
}
})
}
// evmcHostContext wraps the EVM and contract for use in EVMC host callbacks.
type evmcHostContext struct {
evm *EVM
contract *Contract
}
// pinHostContext creates a cgo.Handle for the host context, returning
// the handle value. The caller must call handle.Delete() when done.
func pinHostContext(ctx *evmcHostContext) cgo.Handle {
return cgo.NewHandle(ctx)
}
// hostContextFromHandle recovers the evmcHostContext from a cgo.Handle value.
func hostContextFromHandle(h uintptr) *evmcHostContext {
return cgo.Handle(h).Value().(*evmcHostContext)
}
// Type conversion helpers between Go types and EVMC C types.
func goAddress(addr *C.evmc_address) common.Address {
var a common.Address
copy(a[:], C.GoBytes(unsafe.Pointer(&addr.bytes[0]), 20))
return a
}
func goHash(h *C.evmc_bytes32) common.Hash {
var hash common.Hash
copy(hash[:], C.GoBytes(unsafe.Pointer(&h.bytes[0]), 32))
return hash
}
func evmcAddress(addr common.Address) C.evmc_address {
var a C.evmc_address
for i := 0; i < 20; i++ {
a.bytes[i] = C.uint8_t(addr[i])
}
return a
}
func evmcHash(h common.Hash) C.evmc_bytes32 {
var hash C.evmc_bytes32
for i := 0; i < 32; i++ {
hash.bytes[i] = C.uint8_t(h[i])
}
return hash
}
// evmcUint256 converts a uint256.Int (little-endian limbs) to EVMC big-endian bytes32.
func evmcUint256(v *uint256.Int) C.evmc_uint256be {
b32 := v.Bytes32() // big-endian [32]byte
var out C.evmc_uint256be
for i := 0; i < 32; i++ {
out.bytes[i] = C.uint8_t(b32[i])
}
return out
}
// evmcExecuteResult holds the results from an evmone execution in Go-native types.
type evmcExecuteResult struct {
statusCode int32
gasLeft int64
gasRefund int64
output []byte
}
// executeEvmone calls the C execute_evmone function and returns the result.
// This must be in the same file as the C preamble that defines execute_evmone.
func executeEvmone(
handle cgo.Handle,
rev int32,
gas int64,
recipient common.Address,
sender common.Address,
input []byte,
code []byte,
value *uint256.Int,
depth int32,
readOnly bool,
) evmcExecuteResult {
// Allocate all parameter structs in C memory to avoid cgo pointer violations.
// Go 1.21+ strictly checks that no Go pointers are passed to or returned from C.
cRecipient := (*C.evmc_address)(C.malloc(C.size_t(unsafe.Sizeof(C.evmc_address{}))))
defer C.free(unsafe.Pointer(cRecipient))
*cRecipient = evmcAddress(recipient)
cSender := (*C.evmc_address)(C.malloc(C.size_t(unsafe.Sizeof(C.evmc_address{}))))
defer C.free(unsafe.Pointer(cSender))
*cSender = evmcAddress(sender)
cValue := (*C.evmc_uint256be)(C.malloc(C.size_t(unsafe.Sizeof(C.evmc_uint256be{}))))
defer C.free(unsafe.Pointer(cValue))
*cValue = evmcUint256(value)
var inputPtr *C.uint8_t
var inputSize C.size_t
if len(input) > 0 {
cInput := C.CBytes(input)
defer C.free(cInput)
inputPtr = (*C.uint8_t)(cInput)
inputSize = C.size_t(len(input))
}
var codePtr *C.uint8_t
var codeSize C.size_t
if len(code) > 0 {
cCode := C.CBytes(code)
defer C.free(cCode)
codePtr = (*C.uint8_t)(cCode)
codeSize = C.size_t(len(code))
}
var flags C.uint32_t
if readOnly {
flags = C.EVMC_STATIC
}
result := C.execute_evmone(
evmoneVM,
C.uintptr_t(handle),
C.enum_evmc_revision(rev),
C.int64_t(gas),
cRecipient,
cSender,
inputPtr,
inputSize,
cValue,
codePtr,
codeSize,
C.int32_t(depth),
flags,
)
var output []byte
if result.output_data != nil && result.output_size > 0 {
output = C.GoBytes(unsafe.Pointer(result.output_data), C.int(result.output_size))
}
// Free the result's output buffer via the release callback.
C.release_result(&result)
return evmcExecuteResult{
statusCode: int32(result.status_code),
gasLeft: int64(result.gas_left),
gasRefund: int64(result.gas_refund),
output: output,
}
}

409
core/vm/evmone_host.go Normal file
View file

@ -0,0 +1,409 @@
//go:build evmone && cgo
package vm
/*
#include <evmc/evmc.h>
#include <stdlib.h>
#include <string.h>
// free_result_output is defined in evmone.go's C preamble.
extern void free_result_output(const struct evmc_result* result);
*/
import "C"
import (
"math/big"
"unsafe"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/holiman/uint256"
)
//export goAccountExists
func goAccountExists(handle C.uintptr_t, addr *C.evmc_address) C.bool {
ctx := hostContextFromHandle(uintptr(handle))
return C.bool(ctx.evm.StateDB.Exist(goAddress(addr)))
}
//export goGetStorage
func goGetStorage(handle C.uintptr_t, addr *C.evmc_address, key *C.evmc_bytes32) C.evmc_bytes32 {
ctx := hostContextFromHandle(uintptr(handle))
val := ctx.evm.StateDB.GetState(goAddress(addr), goHash(key))
return evmcHash(val)
}
//export goSetStorage
func goSetStorage(handle C.uintptr_t, addr *C.evmc_address, key *C.evmc_bytes32, value *C.evmc_bytes32) C.enum_evmc_storage_status {
ctx := hostContextFromHandle(uintptr(handle))
address := goAddress(addr)
slot := goHash(key)
newVal := goHash(value)
current, original := ctx.evm.StateDB.GetStateAndCommittedState(address, slot)
ctx.evm.StateDB.SetState(address, slot, newVal)
// Determine the EVMC storage status based on original, current, and new values.
// This follows the EIP-2200 / EIP-1283 net gas metering logic.
// evmone uses this status to calculate the appropriate gas cost internally.
zeroHash := common.Hash{}
if current == newVal {
// No-op or dirty re-assignment: minimal cost (SLOAD_GAS)
return C.EVMC_STORAGE_ASSIGNED
}
if original == current {
if original == zeroHash {
// 0 -> 0 -> Z: creating a new slot
return C.EVMC_STORAGE_ADDED
}
if newVal == zeroHash {
// X -> X -> 0: deleting
return C.EVMC_STORAGE_DELETED
}
// X -> X -> Z: modifying
return C.EVMC_STORAGE_MODIFIED
}
// original != current (dirty slot)
if original != zeroHash {
if current == zeroHash {
if newVal == original {
// X -> 0 -> X: restoring deleted
return C.EVMC_STORAGE_DELETED_RESTORED
}
// X -> 0 -> Z: re-adding after delete
return C.EVMC_STORAGE_DELETED_ADDED
}
if newVal == zeroHash {
// X -> Y -> 0: deleting modified
return C.EVMC_STORAGE_MODIFIED_DELETED
}
if newVal == original {
// X -> Y -> X: restoring modified
return C.EVMC_STORAGE_MODIFIED_RESTORED
}
} else {
// original == zero, current != zero, newVal != current
if newVal == zeroHash {
// 0 -> Y -> 0: deleting added
return C.EVMC_STORAGE_ADDED_DELETED
}
}
// Catch-all: dirty update
return C.EVMC_STORAGE_ASSIGNED
}
//export goGetBalance
func goGetBalance(handle C.uintptr_t, addr *C.evmc_address) C.evmc_uint256be {
ctx := hostContextFromHandle(uintptr(handle))
balance := ctx.evm.StateDB.GetBalance(goAddress(addr))
return evmcUint256(balance)
}
//export goGetCodeSize
func goGetCodeSize(handle C.uintptr_t, addr *C.evmc_address) C.size_t {
ctx := hostContextFromHandle(uintptr(handle))
return C.size_t(ctx.evm.StateDB.GetCodeSize(goAddress(addr)))
}
//export goGetCodeHash
func goGetCodeHash(handle C.uintptr_t, addr *C.evmc_address) C.evmc_bytes32 {
ctx := hostContextFromHandle(uintptr(handle))
hash := ctx.evm.StateDB.GetCodeHash(goAddress(addr))
return evmcHash(hash)
}
//export goCopyCode
func goCopyCode(handle C.uintptr_t, addr *C.evmc_address, codeOffset C.size_t, bufferData *C.uint8_t, bufferSize C.size_t) C.size_t {
ctx := hostContextFromHandle(uintptr(handle))
code := ctx.evm.StateDB.GetCode(goAddress(addr))
offset := int(codeOffset)
if offset >= len(code) {
return 0
}
toCopy := len(code) - offset
if toCopy > int(bufferSize) {
toCopy = int(bufferSize)
}
if toCopy > 0 {
dst := unsafe.Slice((*byte)(unsafe.Pointer(bufferData)), int(bufferSize))
copy(dst[:toCopy], code[offset:offset+toCopy])
}
return C.size_t(toCopy)
}
//export goSelfdestruct
func goSelfdestruct(handle C.uintptr_t, addr *C.evmc_address, beneficiary *C.evmc_address) C.bool {
ctx := hostContextFromHandle(uintptr(handle))
address := goAddress(addr)
benefAddr := goAddress(beneficiary)
// Transfer balance to beneficiary
balance := ctx.evm.StateDB.GetBalance(address)
if balance.Sign() > 0 {
ctx.evm.StateDB.SubBalance(address, balance, 0)
ctx.evm.StateDB.AddBalance(benefAddr, balance, 0)
}
// Post-Cancun: use EIP-6780 semantics
if ctx.evm.chainRules.IsCancun {
_, destructed := ctx.evm.StateDB.SelfDestruct6780(address)
return C.bool(destructed)
}
hasPreviouslyDestructed := ctx.evm.StateDB.HasSelfDestructed(address)
ctx.evm.StateDB.SelfDestruct(address)
return C.bool(!hasPreviouslyDestructed)
}
//export goCall
func goCall(handle C.uintptr_t, msg *C.struct_evmc_message) C.struct_evmc_result {
ctx := hostContextFromHandle(uintptr(handle))
kind := msg.kind
sender := goAddress(&msg.sender)
recipient := goAddress(&msg.recipient)
input := C.GoBytes(unsafe.Pointer(msg.input_data), C.int(msg.input_size))
gas := uint64(msg.gas)
value := new(uint256.Int)
value.SetBytes(C.GoBytes(unsafe.Pointer(&msg.value.bytes[0]), 32))
var (
ret []byte
leftOverGas uint64
err error
)
switch kind {
case C.EVMC_CALL:
if msg.flags&C.EVMC_STATIC != 0 {
ret, leftOverGas, err = ctx.evm.StaticCall(sender, recipient, input, gas)
} else {
ret, leftOverGas, err = ctx.evm.Call(sender, recipient, input, gas, value)
}
case C.EVMC_CALLCODE:
ret, leftOverGas, err = ctx.evm.CallCode(sender, recipient, input, gas, value)
case C.EVMC_DELEGATECALL:
// For DELEGATECALL, sender is the original caller (contract.Caller()),
// recipient is the current contract, and code_address has the code.
codeAddr := goAddress(&msg.code_address)
ret, leftOverGas, err = ctx.evm.DelegateCall(sender, recipient, codeAddr, input, gas, value)
case C.EVMC_CREATE:
var createAddr common.Address
ret, createAddr, leftOverGas, err = ctx.evm.Create(sender, input, gas, value)
return makeEvmcResult(ret, leftOverGas, err, createAddr)
case C.EVMC_CREATE2:
salt := new(uint256.Int)
salt.SetBytes(C.GoBytes(unsafe.Pointer(&msg.create2_salt.bytes[0]), 32))
var createAddr common.Address
ret, createAddr, leftOverGas, err = ctx.evm.Create2(sender, input, gas, value, salt)
return makeEvmcResult(ret, leftOverGas, err, createAddr)
}
return makeEvmcResult(ret, leftOverGas, err, common.Address{})
}
// makeEvmcResult constructs an evmc_result from Go execution results.
func makeEvmcResult(output []byte, gasLeft uint64, err error, createAddr common.Address) C.struct_evmc_result {
var result C.struct_evmc_result
result.gas_left = C.int64_t(gasLeft)
if err == nil {
result.status_code = C.EVMC_SUCCESS
} else if err == ErrExecutionReverted {
result.status_code = C.EVMC_REVERT
} else if err == ErrOutOfGas {
result.status_code = C.EVMC_OUT_OF_GAS
} else if err == ErrDepth {
result.status_code = C.EVMC_CALL_DEPTH_EXCEEDED
} else if err == ErrInsufficientBalance {
result.status_code = C.EVMC_INSUFFICIENT_BALANCE
} else {
result.status_code = C.EVMC_FAILURE
}
if len(output) > 0 {
// Allocate C memory for output data and set release callback.
cData := C.malloc(C.size_t(len(output)))
C.memcpy(cData, unsafe.Pointer(&output[0]), C.size_t(len(output)))
result.output_data = (*C.uint8_t)(cData)
result.output_size = C.size_t(len(output))
result.release = C.evmc_release_result_fn(C.free_result_output)
}
if createAddr != (common.Address{}) {
result.create_address = evmcAddress(createAddr)
}
return result
}
//export goGetTxContext
func goGetTxContext(handle C.uintptr_t) C.struct_evmc_tx_context {
ctx := hostContextFromHandle(uintptr(handle))
evm := ctx.evm
var txCtx C.struct_evmc_tx_context
// Gas price
if evm.GasPrice != nil {
gasPrice := uint256.MustFromBig(evm.GasPrice)
txCtx.tx_gas_price = evmcUint256(gasPrice)
}
// Origin
txCtx.tx_origin = evmcAddress(evm.TxContext.Origin)
// Block coinbase
txCtx.block_coinbase = evmcAddress(evm.Context.Coinbase)
// Block number
if evm.Context.BlockNumber != nil {
txCtx.block_number = C.int64_t(evm.Context.BlockNumber.Int64())
}
// Block timestamp
txCtx.block_timestamp = C.int64_t(evm.Context.Time)
// Block gas limit
txCtx.block_gas_limit = C.int64_t(evm.Context.GasLimit)
// PREVRANDAO (post-merge) / DIFFICULTY (pre-merge)
if evm.Context.Random != nil {
txCtx.block_prev_randao = evmcHash(*evm.Context.Random)
} else if evm.Context.Difficulty != nil {
diff := uint256.MustFromBig(evm.Context.Difficulty)
txCtx.block_prev_randao = evmcUint256(diff)
}
// Chain ID
if evm.chainConfig != nil && evm.chainConfig.ChainID != nil {
chainID := uint256.MustFromBig(evm.chainConfig.ChainID)
txCtx.chain_id = evmcUint256(chainID)
}
// Base fee
if evm.Context.BaseFee != nil {
baseFee := uint256.MustFromBig(evm.Context.BaseFee)
txCtx.block_base_fee = evmcUint256(baseFee)
}
// Blob base fee
if evm.Context.BlobBaseFee != nil {
blobBaseFee := uint256.MustFromBig(evm.Context.BlobBaseFee)
txCtx.blob_base_fee = evmcUint256(blobBaseFee)
}
// Blob hashes — must be allocated in C memory to avoid CGO pointer violation.
if len(evm.TxContext.BlobHashes) > 0 {
n := len(evm.TxContext.BlobHashes)
size := C.size_t(n) * C.size_t(unsafe.Sizeof(C.evmc_bytes32{}))
cBlobs := (*C.evmc_bytes32)(C.malloc(size))
blobArr := unsafe.Slice(cBlobs, n)
for i, h := range evm.TxContext.BlobHashes {
blobArr[i] = evmcHash(h)
}
txCtx.blob_hashes = cBlobs
txCtx.blob_hashes_count = C.size_t(n)
// Note: this C memory is leaked per-call. evmone copies the data
// and doesn't retain the pointer, so a future optimization could
// pool or free it, but correctness requires C-allocated memory here.
}
return txCtx
}
//export goGetBlockHash
func goGetBlockHash(handle C.uintptr_t, number C.int64_t) C.evmc_bytes32 {
ctx := hostContextFromHandle(uintptr(handle))
hash := ctx.evm.Context.GetHash(uint64(number))
return evmcHash(hash)
}
//export goEmitLog
func goEmitLog(handle C.uintptr_t, addr *C.evmc_address, data *C.uint8_t, dataSize C.size_t, topics *C.evmc_bytes32, topicsCount C.size_t) {
ctx := hostContextFromHandle(uintptr(handle))
address := goAddress(addr)
var logData []byte
if dataSize > 0 {
logData = C.GoBytes(unsafe.Pointer(data), C.int(dataSize))
}
nTopics := int(topicsCount)
logTopics := make([]common.Hash, nTopics)
if nTopics > 0 {
topicSlice := unsafe.Slice(topics, nTopics)
for i := 0; i < nTopics; i++ {
logTopics[i] = goHash(&topicSlice[i])
}
}
ctx.evm.StateDB.AddLog(&types.Log{
Address: address,
Topics: logTopics,
Data: logData,
// Block number and tx hash are filled in by the receipt processing.
})
}
//export goAccessAccount
func goAccessAccount(handle C.uintptr_t, addr *C.evmc_address) C.enum_evmc_access_status {
ctx := hostContextFromHandle(uintptr(handle))
address := goAddress(addr)
warm := ctx.evm.StateDB.AddressInAccessList(address)
if !warm {
ctx.evm.StateDB.AddAddressToAccessList(address)
return C.EVMC_ACCESS_COLD
}
return C.EVMC_ACCESS_WARM
}
//export goAccessStorage
func goAccessStorage(handle C.uintptr_t, addr *C.evmc_address, key *C.evmc_bytes32) C.enum_evmc_access_status {
ctx := hostContextFromHandle(uintptr(handle))
address := goAddress(addr)
slot := goHash(key)
_, slotWarm := ctx.evm.StateDB.SlotInAccessList(address, slot)
if !slotWarm {
ctx.evm.StateDB.AddSlotToAccessList(address, slot)
return C.EVMC_ACCESS_COLD
}
return C.EVMC_ACCESS_WARM
}
//export goGetTransientStorage
func goGetTransientStorage(handle C.uintptr_t, addr *C.evmc_address, key *C.evmc_bytes32) C.evmc_bytes32 {
ctx := hostContextFromHandle(uintptr(handle))
val := ctx.evm.StateDB.GetTransientState(goAddress(addr), goHash(key))
return evmcHash(val)
}
//export goSetTransientStorage
func goSetTransientStorage(handle C.uintptr_t, addr *C.evmc_address, key *C.evmc_bytes32, value *C.evmc_bytes32) {
ctx := hostContextFromHandle(uintptr(handle))
ctx.evm.StateDB.SetTransientState(goAddress(addr), goHash(key), goHash(value))
}
// uint256FromBig converts a *big.Int to *uint256.Int, returning zero for nil.
func uint256FromBig(b *big.Int) *uint256.Int {
if b == nil {
return new(uint256.Int)
}
v, _ := uint256.FromBig(b)
if v == nil {
return new(uint256.Int)
}
return v
}

View file

@ -0,0 +1,58 @@
//go:build evmone
package vm
import "github.com/ethereum/go-ethereum/params"
// EVMC revision constants matching evmc/evmc.h enum evmc_revision.
const (
evmcFrontier int32 = 0
evmcHomestead int32 = 1
evmcTangerineWhistle int32 = 2
evmcSpuriousDragon int32 = 3
evmcByzantium int32 = 4
evmcConstantinople int32 = 5
evmcPetersburg int32 = 6
evmcIstanbul int32 = 7
evmcBerlin int32 = 8
evmcLondon int32 = 9
evmcParis int32 = 10
evmcShanghai int32 = 11
evmcCancun int32 = 12
evmcPrague int32 = 13
evmcOsaka int32 = 14
)
// evmcRevision maps go-ethereum chain rules to the corresponding EVMC revision.
func evmcRevision(rules params.Rules) int32 {
switch {
case rules.IsOsaka:
return evmcOsaka
case rules.IsPrague:
return evmcPrague
case rules.IsCancun:
return evmcCancun
case rules.IsShanghai:
return evmcShanghai
case rules.IsMerge:
return evmcParis
case rules.IsLondon:
return evmcLondon
case rules.IsBerlin:
return evmcBerlin
case rules.IsIstanbul:
return evmcIstanbul
case rules.IsConstantinople:
return evmcConstantinople
case rules.IsByzantium:
return evmcByzantium
case rules.IsEIP158:
return evmcSpuriousDragon
case rules.IsEIP150:
return evmcTangerineWhistle
case rules.IsHomestead:
return evmcHomestead
default:
return evmcFrontier
}
}

View file

@ -17,10 +17,7 @@
package vm
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/holiman/uint256"
)
@ -87,178 +84,3 @@ func (ctx *ScopeContext) CallInput() []byte {
func (ctx *ScopeContext) ContractCode() []byte {
return ctx.Contract.Code
}
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
// Increment the call depth which is restricted to 1024
evm.depth++
defer func() { evm.depth-- }()
// Make sure the readOnly is only set if we aren't in readOnly yet.
// This also makes sure that the readOnly flag isn't removed for child calls.
if readOnly && !evm.readOnly {
evm.readOnly = true
defer func() { evm.readOnly = false }()
}
// Reset the previous call's return data. It's unimportant to preserve the old buffer
// as every returning call will return new data anyway.
evm.returnData = nil
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}
var (
op OpCode // current opcode
jumpTable *JumpTable = evm.table
mem = NewMemory() // bound memory
stack = newstack() // local stack
callContext = &ScopeContext{
Memory: mem,
Stack: stack,
Contract: contract,
}
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
// to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter
cost uint64
// copies used by tracer
pcCopy uint64 // needed for the deferred EVMLogger
gasCopy uint64 // for EVMLogger to log gas remaining before execution
logged bool // deferred EVMLogger should ignore already logged steps
res []byte // result of the opcode execution function
debug = evm.Config.Tracer != nil
isEIP4762 = evm.chainRules.IsEIP4762
)
// Don't move this deferred function, it's placed before the OnOpcode-deferred method,
// so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools
defer func() {
returnStack(stack)
mem.Free()
}()
contract.Input = input
if debug {
defer func() { // this deferred method handles exit-with-error
if err == nil {
return
}
if !logged && evm.Config.Tracer.OnOpcode != nil {
evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err))
}
if logged && evm.Config.Tracer.OnFault != nil {
evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, evm.depth, VMErrorFromErr(err))
}
}()
}
// The Interpreter main run loop (contextual). This loop runs until either an
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
// the execution of one of the operations or until the done flag is set by the
// parent context.
_ = jumpTable[0] // nil-check the jumpTable out of the loop
for {
if debug {
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
// if the PC ends up in a new "chunk" of verkleized code, charge the
// associated costs.
contractAddr := contract.Address()
consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas)
contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if consumed < wanted {
return nil, ErrOutOfGas
}
}
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
op = contract.GetOp(pc)
operation := jumpTable[op]
cost = operation.constantGas // For tracing
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
} else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
// for tracing: this gas consumption event is emitted below in the debug section.
if contract.Gas < cost {
return nil, ErrOutOfGas
} else {
contract.Gas -= cost
}
// All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64
if operation.dynamicGas != nil {
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
// Consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
}
// for tracing: this gas consumption event is emitted below in the debug section.
if contract.Gas < dynamicCost {
return nil, ErrOutOfGas
} else {
contract.Gas -= dynamicCost
}
}
// Do tracing before potential memory expansion
if debug {
if evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if evm.Config.Tracer.OnOpcode != nil {
evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err))
logged = true
}
}
if memorySize > 0 {
mem.Resize(memorySize)
}
// execute the operation
res, err = operation.execute(&pc, evm, callContext)
if err != nil {
break
}
pc++
}
if err == errStopToken {
err = nil // clear stop token error
}
return res, err
}

View file

@ -0,0 +1,29 @@
//go:build !evmone || !cgo
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
return evm.runGoInterpreter(contract, input, readOnly)
}

View file

@ -0,0 +1,141 @@
//go:build evmone && cgo
package vm
import (
"errors"
"math"
"runtime/cgo"
)
// EVMC status code constants (must match evmc_status_code enum in evmc.h).
const (
evmcSuccess int32 = 0
evmcFailure int32 = 1
evmcRevert int32 = 2
evmcOutOfGas int32 = 3
evmcInvalidInstruction int32 = 4
evmcUndefinedInstruction int32 = 5
evmcStackOverflowStatus int32 = 6
evmcStackUnderflowStatus int32 = 7
evmcBadJumpDestination int32 = 8
evmcInvalidMemoryAccess int32 = 9
evmcCallDepthExceeded int32 = 10
evmcStaticModeViolation int32 = 11
)
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
//
// When built with the evmone tag, this method executes bytecode via the evmone
// C++ EVM, falling back to the Go interpreter for tracing, Verkle, or ExtraEips.
func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
// Fall back to Go interpreter for cases evmone cannot handle:
// 1. Tracing: evmone doesn't support per-opcode Go callbacks
// 2. Verkle/EIP-4762: requires per-chunk gas charging not in EVMC
// 3. ExtraEips: evmone doesn't support arbitrary custom EIPs
if evm.Config.Tracer != nil || evm.chainRules.IsEIP4762 || len(evm.Config.ExtraEips) > 0 {
return evm.runGoInterpreter(contract, input, readOnly)
}
// Increment the call depth which is restricted to 1024
evm.depth++
defer func() { evm.depth-- }()
// Make sure the readOnly is only set if we aren't in readOnly yet.
// This also makes sure that the readOnly flag isn't removed for child calls.
if readOnly && !evm.readOnly {
evm.readOnly = true
defer func() { evm.readOnly = false }()
}
// Reset the previous call's return data.
evm.returnData = nil
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}
contract.Input = input
// Initialize the singleton evmone VM.
initEvmone()
// Map chain rules to EVMC revision.
rev := evmcRevision(evm.chainRules)
// Create and pin the host context.
hostCtx := &evmcHostContext{
evm: evm,
contract: contract,
}
handle := pinHostContext(hostCtx)
defer cgo.Handle(handle).Delete()
// EVMC uses int64 for gas; cap to avoid overflow when Go uses uint64 gas limits.
gas := int64(contract.Gas)
if contract.Gas > uint64(math.MaxInt64) {
gas = math.MaxInt64
}
// Execute via evmone.
result := executeEvmone(
handle,
rev,
gas,
contract.Address(),
contract.Caller(),
input,
contract.Code,
contract.Value(),
int32(evm.depth),
readOnly,
)
ret = result.output
// Update gas accounting: evmone returns gas_left.
if result.gasLeft >= 0 {
contract.Gas = uint64(result.gasLeft)
} else {
contract.Gas = 0
}
// Propagate gas refund from evmone to StateDB.
// evmone tracks SSTORE refunds internally; we must relay them to the
// Go state so that state_transition.calcRefund() can apply them.
if result.gasRefund > 0 {
evm.StateDB.AddRefund(uint64(result.gasRefund))
} else if result.gasRefund < 0 {
evm.StateDB.SubRefund(uint64(-result.gasRefund))
}
// Map EVMC status to go-ethereum errors.
switch result.statusCode {
case evmcSuccess:
return ret, nil
case evmcRevert:
return ret, ErrExecutionReverted
case evmcOutOfGas:
return nil, ErrOutOfGas
case evmcInvalidInstruction:
return nil, &ErrInvalidOpCode{}
case evmcUndefinedInstruction:
return nil, &ErrInvalidOpCode{}
case evmcStackOverflowStatus:
return nil, &ErrStackOverflow{}
case evmcStackUnderflowStatus:
return nil, &ErrStackUnderflow{}
case evmcBadJumpDestination:
return nil, ErrInvalidJump
case evmcStaticModeViolation:
return nil, ErrWriteProtection
case evmcCallDepthExceeded:
return nil, ErrDepth
case evmcInvalidMemoryAccess:
return nil, ErrReturnDataOutOfBounds
default:
return nil, errors.New("evmone: execution failure")
}
}

199
core/vm/interpreter_run.go Normal file
View file

@ -0,0 +1,199 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"fmt"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/tracing"
)
// runGoInterpreter loops and evaluates the contract's code with the given input
// data and returns the return byte-slice and an error if one occurred.
//
// It's important to note that any errors returned by the interpreter should be
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (evm *EVM) runGoInterpreter(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
// Increment the call depth which is restricted to 1024
evm.depth++
defer func() { evm.depth-- }()
// Make sure the readOnly is only set if we aren't in readOnly yet.
// This also makes sure that the readOnly flag isn't removed for child calls.
if readOnly && !evm.readOnly {
evm.readOnly = true
defer func() { evm.readOnly = false }()
}
// Reset the previous call's return data. It's unimportant to preserve the old buffer
// as every returning call will return new data anyway.
evm.returnData = nil
// Don't bother with the execution if there's no code.
if len(contract.Code) == 0 {
return nil, nil
}
var (
op OpCode // current opcode
jumpTable *JumpTable = evm.table
mem = NewMemory() // bound memory
stack = newstack() // local stack
callContext = &ScopeContext{
Memory: mem,
Stack: stack,
Contract: contract,
}
// For optimisation reason we're using uint64 as the program counter.
// It's theoretically possible to go above 2^64. The YP defines the PC
// to be uint256. Practically much less so feasible.
pc = uint64(0) // program counter
cost uint64
// copies used by tracer
pcCopy uint64 // needed for the deferred EVMLogger
gasCopy uint64 // for EVMLogger to log gas remaining before execution
logged bool // deferred EVMLogger should ignore already logged steps
res []byte // result of the opcode execution function
debug = evm.Config.Tracer != nil
isEIP4762 = evm.chainRules.IsEIP4762
)
// Don't move this deferred function, it's placed before the OnOpcode-deferred method,
// so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools
defer func() {
returnStack(stack)
mem.Free()
}()
contract.Input = input
if debug {
defer func() { // this deferred method handles exit-with-error
if err == nil {
return
}
if !logged && evm.Config.Tracer.OnOpcode != nil {
evm.Config.Tracer.OnOpcode(pcCopy, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err))
}
if logged && evm.Config.Tracer.OnFault != nil {
evm.Config.Tracer.OnFault(pcCopy, byte(op), gasCopy, cost, callContext, evm.depth, VMErrorFromErr(err))
}
}()
}
// The Interpreter main run loop (contextual). This loop runs until either an
// explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during
// the execution of one of the operations or until the done flag is set by the
// parent context.
_ = jumpTable[0] // nil-check the jumpTable out of the loop
for {
if debug {
// Capture pre-execution values for tracing.
logged, pcCopy, gasCopy = false, pc, contract.Gas
}
if isEIP4762 && !contract.IsDeployment && !contract.IsSystemCall {
// if the PC ends up in a new "chunk" of verkleized code, charge the
// associated costs.
contractAddr := contract.Address()
consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas)
contract.UseGas(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if consumed < wanted {
return nil, ErrOutOfGas
}
}
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
op = contract.GetOp(pc)
operation := jumpTable[op]
cost = operation.constantGas // For tracing
// Validate stack
if sLen := stack.len(); sLen < operation.minStack {
return nil, &ErrStackUnderflow{stackLen: sLen, required: operation.minStack}
} else if sLen > operation.maxStack {
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
}
// for tracing: this gas consumption event is emitted below in the debug section.
if contract.Gas < cost {
return nil, ErrOutOfGas
} else {
contract.Gas -= cost
}
// All ops with a dynamic memory usage also has a dynamic gas cost.
var memorySize uint64
if operation.dynamicGas != nil {
// calculate the new memory size and expand the memory to fit
// the operation
// Memory check needs to be done prior to evaluating the dynamic gas portion,
// to detect calculation overflows
if operation.memorySize != nil {
memSize, overflow := operation.memorySize(stack)
if overflow {
return nil, ErrGasUintOverflow
}
// memory is expanded in words of 32 bytes. Gas
// is also calculated in words.
if memorySize, overflow = math.SafeMul(toWordSize(memSize), 32); overflow {
return nil, ErrGasUintOverflow
}
}
// Consume the gas and return an error if not enough gas is available.
// cost is explicitly set so that the capture state defer method can get the proper cost
var dynamicCost uint64
dynamicCost, err = operation.dynamicGas(evm, contract, stack, mem, memorySize)
cost += dynamicCost // for tracing
if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
}
// for tracing: this gas consumption event is emitted below in the debug section.
if contract.Gas < dynamicCost {
return nil, ErrOutOfGas
} else {
contract.Gas -= dynamicCost
}
}
// Do tracing before potential memory expansion
if debug {
if evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gasCopy, gasCopy-cost, tracing.GasChangeCallOpCode)
}
if evm.Config.Tracer.OnOpcode != nil {
evm.Config.Tracer.OnOpcode(pc, byte(op), gasCopy, cost, callContext, evm.returnData, evm.depth, VMErrorFromErr(err))
logged = true
}
}
if memorySize > 0 {
mem.Resize(memorySize)
}
// execute the operation
res, err = operation.execute(&pc, evm, callContext)
if err != nil {
break
}
pc++
}
if err == errStopToken {
err = nil // clear stop token error
}
return res, err
}

1
evmone Submodule

@ -0,0 +1 @@
Subproject commit 3f47614611cb63697d783cbaa5b07590b6bd5f77