diff --git a/.gitignore b/.gitignore index 293359a669..4ea6c10200 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,7 @@ cmd/geth/geth cmd/rlpdump/rlpdump cmd/workload/workload cmd/keeper/keeper + +geth +cmd/keeper/keeper-zisk +keeper-zisk.elf diff --git a/.gitmodules b/.gitmodules index 241c169c47..d8e221b782 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/build/ci.go b/build/ci.go index abb7c4997f..ac81b2d499 100644 --- a/build/ci.go +++ b/build/ci.go @@ -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) diff --git a/cmd/keeper/zisk.ld b/cmd/keeper/zisk.ld new file mode 100644 index 0000000000..513855e3e9 --- /dev/null +++ b/cmd/keeper/zisk.ld @@ -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 = .; +} diff --git a/core/vm/evmone.go b/core/vm/evmone.go new file mode 100644 index 0000000000..7206df8776 --- /dev/null +++ b/core/vm/evmone.go @@ -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 +#include +#include + +// 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, + } +} diff --git a/core/vm/evmone_host.go b/core/vm/evmone_host.go new file mode 100644 index 0000000000..cd2b491b46 --- /dev/null +++ b/core/vm/evmone_host.go @@ -0,0 +1,409 @@ +//go:build evmone && cgo + +package vm + +/* +#include +#include +#include + +// 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 +} diff --git a/core/vm/evmone_revision.go b/core/vm/evmone_revision.go new file mode 100644 index 0000000000..04287cf651 --- /dev/null +++ b/core/vm/evmone_revision.go @@ -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 + } +} diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 52dbe83d86..2b37144736 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -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 -} diff --git a/core/vm/interpreter_default.go b/core/vm/interpreter_default.go new file mode 100644 index 0000000000..504eeabd94 --- /dev/null +++ b/core/vm/interpreter_default.go @@ -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 . + +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) +} diff --git a/core/vm/interpreter_evmone.go b/core/vm/interpreter_evmone.go new file mode 100644 index 0000000000..6ff5d691bf --- /dev/null +++ b/core/vm/interpreter_evmone.go @@ -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") + } +} diff --git a/core/vm/interpreter_run.go b/core/vm/interpreter_run.go new file mode 100644 index 0000000000..2931065d27 --- /dev/null +++ b/core/vm/interpreter_run.go @@ -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 . + +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 +} diff --git a/evmone b/evmone new file mode 160000 index 0000000000..3f47614611 --- /dev/null +++ b/evmone @@ -0,0 +1 @@ +Subproject commit 3f47614611cb63697d783cbaa5b07590b6bd5f77