@@ -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+
3337func 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+
3747type 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
50103type 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
58112type 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.
77132func (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.
106152func (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+
185272func bytesToHex (s []byte ) string {
186273 return "0x" + common .Bytes2Hex (s )
187274}
0 commit comments