go-ethereum/core/vm/evmone_host.go
Khâny 73f4ec86e7 core, build: reintroduce evmone
Reintroduce evmone as an EVM interpreter option behind the 'evmone' build tag.
Includes gas refund propagation from evmone to Go StateDB, and C memory
allocation for all parameters passed across the CGO boundary.
2026-02-13 20:44:30 +01:00

409 lines
12 KiB
Go

//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
}