github.com/tacshi/go-ethereum@v0.0.0-20230616113857-84a434e20921/eth/tracers/native/call.go (about) 1 // Copyright 2021 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package native 18 19 import ( 20 "encoding/json" 21 "errors" 22 "math/big" 23 "sync/atomic" 24 25 "github.com/tacshi/go-ethereum/accounts/abi" 26 "github.com/tacshi/go-ethereum/common" 27 "github.com/tacshi/go-ethereum/common/hexutil" 28 "github.com/tacshi/go-ethereum/core/vm" 29 "github.com/tacshi/go-ethereum/eth/tracers" 30 ) 31 32 //go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go 33 34 func init() { 35 tracers.DefaultDirectory.Register("callTracer", newCallTracer, false) 36 } 37 38 type callLog struct { 39 Address common.Address `json:"address"` 40 Topics []common.Hash `json:"topics"` 41 Data hexutil.Bytes `json:"data"` 42 } 43 44 type callFrame struct { 45 // Arbitrum: we add these here due to the tracer returning the top frame 46 BeforeEVMTransfers *[]arbitrumTransfer `json:"beforeEVMTransfers,omitempty"` 47 AfterEVMTransfers *[]arbitrumTransfer `json:"afterEVMTransfers,omitempty"` 48 49 Type vm.OpCode `json:"-"` 50 From common.Address `json:"from"` 51 Gas uint64 `json:"gas"` 52 GasUsed uint64 `json:"gasUsed"` 53 To *common.Address `json:"to,omitempty" rlp:"optional"` 54 Input []byte `json:"input" rlp:"optional"` 55 Output []byte `json:"output,omitempty" rlp:"optional"` 56 Error string `json:"error,omitempty" rlp:"optional"` 57 RevertReason string `json:"revertReason,omitempty"` 58 Calls []callFrame `json:"calls,omitempty" rlp:"optional"` 59 Logs []callLog `json:"logs,omitempty" rlp:"optional"` 60 61 // Placed at end on purpose. The RLP will be decoded to 0 instead of 62 // nil if there are non-empty elements after in the struct. 63 Value *big.Int `json:"value,omitempty" rlp:"optional"` 64 } 65 66 func (f callFrame) TypeString() string { 67 return f.Type.String() 68 } 69 70 func (f callFrame) failed() bool { 71 return len(f.Error) > 0 72 } 73 74 func (f *callFrame) processOutput(output []byte, err error) { 75 output = common.CopyBytes(output) 76 if err == nil { 77 f.Output = output 78 return 79 } 80 f.Error = err.Error() 81 if f.Type == vm.CREATE || f.Type == vm.CREATE2 { 82 f.To = nil 83 } 84 if !errors.Is(err, vm.ErrExecutionReverted) || len(output) == 0 { 85 return 86 } 87 f.Output = output 88 if len(output) < 4 { 89 return 90 } 91 if unpacked, err := abi.UnpackRevert(output); err == nil { 92 f.RevertReason = unpacked 93 } 94 } 95 96 type callFrameMarshaling struct { 97 TypeString string `json:"type"` 98 Gas hexutil.Uint64 99 GasUsed hexutil.Uint64 100 Value *hexutil.Big 101 Input hexutil.Bytes 102 Output hexutil.Bytes 103 } 104 105 type callTracer struct { 106 // Arbitrum: capture transfers occurring outside of evm execution 107 beforeEVMTransfers []arbitrumTransfer 108 afterEVMTransfers []arbitrumTransfer 109 110 noopTracer 111 callstack []callFrame 112 config callTracerConfig 113 gasLimit uint64 114 interrupt uint32 // Atomic flag to signal execution interruption 115 reason error // Textual reason for the interruption 116 } 117 118 type callTracerConfig struct { 119 OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls 120 WithLog bool `json:"withLog"` // If true, call tracer will collect event logs 121 } 122 123 // newCallTracer returns a native go tracer which tracks 124 // call frames of a tx, and implements vm.EVMLogger. 125 func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) { 126 var config callTracerConfig 127 if cfg != nil { 128 if err := json.Unmarshal(cfg, &config); err != nil { 129 return nil, err 130 } 131 } 132 // First callframe contains tx context info 133 // and is populated on start and end. 134 return &callTracer{ 135 callstack: make([]callFrame, 1), 136 config: config, 137 beforeEVMTransfers: []arbitrumTransfer{}, 138 afterEVMTransfers: []arbitrumTransfer{}, 139 }, nil 140 } 141 142 // CaptureStart implements the EVMLogger interface to initialize the tracing operation. 143 func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { 144 toCopy := to 145 t.callstack[0] = callFrame{ 146 Type: vm.CALL, 147 From: from, 148 To: &toCopy, 149 Input: common.CopyBytes(input), 150 Gas: gas, 151 Value: value, 152 } 153 if create { 154 t.callstack[0].Type = vm.CREATE 155 } 156 } 157 158 // CaptureEnd is called after the call finishes to finalize the tracing. 159 func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { 160 t.callstack[0].processOutput(output, err) 161 } 162 163 // CaptureState implements the EVMLogger interface to trace a single step of VM execution. 164 func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { 165 // Only logs need to be captured via opcode processing 166 if !t.config.WithLog { 167 return 168 } 169 // Avoid processing nested calls when only caring about top call 170 if t.config.OnlyTopCall && depth > 0 { 171 return 172 } 173 // Skip if tracing was interrupted 174 if atomic.LoadUint32(&t.interrupt) > 0 { 175 return 176 } 177 switch op { 178 case vm.LOG0, vm.LOG1, vm.LOG2, vm.LOG3, vm.LOG4: 179 size := int(op - vm.LOG0) 180 181 stack := scope.Stack 182 stackData := stack.Data() 183 184 // Don't modify the stack 185 mStart := stackData[len(stackData)-1] 186 mSize := stackData[len(stackData)-2] 187 topics := make([]common.Hash, size) 188 for i := 0; i < size; i++ { 189 topic := stackData[len(stackData)-2-(i+1)] 190 topics[i] = common.Hash(topic.Bytes32()) 191 } 192 193 data := scope.Memory.GetCopy(int64(mStart.Uint64()), int64(mSize.Uint64())) 194 log := callLog{Address: scope.Contract.Address(), Topics: topics, Data: hexutil.Bytes(data)} 195 t.callstack[len(t.callstack)-1].Logs = append(t.callstack[len(t.callstack)-1].Logs, log) 196 } 197 } 198 199 // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). 200 func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 201 if t.config.OnlyTopCall { 202 return 203 } 204 // Skip if tracing was interrupted 205 if atomic.LoadUint32(&t.interrupt) > 0 { 206 return 207 } 208 209 toCopy := to 210 call := callFrame{ 211 Type: typ, 212 From: from, 213 To: &toCopy, 214 Input: common.CopyBytes(input), 215 Gas: gas, 216 Value: value, 217 } 218 t.callstack = append(t.callstack, call) 219 } 220 221 // CaptureExit is called when EVM exits a scope, even if the scope didn't 222 // execute any code. 223 func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { 224 if t.config.OnlyTopCall { 225 return 226 } 227 size := len(t.callstack) 228 if size <= 1 { 229 return 230 } 231 // pop call 232 call := t.callstack[size-1] 233 t.callstack = t.callstack[:size-1] 234 size -= 1 235 236 call.GasUsed = gasUsed 237 call.processOutput(output, err) 238 t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) 239 } 240 241 func (t *callTracer) CaptureTxStart(gasLimit uint64) { 242 t.gasLimit = gasLimit 243 } 244 245 func (t *callTracer) CaptureTxEnd(restGas uint64) { 246 t.callstack[0].GasUsed = t.gasLimit - restGas 247 if t.config.WithLog { 248 // Logs are not emitted when the call fails 249 clearFailedLogs(&t.callstack[0], false) 250 } 251 } 252 253 // GetResult returns the json-encoded nested list of call traces, and any 254 // error arising from the encoding or forceful termination (via `Stop`). 255 func (t *callTracer) GetResult() (json.RawMessage, error) { 256 if len(t.callstack) != 1 { 257 return nil, errors.New("incorrect number of top-level calls") 258 } 259 260 // Arbitrum: populate the top-level call with additional info 261 call := t.callstack[0] 262 call.BeforeEVMTransfers = &t.beforeEVMTransfers 263 call.AfterEVMTransfers = &t.afterEVMTransfers 264 265 res, err := json.Marshal(call) 266 if err != nil { 267 return nil, err 268 } 269 return json.RawMessage(res), t.reason 270 } 271 272 // Stop terminates execution of the tracer at the first opportune moment. 273 func (t *callTracer) Stop(err error) { 274 t.reason = err 275 atomic.StoreUint32(&t.interrupt, 1) 276 } 277 278 // clearFailedLogs clears the logs of a callframe and all its children 279 // in case of execution failure. 280 func clearFailedLogs(cf *callFrame, parentFailed bool) { 281 failed := cf.failed() || parentFailed 282 // Clear own logs 283 if failed { 284 cf.Logs = nil 285 } 286 for i := range cf.Calls { 287 clearFailedLogs(&cf.Calls[i], failed) 288 } 289 }