mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
Merge 73f4ec86e7 into 2a45272408
This commit is contained in:
commit
e8391b6850
12 changed files with 1268 additions and 178 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -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
3
.gitmodules
vendored
|
|
@ -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
|
||||
|
|
|
|||
50
build/ci.go
50
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)
|
||||
|
||||
|
|
|
|||
45
cmd/keeper/zisk.ld
Normal file
45
cmd/keeper/zisk.ld
Normal 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
329
core/vm/evmone.go
Normal 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
409
core/vm/evmone_host.go
Normal 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
|
||||
}
|
||||
58
core/vm/evmone_revision.go
Normal file
58
core/vm/evmone_revision.go
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
29
core/vm/interpreter_default.go
Normal file
29
core/vm/interpreter_default.go
Normal 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)
|
||||
}
|
||||
141
core/vm/interpreter_evmone.go
Normal file
141
core/vm/interpreter_evmone.go
Normal 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
199
core/vm/interpreter_run.go
Normal 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
1
evmone
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 3f47614611cb63697d783cbaa5b07590b6bd5f77
|
||||
Loading…
Reference in a new issue