mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-04-04 09:05:57 +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.
141 lines
3.9 KiB
Go
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")
|
|
}
|
|
}
|