mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-20 13:44:31 +00:00
* feat: read-only argument to stateful precompiles
* refactor: group `PrecompiledStatefulRun()` args except input
* test: read-only status of all `EVM.*Call*()` methods
* feat: `PrecompileEnvironment.ReadOnlyStateDB()`
* doc: warning about allowing recursive calling
* test: precompile call from within read-only env
* refactor: introduce `{inherit,force}ReadOnly` consts
* fix: `nolint` verbose if statement
166 lines
5.2 KiB
Go
166 lines
5.2 KiB
Go
package vm
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/holiman/uint256"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/libevm"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
// evmCallArgs mirrors the parameters of the [EVM] methods Call(), CallCode(),
|
|
// DelegateCall() and StaticCall(). Its fields are identical to those of the
|
|
// parameters, prepended with the receiver name and appended with additional
|
|
// values. As {Delegate,Static}Call don't accept a value, they MUST set the
|
|
// respective field to nil.
|
|
//
|
|
// Instantiation can be achieved by merely copying the parameter names, in
|
|
// order, which is trivially achieved with AST manipulation:
|
|
//
|
|
// func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) ... {
|
|
// ...
|
|
// args := &evmCallArgs{evm, caller, addr, input, gas, value, false}
|
|
type evmCallArgs struct {
|
|
evm *EVM
|
|
// args:start
|
|
caller ContractRef
|
|
addr common.Address
|
|
input []byte
|
|
gas uint64
|
|
value *uint256.Int
|
|
// args:end
|
|
|
|
// evm.interpreter.readOnly is only set to true via a call to
|
|
// EVMInterpreter.Run() so, if a precompile is called directly with
|
|
// StaticCall(), then readOnly might not be set yet. StaticCall() MUST set
|
|
// this to forceReadOnly and all other methods MUST set it to
|
|
// inheritReadOnly; i.e. equivalent to the boolean they each pass to
|
|
// EVMInterpreter.Run().
|
|
readWrite rwInheritance
|
|
}
|
|
|
|
type rwInheritance uint8
|
|
|
|
const (
|
|
inheritReadOnly rwInheritance = iota + 1
|
|
forceReadOnly
|
|
)
|
|
|
|
// run runs the [PrecompiledContract], differentiating between stateful and
|
|
// regular types.
|
|
func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) {
|
|
if p, ok := p.(statefulPrecompile); ok {
|
|
return p.run(args, input)
|
|
}
|
|
return p.Run(input)
|
|
}
|
|
|
|
// PrecompiledStatefulRun is the stateful equivalent of the Run() method of a
|
|
// [PrecompiledContract].
|
|
type PrecompiledStatefulRun func(env PrecompileEnvironment, input []byte) ([]byte, error)
|
|
|
|
// NewStatefulPrecompile constructs a new PrecompiledContract that can be used
|
|
// via an [EVM] instance but MUST NOT be called directly; a direct call to Run()
|
|
// reserves the right to panic. See other requirements defined in the comments
|
|
// on [PrecompiledContract].
|
|
func NewStatefulPrecompile(run PrecompiledStatefulRun, requiredGas func([]byte) uint64) PrecompiledContract {
|
|
return statefulPrecompile{
|
|
gas: requiredGas,
|
|
run: run,
|
|
}
|
|
}
|
|
|
|
type statefulPrecompile struct {
|
|
gas func([]byte) uint64
|
|
run PrecompiledStatefulRun
|
|
}
|
|
|
|
func (p statefulPrecompile) RequiredGas(input []byte) uint64 {
|
|
return p.gas(input)
|
|
}
|
|
|
|
func (p statefulPrecompile) Run([]byte) ([]byte, error) {
|
|
// https://google.github.io/styleguide/go/best-practices.html#when-to-panic
|
|
// This would indicate an API misuse and would occur in tests, not in
|
|
// production.
|
|
panic(fmt.Sprintf("BUG: call to %T.Run(); MUST call %T", p, p.run))
|
|
}
|
|
|
|
// A PrecompileEnvironment provides information about the context in which a
|
|
// precompiled contract is being run.
|
|
type PrecompileEnvironment interface {
|
|
Rules() params.Rules
|
|
ReadOnly() bool
|
|
// StateDB will be non-nil i.f.f !ReadOnly().
|
|
StateDB() StateDB
|
|
// ReadOnlyState will always be non-nil.
|
|
ReadOnlyState() libevm.StateReader
|
|
Addresses() *libevm.AddressContext
|
|
}
|
|
|
|
//
|
|
// ****** SECURITY ******
|
|
//
|
|
// If you are updating PrecompileEnvironment to provide the ability to call back
|
|
// into another contract, you MUST revisit the evmCallArgs.forceReadOnly flag.
|
|
//
|
|
// It is possible that forceReadOnly is true but evm.interpreter.readOnly is
|
|
// false. This is safe for now, but may not be if recursive calling *from* a
|
|
// precompile is enabled.
|
|
//
|
|
// ****** SECURITY ******
|
|
|
|
var _ PrecompileEnvironment = (*evmCallArgs)(nil)
|
|
|
|
func (args *evmCallArgs) Rules() params.Rules { return args.evm.chainRules }
|
|
|
|
func (args *evmCallArgs) ReadOnly() bool {
|
|
if args.readWrite == inheritReadOnly {
|
|
if args.evm.interpreter.readOnly { //nolint:gosimple // Clearer code coverage for difficult-to-test branch
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
// Even though args.readWrite may be some value other than forceReadOnly,
|
|
// that would be an invalid use of the API so we default to read-only as the
|
|
// safest failure mode.
|
|
return true
|
|
}
|
|
|
|
func (args *evmCallArgs) StateDB() StateDB {
|
|
if args.ReadOnly() {
|
|
return nil
|
|
}
|
|
return args.evm.StateDB
|
|
}
|
|
|
|
func (args *evmCallArgs) ReadOnlyState() libevm.StateReader {
|
|
// Even though we're actually returning a full state database, the user
|
|
// would have to actively circumvent the returned interface to use it. At
|
|
// that point they're off-piste and it's not our problem.
|
|
return args.evm.StateDB
|
|
}
|
|
|
|
func (args *evmCallArgs) Addresses() *libevm.AddressContext {
|
|
return &libevm.AddressContext{
|
|
Origin: args.evm.TxContext.Origin,
|
|
Caller: args.caller.Address(),
|
|
Self: args.addr,
|
|
}
|
|
}
|
|
|
|
var (
|
|
// These lock in the assumptions made when implementing [evmCallArgs]. If
|
|
// these break then the struct fields SHOULD be changed to match these
|
|
// signatures.
|
|
_ = [](func(ContractRef, common.Address, []byte, uint64, *uint256.Int) ([]byte, uint64, error)){
|
|
(*EVM)(nil).Call,
|
|
(*EVM)(nil).CallCode,
|
|
}
|
|
_ = [](func(ContractRef, common.Address, []byte, uint64) ([]byte, uint64, error)){
|
|
(*EVM)(nil).DelegateCall,
|
|
(*EVM)(nil).StaticCall,
|
|
}
|
|
)
|