diff --git a/core/vm/contracts.libevm.go b/core/vm/contracts.libevm.go index d56dbd1c396..19632eaf078 100644 --- a/core/vm/contracts.libevm.go +++ b/core/vm/contracts.libevm.go @@ -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. func (t CallType) String() string { if t.isValid() { @@ -120,15 +126,28 @@ func (t CallType) OpCode() OpCode { // run runs the [PrecompiledContract], differentiating between stateful and // regular types, updating `args.gasRemaining` in the stateful case. func (args *evmCallArgs) run(p PrecompiledContract, input []byte) (ret []byte, err error) { - switch p := p.(type) { - default: + sp, ok := p.(statefulPrecompile) + if !ok { 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 diff --git a/core/vm/environment.libevm.go b/core/vm/environment.libevm.go index edd6c4314b6..6551b21d0a7 100644 --- a/core/vm/environment.libevm.go +++ b/core/vm/environment.libevm.go @@ -63,19 +63,7 @@ func (e *environment) refundGas(add uint64) error { } func (e *environment) ReadOnly() bool { - // A switch statement provides clearer code coverage for difficult-to-test - // 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 - } + return e.evm.interpreter.readOnly } 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) { - // 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 if options.As[callConfig](opts...).unsafeCallerAddressProxying { // 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 } if !e.UseGas(gas) {