go-ethereum/core/vm/interpreter_evmone.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

141 lines
3.9 KiB
Go

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