Skip to content

Commit

Permalink
Port call tracer with log to bsc
Browse files Browse the repository at this point in the history
Port ethereum/go-ethereum#25991 to bsc,
which adds `withLog` parameter to output event logs
  • Loading branch information
cannium committed Jan 3, 2023
1 parent b4773e8 commit 9777d11
Showing 1 changed file with 137 additions and 50 deletions.
187 changes: 137 additions & 50 deletions eth/tracers/native/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,38 +25,93 @@ import (
"sync/atomic"
"time"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
)

//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go

func init() {
register("callTracer", newCallTracer)
}

type callLog struct {
Address common.Address `json:"address"`
Topics []common.Hash `json:"topics"`
Data hexutil.Bytes `json:"data"`
}

type callFrame struct {
Type string `json:"type"`
From string `json:"from"`
To string `json:"to,omitempty"`
Value string `json:"value,omitempty"`
Gas string `json:"gas"`
GasUsed string `json:"gasUsed"`
Input string `json:"input"`
Output string `json:"output,omitempty"`
Error string `json:"error,omitempty"`
Calls []callFrame `json:"calls,omitempty"`
Type vm.OpCode `json:"-"`
From common.Address `json:"from"`
Gas uint64 `json:"gas"`
GasUsed uint64 `json:"gasUsed"`
To common.Address `json:"to,omitempty" rlp:"optional"`
Input []byte `json:"input" rlp:"optional"`
Output []byte `json:"output,omitempty" rlp:"optional"`
Error string `json:"error,omitempty" rlp:"optional"`
Revertal string `json:"revertReason,omitempty"`
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
// Placed at end on purpose. The RLP will be decoded to 0 instead of
// nil if there are non-empty elements after in the struct.
Value *big.Int `json:"value,omitempty" rlp:"optional"`
}

func (f callFrame) TypeString() string {
return f.Type.String()
}

func (f callFrame) failed() bool {
return len(f.Error) > 0
}

func (f *callFrame) processOutput(output []byte, err error) {
output = common.CopyBytes(output)
if err == nil {
f.Output = output
return
}
f.Error = err.Error()
if f.Type == vm.CREATE || f.Type == vm.CREATE2 {
f.To = common.Address{}
}
if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 {
return
}
f.Output = output
if len(output) < 4 {
return
}
if unpacked, err := abi.UnpackRevert(output); err == nil {
f.Revertal = unpacked
}
}

type callFrameMarshaling struct {
TypeString string `json:"type"`
Gas hexutil.Uint64
GasUsed hexutil.Uint64
Value *hexutil.Big
Input hexutil.Bytes
Output hexutil.Bytes
}

type callTracer struct {
env *vm.EVM
noopTracer
callstack []callFrame
config callTracerConfig
gasLimit uint64
interrupt uint32 // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
}

type callTracerConfig struct {
OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls
WithLog bool `json:"withLog"` // If true, call tracer will collect event logs
}

// newCallTracer returns a native go tracer which tracks
Expand All @@ -75,39 +130,58 @@ func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, e

// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
t.env = env
t.callstack[0] = callFrame{
Type: "CALL",
From: addrToHex(from),
To: addrToHex(to),
Input: bytesToHex(input),
Gas: uintToHex(gas),
Value: bigToHex(value),
Type: vm.CALL,
From: from,
To: to,
Input: common.CopyBytes(input),
Gas: gas,
Value: value,
}
if create {
t.callstack[0].Type = "CREATE"
t.callstack[0].Type = vm.CREATE
}
}

// CaptureEnd is called after the call finishes to finalize the tracing.
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
t.callstack[0].GasUsed = uintToHex(gasUsed)
if err != nil {
t.callstack[0].Error = err.Error()
if err.Error() == "execution reverted" && len(output) > 0 {
t.callstack[0].Output = bytesToHex(output)
}
} else {
t.callstack[0].Output = bytesToHex(output)
}
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, d time.Duration, err error) {
t.callstack[0].processOutput(output, err)
}

// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// Only logs need to be captured via opcode processing
if !t.config.WithLog {
return
}
// Avoid processing nested calls when only caring about top call
if t.config.OnlyTopCall && depth > 0 {
return
}
// Skip if tracing was interrupted
if atomic.LoadUint32(&t.interrupt) > 0 {
return
}
switch op {
case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4:
size := int(op - vm.LOG0)

stack := scope.Stack
stackData := stack.Data()

// Don't modify the stack
mStart := stackData[len(stackData)-1]
mSize := stackData[len(stackData)-2]
topics := make([]common.Hash, size)
for i := 0; i < size; i++ {
topic := stackData[len(stackData)-2-(i+1)]
topics[i] = common.Hash(topic.Bytes32())
}

// CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
data := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64()))
log := callLog{Address: scope.Contract.Address(), Topics: topics, Data: hexutil.Bytes(data)}
t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log)
}
}

// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
Expand All @@ -117,17 +191,16 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.
}
// Skip if tracing was interrupted
if atomic.LoadUint32(&t.interrupt) > 0 {
t.env.Cancel()
return
}

call := callFrame{
Type: typ.String(),
From: addrToHex(from),
To: addrToHex(to),
Input: bytesToHex(input),
Gas: uintToHex(gas),
Value: bigToHex(value),
Type: typ,
From: from,
To: to,
Input: common.CopyBytes(input),
Gas: gas,
Value: value,
}
t.callstack = append(t.callstack, call)
}
Expand All @@ -147,21 +220,22 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
t.callstack = t.callstack[:size-1]
size -= 1

call.GasUsed = uintToHex(gasUsed)
if err == nil {
call.Output = bytesToHex(output)
} else {
call.Error = err.Error()
if call.Type == "CREATE" || call.Type == "CREATE2" {
call.To = ""
}
}
call.GasUsed = gasUsed
call.processOutput(output, err)
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
}

func (*callTracer) CaptureTxStart(gasLimit uint64) {}
func (t *callTracer) CaptureTxStart(gasLimit uint64) {
t.gasLimit = gasLimit
}

func (*callTracer) CaptureTxEnd(restGas uint64) {}
func (t *callTracer) CaptureTxEnd(restGas uint64) {
t.callstack[0].GasUsed = t.gasLimit - restGas
if t.config.WithLog {
// Logs are not emitted when the call fails
clearFailedLogs(&t.callstack[0], false)
}
}

// GetResult returns the json-encoded nested list of call traces, and any
// error arising from the encoding or forceful termination (via `Stop`).
Expand All @@ -182,6 +256,19 @@ func (t *callTracer) Stop(err error) {
atomic.StoreUint32(&t.interrupt, 1)
}

// clearFailedLogs clears the logs of a callframe and all its children
// in case of execution failure.
func clearFailedLogs(cf *callFrame, parentFailed bool) {
failed := cf.failed() || parentFailed
// Clear own logs
if failed {
cf.Logs = nil
}
for i := range cf.Calls {
clearFailedLogs(&cf.Calls[i], failed)
}
}

func bytesToHex(s []byte) string {
return "0x" + common.Bytes2Hex(s)
}
Expand Down

0 comments on commit 9777d11

Please sign in to comment.