mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-03-28 22:02:55 +00:00
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.
409 lines
12 KiB
Go
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
|
|
}
|