mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-20 21:54:30 +00:00
## Why this should be merged
Fixes tracing when a stateful precompile calls another contract that
itself accesses storage.
## How this works
The pre-state tracer from `eth/tracers/native` doesn't implement
`CaptureEnter()` (entry of a new context), instead relying on
`CaptureState()` (per-opcode tracing) to detect that a new contract has
been entered. In doing so, it [maintains an
invariant](cb7eb89341/eth/tracers/native/prestate.go (L160))
that is expected when `CaptureState(vm.SLOAD, ...)` is called—breaking
the invariant results in a panic due to a nil map.
The fix involves (a) maintaining the invariant as part of
`CaptureEnter()` (previously a no-op); and (b) calling said method
inside `vm.PrecompileEnvironment.Call()`. The latter has the added
benefit of properly handling all tracing involving an outbound call from
precompiles.
## How this was tested
New integration test demonstrates that the tracer can log the retrieved
storage value.
---------
Co-authored-by: Darioush Jalali <darioush.jalali@avalabs.org>
201 lines
6.4 KiB
Go
201 lines
6.4 KiB
Go
// Copyright 2024 the libevm authors.
|
|
//
|
|
// The libevm additions to go-ethereum are free software: you can redistribute
|
|
// them and/or modify them under the terms of the GNU Lesser General Public License
|
|
// as published by the Free Software Foundation, either version 3 of the License,
|
|
// or (at your option) any later version.
|
|
//
|
|
// The libevm additions are distributed in the hope that they will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
|
|
// General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see
|
|
// <http://www.gnu.org/licenses/>.
|
|
|
|
package vm
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/holiman/uint256"
|
|
|
|
"github.com/ava-labs/libevm/common"
|
|
"github.com/ava-labs/libevm/core/types"
|
|
"github.com/ava-labs/libevm/libevm"
|
|
"github.com/ava-labs/libevm/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 call type. As
|
|
// {Delegate,Static}Call don't accept a value, they MAY set the respective field
|
|
// to nil as it will be ignored.
|
|
//
|
|
// Instantiation can be achieved by merely copying the parameter names, in
|
|
// order, which is trivially achieved with AST manipulation:
|
|
//
|
|
// func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) ... {
|
|
// ...
|
|
// args := &evmCallArgs{evm, staticCall, caller, addr, input, gas, nil /*value*/}
|
|
type evmCallArgs struct {
|
|
evm *EVM
|
|
callType CallType
|
|
|
|
// args:start
|
|
caller ContractRef
|
|
addr common.Address
|
|
input []byte
|
|
gas uint64
|
|
value *uint256.Int
|
|
// args:end
|
|
}
|
|
|
|
// A CallType refers to a *CALL* [OpCode] / respective method on [EVM].
|
|
type CallType OpCode
|
|
|
|
const (
|
|
Call = CallType(CALL)
|
|
CallCode = CallType(CALLCODE)
|
|
DelegateCall = CallType(DELEGATECALL)
|
|
StaticCall = CallType(STATICCALL)
|
|
)
|
|
|
|
func (t CallType) isValid() bool {
|
|
switch t {
|
|
case Call, CallCode, DelegateCall, StaticCall:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// String returns a human-readable representation of the CallType.
|
|
func (t CallType) String() string {
|
|
if t.isValid() {
|
|
return t.OpCode().String()
|
|
}
|
|
return fmt.Sprintf("Unknown %T(%d)", t, t)
|
|
}
|
|
|
|
// OpCode returns t's equivalent OpCode.
|
|
func (t CallType) OpCode() OpCode {
|
|
if t.isValid() {
|
|
return OpCode(t)
|
|
}
|
|
return INVALID
|
|
}
|
|
|
|
// run runs the [PrecompiledContract], differentiating between stateful and
|
|
// regular types.
|
|
func (args *evmCallArgs) run(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) {
|
|
if p, ok := p.(statefulPrecompile); ok {
|
|
return p(args.env(), input, suppliedGas)
|
|
}
|
|
// Gas consumption for regular precompiles was already handled by the native
|
|
// RunPrecompiledContract(), which called this method.
|
|
ret, err = p.Run(input)
|
|
return ret, suppliedGas, err
|
|
}
|
|
|
|
// PrecompiledStatefulContract is the stateful equivalent of a
|
|
// [PrecompiledContract].
|
|
type PrecompiledStatefulContract func(env PrecompileEnvironment, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err 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 PrecompiledStatefulContract) PrecompiledContract {
|
|
return statefulPrecompile(run)
|
|
}
|
|
|
|
// statefulPrecompile implements the [PrecompiledContract] interface to allow a
|
|
// [PrecompiledStatefulContract] to be carried with regular geth plumbing. The
|
|
// methods are defined on this unexported type instead of directly on
|
|
// [PrecompiledStatefulContract] to hide implementation details.
|
|
type statefulPrecompile PrecompiledStatefulContract
|
|
|
|
// RequiredGas always returns zero as this gas is consumed by native geth code
|
|
// before the contract is run.
|
|
func (statefulPrecompile) RequiredGas([]byte) uint64 { return 0 }
|
|
|
|
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 itself", p, p))
|
|
}
|
|
|
|
// A PrecompileEnvironment provides (a) information about the context in which a
|
|
// precompiled contract is being run; and (b) a means of calling other
|
|
// contracts.
|
|
type PrecompileEnvironment interface {
|
|
ChainConfig() *params.ChainConfig
|
|
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
|
|
IncomingCallType() CallType
|
|
|
|
BlockHeader() (types.Header, error)
|
|
BlockNumber() *big.Int
|
|
BlockTime() uint64
|
|
|
|
// Call is equivalent to [EVM.Call] except that the `caller` argument is
|
|
// removed and automatically determined according to the type of call that
|
|
// invoked the precompile.
|
|
Call(addr common.Address, input []byte, gas uint64, value *uint256.Int, _ ...CallOption) (ret []byte, gasRemaining uint64, _ error)
|
|
}
|
|
|
|
func (args *evmCallArgs) env() *environment {
|
|
var (
|
|
self common.Address
|
|
value = args.value
|
|
)
|
|
switch args.callType {
|
|
case StaticCall:
|
|
value = new(uint256.Int)
|
|
fallthrough
|
|
case Call:
|
|
self = args.addr
|
|
|
|
case DelegateCall:
|
|
value = nil
|
|
fallthrough
|
|
case CallCode:
|
|
self = args.caller.Address()
|
|
}
|
|
|
|
// This is equivalent to the `contract` variables created by evm.*Call*()
|
|
// methods, for non precompiles, to pass to [EVMInterpreter.Run].
|
|
contract := NewContract(args.caller, AccountRef(self), value, args.gas)
|
|
if args.callType == DelegateCall {
|
|
contract = contract.AsDelegate()
|
|
}
|
|
|
|
return &environment{
|
|
evm: args.evm,
|
|
self: contract,
|
|
callType: args.callType,
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
)
|