Skip to content

Commit 9777d11

Browse files
committed
Port call tracer with log to bsc
Port ethereum/go-ethereum#25991 to bsc, which adds `withLog` parameter to output event logs
1 parent b4773e8 commit 9777d11

1 file changed

Lines changed: 137 additions & 50 deletions

File tree

eth/tracers/native/call.go

Lines changed: 137 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -25,38 +25,93 @@ import (
2525
"sync/atomic"
2626
"time"
2727

28+
"github.com/ethereum/go-ethereum/accounts/abi"
2829
"github.com/ethereum/go-ethereum/common"
30+
"github.com/ethereum/go-ethereum/common/hexutil"
2931
"github.com/ethereum/go-ethereum/core/vm"
3032
"github.com/ethereum/go-ethereum/eth/tracers"
3133
)
3234

35+
//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go
36+
3337
func init() {
3438
register("callTracer", newCallTracer)
3539
}
3640

41+
type callLog struct {
42+
Address common.Address `json:"address"`
43+
Topics []common.Hash `json:"topics"`
44+
Data hexutil.Bytes `json:"data"`
45+
}
46+
3747
type callFrame struct {
38-
Type string `json:"type"`
39-
From string `json:"from"`
40-
To string `json:"to,omitempty"`
41-
Value string `json:"value,omitempty"`
42-
Gas string `json:"gas"`
43-
GasUsed string `json:"gasUsed"`
44-
Input string `json:"input"`
45-
Output string `json:"output,omitempty"`
46-
Error string `json:"error,omitempty"`
47-
Calls []callFrame `json:"calls,omitempty"`
48+
Type vm.OpCode `json:"-"`
49+
From common.Address `json:"from"`
50+
Gas uint64 `json:"gas"`
51+
GasUsed uint64 `json:"gasUsed"`
52+
To common.Address `json:"to,omitempty" rlp:"optional"`
53+
Input []byte `json:"input" rlp:"optional"`
54+
Output []byte `json:"output,omitempty" rlp:"optional"`
55+
Error string `json:"error,omitempty" rlp:"optional"`
56+
Revertal string `json:"revertReason,omitempty"`
57+
Calls []callFrame `json:"calls,omitempty" rlp:"optional"`
58+
Logs []callLog `json:"logs,omitempty" rlp:"optional"`
59+
// Placed at end on purpose. The RLP will be decoded to 0 instead of
60+
// nil if there are non-empty elements after in the struct.
61+
Value *big.Int `json:"value,omitempty" rlp:"optional"`
62+
}
63+
64+
func (f callFrame) TypeString() string {
65+
return f.Type.String()
66+
}
67+
68+
func (f callFrame) failed() bool {
69+
return len(f.Error) > 0
70+
}
71+
72+
func (f *callFrame) processOutput(output []byte, err error) {
73+
output = common.CopyBytes(output)
74+
if err == nil {
75+
f.Output = output
76+
return
77+
}
78+
f.Error = err.Error()
79+
if f.Type == vm.CREATE || f.Type == vm.CREATE2 {
80+
f.To = common.Address{}
81+
}
82+
if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 {
83+
return
84+
}
85+
f.Output = output
86+
if len(output) < 4 {
87+
return
88+
}
89+
if unpacked, err := abi.UnpackRevert(output); err == nil {
90+
f.Revertal = unpacked
91+
}
92+
}
93+
94+
type callFrameMarshaling struct {
95+
TypeString string `json:"type"`
96+
Gas hexutil.Uint64
97+
GasUsed hexutil.Uint64
98+
Value *hexutil.Big
99+
Input hexutil.Bytes
100+
Output hexutil.Bytes
48101
}
49102

50103
type callTracer struct {
51-
env *vm.EVM
104+
noopTracer
52105
callstack []callFrame
53106
config callTracerConfig
107+
gasLimit uint64
54108
interrupt uint32 // Atomic flag to signal execution interruption
55109
reason error // Textual reason for the interruption
56110
}
57111

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

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

76131
// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
77132
func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
78-
t.env = env
79133
t.callstack[0] = callFrame{
80-
Type: "CALL",
81-
From: addrToHex(from),
82-
To: addrToHex(to),
83-
Input: bytesToHex(input),
84-
Gas: uintToHex(gas),
85-
Value: bigToHex(value),
134+
Type: vm.CALL,
135+
From: from,
136+
To: to,
137+
Input: common.CopyBytes(input),
138+
Gas: gas,
139+
Value: value,
86140
}
87141
if create {
88-
t.callstack[0].Type = "CREATE"
142+
t.callstack[0].Type = vm.CREATE
89143
}
90144
}
91145

92146
// CaptureEnd is called after the call finishes to finalize the tracing.
93-
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
94-
t.callstack[0].GasUsed = uintToHex(gasUsed)
95-
if err != nil {
96-
t.callstack[0].Error = err.Error()
97-
if err.Error() == "execution reverted" && len(output) > 0 {
98-
t.callstack[0].Output = bytesToHex(output)
99-
}
100-
} else {
101-
t.callstack[0].Output = bytesToHex(output)
102-
}
147+
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, d time.Duration, err error) {
148+
t.callstack[0].processOutput(output, err)
103149
}
104150

105151
// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
106152
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
107-
}
153+
// Only logs need to be captured via opcode processing
154+
if !t.config.WithLog {
155+
return
156+
}
157+
// Avoid processing nested calls when only caring about top call
158+
if t.config.OnlyTopCall && depth > 0 {
159+
return
160+
}
161+
// Skip if tracing was interrupted
162+
if atomic.LoadUint32(&t.interrupt) > 0 {
163+
return
164+
}
165+
switch op {
166+
case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4:
167+
size := int(op - vm.LOG0)
168+
169+
stack := scope.Stack
170+
stackData := stack.Data()
171+
172+
// Don't modify the stack
173+
mStart := stackData[len(stackData)-1]
174+
mSize := stackData[len(stackData)-2]
175+
topics := make([]common.Hash, size)
176+
for i := 0; i < size; i++ {
177+
topic := stackData[len(stackData)-2-(i+1)]
178+
topics[i] = common.Hash(topic.Bytes32())
179+
}
108180

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

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

124197
call := callFrame{
125-
Type: typ.String(),
126-
From: addrToHex(from),
127-
To: addrToHex(to),
128-
Input: bytesToHex(input),
129-
Gas: uintToHex(gas),
130-
Value: bigToHex(value),
198+
Type: typ,
199+
From: from,
200+
To: to,
201+
Input: common.CopyBytes(input),
202+
Gas: gas,
203+
Value: value,
131204
}
132205
t.callstack = append(t.callstack, call)
133206
}
@@ -147,21 +220,22 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
147220
t.callstack = t.callstack[:size-1]
148221
size -= 1
149222

150-
call.GasUsed = uintToHex(gasUsed)
151-
if err == nil {
152-
call.Output = bytesToHex(output)
153-
} else {
154-
call.Error = err.Error()
155-
if call.Type == "CREATE" || call.Type == "CREATE2" {
156-
call.To = ""
157-
}
158-
}
223+
call.GasUsed = gasUsed
224+
call.processOutput(output, err)
159225
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
160226
}
161227

162-
func (*callTracer) CaptureTxStart(gasLimit uint64) {}
228+
func (t *callTracer) CaptureTxStart(gasLimit uint64) {
229+
t.gasLimit = gasLimit
230+
}
163231

164-
func (*callTracer) CaptureTxEnd(restGas uint64) {}
232+
func (t *callTracer) CaptureTxEnd(restGas uint64) {
233+
t.callstack[0].GasUsed = t.gasLimit - restGas
234+
if t.config.WithLog {
235+
// Logs are not emitted when the call fails
236+
clearFailedLogs(&t.callstack[0], false)
237+
}
238+
}
165239

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

259+
// clearFailedLogs clears the logs of a callframe and all its children
260+
// in case of execution failure.
261+
func clearFailedLogs(cf *callFrame, parentFailed bool) {
262+
failed := cf.failed() || parentFailed
263+
// Clear own logs
264+
if failed {
265+
cf.Logs = nil
266+
}
267+
for i := range cf.Calls {
268+
clearFailedLogs(&cf.Calls[i], failed)
269+
}
270+
}
271+
185272
func bytesToHex(s []byte) string {
186273
return "0x" + common.Bytes2Hex(s)
187274
}

0 commit comments

Comments
 (0)