mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 10:50:44 +00:00
refactor: set EVM.readOnly and depth before running stateful precompile (#210)
## Why this should be merged Improved clarity. ## How this works Semantically equivalent refactor. These values aren't accessible / needed during execution of a stateful precompile, but are important when making outgoing calls so were originally only set in the `PrecompileEnvironment.Call()` implementation. This was confusing and led to a false-positive bug report. ## How this was tested Existing tests.
This commit is contained in:
parent
e7035f19ee
commit
62dc2ea087
2 changed files with 28 additions and 34 deletions
|
|
@ -101,6 +101,12 @@ func (t CallType) isValid() bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readOnly returns whether the CallType induces a read-only state if not
|
||||||
|
// already in one.
|
||||||
|
func (t CallType) readOnly() bool {
|
||||||
|
return t == StaticCall
|
||||||
|
}
|
||||||
|
|
||||||
// String returns a human-readable representation of the CallType.
|
// String returns a human-readable representation of the CallType.
|
||||||
func (t CallType) String() string {
|
func (t CallType) String() string {
|
||||||
if t.isValid() {
|
if t.isValid() {
|
||||||
|
|
@ -120,15 +126,28 @@ func (t CallType) OpCode() OpCode {
|
||||||
// run runs the [PrecompiledContract], differentiating between stateful and
|
// run runs the [PrecompiledContract], differentiating between stateful and
|
||||||
// regular types, updating `args.gasRemaining` in the stateful case.
|
// regular types, updating `args.gasRemaining` in the stateful case.
|
||||||
func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) {
|
func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) {
|
||||||
switch p := p.(type) {
|
sp, ok := p.(statefulPrecompile)
|
||||||
default:
|
if !ok {
|
||||||
return p.Run(input)
|
return p.Run(input)
|
||||||
case statefulPrecompile:
|
|
||||||
env := args.env()
|
|
||||||
ret, err := p(env, input)
|
|
||||||
args.gasRemaining = env.Gas()
|
|
||||||
return ret, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
env := args.env()
|
||||||
|
// Depth and read-only setting are handled by [EVMInterpreter.Run],
|
||||||
|
// which isn't used for precompiles, so we need to do it ourselves to
|
||||||
|
// maintain the expected invariants.
|
||||||
|
in := env.evm.interpreter
|
||||||
|
|
||||||
|
in.evm.depth++
|
||||||
|
defer func() { in.evm.depth-- }()
|
||||||
|
|
||||||
|
if env.callType.readOnly() && !in.readOnly {
|
||||||
|
in.readOnly = true
|
||||||
|
defer func() { in.readOnly = false }()
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err = sp(env, input)
|
||||||
|
args.gasRemaining = env.Gas()
|
||||||
|
return ret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrecompiledStatefulContract is the stateful equivalent of a
|
// PrecompiledStatefulContract is the stateful equivalent of a
|
||||||
|
|
|
||||||
|
|
@ -63,19 +63,7 @@ func (e *environment) refundGas(add uint64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *environment) ReadOnly() bool {
|
func (e *environment) ReadOnly() bool {
|
||||||
// A switch statement provides clearer code coverage for difficult-to-test
|
return e.evm.interpreter.readOnly
|
||||||
// cases.
|
|
||||||
switch {
|
|
||||||
case e.callType == StaticCall:
|
|
||||||
// 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.
|
|
||||||
return true
|
|
||||||
case e.evm.interpreter.readOnly:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *environment) Addresses() *libevm.AddressContext {
|
func (e *environment) Addresses() *libevm.AddressContext {
|
||||||
|
|
@ -116,19 +104,6 @@ func (e *environment) Call(addr common.Address, input []byte, gas uint64, value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *environment) callContract(typ CallType, addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) (retData []byte, retErr error) {
|
func (e *environment) callContract(typ CallType, addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) (retData []byte, retErr error) {
|
||||||
// Depth and read-only setting are handled by [EVMInterpreter.Run], which
|
|
||||||
// isn't used for precompiles, so we need to do it ourselves to maintain the
|
|
||||||
// expected invariants.
|
|
||||||
in := e.evm.interpreter
|
|
||||||
|
|
||||||
in.evm.depth++
|
|
||||||
defer func() { in.evm.depth-- }()
|
|
||||||
|
|
||||||
if e.ReadOnly() && !in.readOnly { // i.e. the precompile was StaticCall()ed
|
|
||||||
in.readOnly = true
|
|
||||||
defer func() { in.readOnly = false }()
|
|
||||||
}
|
|
||||||
|
|
||||||
var caller ContractRef = e.self
|
var caller ContractRef = e.self
|
||||||
if options.As[callConfig](opts...).unsafeCallerAddressProxying {
|
if options.As[callConfig](opts...).unsafeCallerAddressProxying {
|
||||||
// Note that, in addition to being unsafe, this breaks an EVM
|
// Note that, in addition to being unsafe, this breaks an EVM
|
||||||
|
|
@ -141,7 +116,7 @@ func (e *environment) callContract(typ CallType, addr common.Address, input []by
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.readOnly && value != nil && !value.IsZero() {
|
if e.ReadOnly() && value != nil && !value.IsZero() {
|
||||||
return nil, ErrWriteProtection
|
return nil, ErrWriteProtection
|
||||||
}
|
}
|
||||||
if !e.UseGas(gas) {
|
if !e.UseGas(gas) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue