Skip to content

Commit

Permalink
refactor: vm.PrecompileEnvironment.Call() tracer integration
Browse files Browse the repository at this point in the history
  • Loading branch information
ARR4N committed Oct 28, 2024
1 parent 7fe6b5e commit 585c1f3
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 32 deletions.
39 changes: 24 additions & 15 deletions core/vm/contracts.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,40 @@ type evmCallArgs struct {
}

// A CallType refers to a *CALL* [OpCode] / respective method on [EVM].
type CallType uint8
type CallType OpCode

const (
UnknownCallType CallType = iota
Call
CallCode
DelegateCall
StaticCall
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 {
switch t {
case Call:
return "Call"
case CallCode:
return "CallCode"
case DelegateCall:
return "DelegateCall"
case StaticCall:
return "StaticCall"
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) {
Expand Down
31 changes: 18 additions & 13 deletions core/vm/environment.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (e *environment) Call(addr common.Address, input []byte, gas uint64, value
return e.callContract(Call, addr, input, gas, value, opts...)
}

func (e *environment) callContract(typ CallType, addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) ([]byte, uint64, error) {
func (e *environment) callContract(typ CallType, addr common.Address, input []byte, gas uint64, value *uint256.Int, opts ...CallOption) (retData []byte, retGas uint64, 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.
Expand Down Expand Up @@ -122,20 +122,25 @@ func (e *environment) callContract(typ CallType, addr common.Address, input []by
}
}

switch typ {
case Call:
if in.readOnly && !value.IsZero() {
return nil, gas, ErrWriteProtection
}
if e.evm.Config.Tracer != nil {
e.evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value.ToBig())
if in.readOnly && value != nil && !value.IsZero() {
return nil, gas, ErrWriteProtection
}
if t := e.evm.Config.Tracer; t != nil {
var bigVal *big.Int
if value != nil {
bigVal = value.ToBig()
}
t.CaptureEnter(typ.OpCode(), caller.Address(), addr, input, gas, bigVal)

startGas := gas
ret, gas, err := e.evm.Call(caller, addr, input, gas, value)
if e.evm.Config.Tracer != nil {
e.evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err)
}
return ret, gas, err
defer func() {
t.CaptureEnd(retData, startGas-retGas, retErr)
}()
}

switch typ {
case Call:
return e.evm.Call(caller, addr, input, gas, value)
case CallCode, DelegateCall, StaticCall:
// TODO(arr4n): these cases should be very similar to CALL, hence the
// early abstraction, to signal to future maintainers. If implementing
Expand Down
4 changes: 0 additions & 4 deletions eth/tracers/native/prestate.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Trace
}, nil
}

func (t *prestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
t.lookupAccount(to)
}

// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.env = env
Expand Down
36 changes: 36 additions & 0 deletions eth/tracers/native/prestate_libevm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// 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 native

import (
"math/big"

"github.com/ava-labs/libevm/common"
"github.com/ava-labs/libevm/core/vm"
)

// CaptureEnter implements the [vm.EVMLogger] hook for entering a new scope (via
// call, create or selfdestruct).
func (t *prestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Although [prestateTracer.lookupStorage] expects
// [prestateTracer.lookupAccount] to have been called, the invariant is
// maintained by [prestateTracer.CaptureState] when encountering an opcode
// corresponding to scope entry. This, however, doesn't work when using a
// call method exposed by [vm.PrecompileEnvironment], so we expose it here
// as it is idempotent.
t.lookupAccount(to)
}

0 comments on commit 585c1f3

Please sign in to comment.