github.com/lmittmann/w3@v0.20.0/w3vm/hooks/call_tracer.go (about) 1 package hooks 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "io" 7 "math/big" 8 "reflect" 9 "strings" 10 "sync" 11 12 "github.com/charmbracelet/lipgloss" 13 "github.com/ethereum/go-ethereum/accounts/abi" 14 "github.com/ethereum/go-ethereum/common" 15 "github.com/ethereum/go-ethereum/core/tracing" 16 "github.com/ethereum/go-ethereum/core/types" 17 "github.com/ethereum/go-ethereum/core/vm" 18 "github.com/lmittmann/w3" 19 "github.com/lmittmann/w3/internal/fourbyte" 20 ) 21 22 var ( 23 // styles 24 styleDim = lipgloss.NewStyle().Faint(true) 25 styleTarget = lipgloss.NewStyle().Foreground(lipgloss.Color("#EBFF71")) 26 styleValue = lipgloss.NewStyle().Foreground(lipgloss.Color("#71FF71")) 27 styleRevert = lipgloss.NewStyle().Foreground(lipgloss.Color("#FE5F86")) 28 stylesStaticcall = lipgloss.NewStyle().Faint(true) 29 stylesDelegatecall = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500")) 30 ) 31 32 // TargetAddress can be used to match the target (to) address in the TargetStyler 33 // of [CallTracerOptions]. 34 var TargetAddress = common.BytesToAddress([]byte("target")) 35 36 // CallTracerOptions configures the CallTracer hook. A zero CallTracerOptions 37 // consists entirely of default values. 38 type CallTracerOptions struct { 39 TargetStyler func(addr common.Address) lipgloss.Style 40 targetAddr common.Address 41 42 ShowStaticcall bool 43 44 ShowOp func(op byte, pc uint64, addr common.Address) bool 45 OpStyler func(op byte) lipgloss.Style 46 47 DecodeABI bool 48 } 49 50 func (opts *CallTracerOptions) targetStyler(addr common.Address) lipgloss.Style { 51 if addr == opts.targetAddr { 52 addr = TargetAddress 53 } 54 55 if opts.TargetStyler == nil { 56 return defaultTargetStyler(addr) 57 } 58 return opts.TargetStyler(addr) 59 } 60 61 func (opts *CallTracerOptions) showOp(op byte, pc uint64, addr common.Address) bool { 62 if opts.ShowOp == nil { 63 return false 64 } 65 return opts.ShowOp(op, pc, addr) 66 } 67 68 func (opts *CallTracerOptions) opStyler(op byte) lipgloss.Style { 69 if opts.OpStyler == nil { 70 return defaultOpStyler(op) 71 } 72 return opts.OpStyler(op) 73 } 74 75 func defaultTargetStyler(addr common.Address) lipgloss.Style { 76 switch addr { 77 case TargetAddress: 78 return styleTarget 79 default: 80 return lipgloss.NewStyle() 81 } 82 } 83 84 func defaultOpStyler(byte) lipgloss.Style { 85 return lipgloss.NewStyle() 86 } 87 88 // NewCallTracer returns a new hook that writes to w and is configured with opts. 89 func NewCallTracer(w io.Writer, opts *CallTracerOptions) *tracing.Hooks { 90 if opts == nil { 91 opts = new(CallTracerOptions) 92 } 93 tracer := &callTracer{w: w, opts: opts} 94 95 return &tracing.Hooks{ 96 OnEnter: tracer.EnterHook, 97 OnExit: tracer.ExitHook, 98 OnOpcode: tracer.OpcodeHook, 99 OnLog: tracer.OnLog, 100 } 101 } 102 103 type callTracer struct { 104 w io.Writer 105 opts *CallTracerOptions 106 once sync.Once 107 108 callStack []call 109 110 // isInStaticcall is the number of nested staticcalls on the callStack. 111 // It is only incremented if opts.ShowStatic is true. 112 isInStaticcall int 113 } 114 115 func (c *callTracer) EnterHook(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { 116 c.once.Do(func() { 117 c.opts.targetAddr = to 118 }) 119 120 var ( 121 fn *w3.Func 122 isPrecompile bool 123 ) 124 if c.opts.DecodeABI && len(input) >= 4 { 125 sig := ([4]byte)(input[:4]) 126 fn, isPrecompile = fourbyte.Function(sig, to) 127 } 128 129 callType := vm.OpCode(typ) 130 defer func() { c.callStack = append(c.callStack, call{callType, to, fn}) }() 131 if !c.opts.ShowStaticcall && callType == vm.STATICCALL { 132 c.isInStaticcall++ 133 } 134 if c.isInStaticcall > 0 { 135 return 136 } 137 138 fmt.Fprintf(c.w, "%s%s %s%s%s\n", renderIdent(c.callStack, c.opts.targetStyler, 1), renderAddr(to, c.opts.targetStyler), renderCallType(typ), renderValue(c.opts.DecodeABI, value), renderInput(fn, isPrecompile, input, c.opts.targetStyler)) 139 } 140 141 func (c *callTracer) ExitHook(depth int, output []byte, gasUsed uint64, err error, reverted bool) { 142 call := c.callStack[len(c.callStack)-1] 143 defer func() { c.callStack = c.callStack[:depth] }() 144 145 if !c.opts.ShowStaticcall && call.Type == vm.STATICCALL { 146 defer func() { c.isInStaticcall-- }() 147 } 148 if c.isInStaticcall > 0 { 149 return 150 } 151 152 if reverted { 153 fmt.Fprintf(c.w, "%s%s\n", renderIdent(c.callStack, c.opts.targetStyler, -1), styleRevert.Render(fmt.Sprintf("[%d]", gasUsed), renderRevert(err, output, c.opts.DecodeABI))) 154 } else { 155 fmt.Fprintf(c.w, "%s[%d] %s\n", renderIdent(c.callStack, c.opts.targetStyler, -1), gasUsed, renderOutput(call.Func, output, c.opts.targetStyler)) 156 } 157 } 158 159 func (c *callTracer) OpcodeHook(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { 160 if c.isInStaticcall > 0 || 161 !c.opts.showOp(op, pc, scope.Address()) { 162 return 163 } 164 fmt.Fprintln(c.w, renderIdent(c.callStack, c.opts.targetStyler, 0)+renderOp(op, c.opts.opStyler, pc, scope)) 165 } 166 167 func (c *callTracer) OnLog(log *types.Log) { 168 if c.isInStaticcall > 0 { 169 return 170 } 171 } 172 173 func renderIdent(callStack []call, styler func(addr common.Address) lipgloss.Style, kind int) (ident string) { 174 for i, call := range callStack { 175 style := styler(call.Target) 176 177 s := "│ " 178 if isLast := i == len(callStack)-1; isLast { 179 switch kind { 180 case 1: 181 s = "├╴" 182 case -1: 183 s = "└╴" 184 } 185 } 186 ident += style.Faint(true).Render(s) 187 } 188 return ident 189 } 190 191 func renderAddr(addr common.Address, styler func(addr common.Address) lipgloss.Style) string { 192 return styler(addr).Render(addr.Hex()) 193 } 194 195 func renderCallType(typ byte) string { 196 switch vm.OpCode(typ) { 197 case vm.CALL: 198 return "" 199 case vm.CALLCODE: 200 return "callcode " 201 case vm.STATICCALL: 202 return stylesStaticcall.Render("static") + " " 203 case vm.DELEGATECALL: 204 return stylesDelegatecall.Render("delegate") + " " 205 case vm.CREATE: 206 return "create " 207 case vm.CREATE2: 208 return "create2 " 209 case vm.SELFDESTRUCT: 210 return "selfdestruct " 211 default: 212 panic(fmt.Sprintf("unknown call type %02x", typ)) 213 } 214 } 215 216 func renderValue(decodeABI bool, val *big.Int) string { 217 if val == nil || val.Sign() == 0 { 218 return "" 219 } 220 if !decodeABI { 221 return styleValue.Render(val.String(), "ETH") + " " 222 } 223 return styleValue.Render(w3.FromWei(val, 18), "ETH") + " " 224 } 225 226 func renderInput(fn *w3.Func, isPrecompile bool, input []byte, styler func(addr common.Address) lipgloss.Style) string { 227 if fn != nil && len(input) >= 4 { 228 s, err := renderAbiInput(fn, isPrecompile, input, styler) 229 if err == nil { 230 return s 231 } 232 } 233 return renderRawInput(input, styler) 234 } 235 236 func renderOutput(fn *w3.Func, output []byte, styler func(addr common.Address) lipgloss.Style) string { 237 if fn != nil && len(output) >= 4 { 238 s, err := renderAbiOutput(fn, output, styler) 239 if err == nil { 240 return s 241 } 242 } 243 return renderRawOutput(output, styler) 244 } 245 246 func renderRevert(revertErr error, output []byte, decodeABI bool) string { 247 if decodeABI && len(output) >= 4 { 248 sig := ([4]byte)(output[:4]) 249 fn, isPrecompile := fourbyte.Function(sig, w3.Addr0) 250 if fn != nil && !isPrecompile { 251 args, err := fn.Args.Unpack(output[4:]) 252 if err == nil { 253 funcName := strings.Split(fn.Signature, "(")[0] 254 return fmt.Sprintf("%s: %s(%s)", revertErr, funcName, renderAbiArgs(fn.Args, args, nil)) 255 } 256 } 257 } 258 return fmt.Sprintf("%s: %x", revertErr, output) 259 } 260 261 func renderRawInput(input []byte, styler func(addr common.Address) lipgloss.Style) (s string) { 262 s = "0x" 263 if len(input)%32 == 4 { 264 s += hex.EncodeToString(input[:4]) 265 for i := 4; i < len(input); i += 32 { 266 s += renderWord(input[i:i+32], styler) 267 } 268 } else { 269 s += hex.EncodeToString(input) 270 } 271 return 272 } 273 274 func renderRawOutput(output []byte, styler func(addr common.Address) lipgloss.Style) (s string) { 275 s = "0x" 276 if len(output)%32 == 0 { 277 for i := 0; i < len(output); i += 32 { 278 s += renderWord(output[i:i+32], styler) 279 } 280 } else { 281 s += hex.EncodeToString(output) 282 } 283 return 284 } 285 286 func renderWord(word []byte, _ func(addr common.Address) lipgloss.Style) string { 287 s := hex.EncodeToString(word) 288 nonZeroWord := strings.TrimLeft(s, "0") 289 if len(nonZeroWord) < len(s) { 290 s = styleDim.Render(strings.Repeat("0", len(s)-len(nonZeroWord))) + nonZeroWord 291 } 292 return s 293 } 294 295 func renderAbiInput(fn *w3.Func, isPrecompile bool, input []byte, styler func(addr common.Address) lipgloss.Style) (string, error) { 296 if !isPrecompile { 297 input = input[4:] 298 } 299 300 args, err := fn.Args.Unpack(input) 301 if err != nil { 302 return "", err 303 } 304 305 funcName := strings.Split(fn.Signature, "(")[0] 306 return funcName + "(" + renderAbiArgs(fn.Args, args, styler) + ")", nil 307 } 308 309 func renderAbiOutput(fn *w3.Func, output []byte, styler func(addr common.Address) lipgloss.Style) (string, error) { 310 returns, err := fn.Returns.Unpack(output) 311 if err != nil { 312 return "", err 313 } 314 315 return renderAbiArgs(fn.Returns, returns, styler), nil 316 } 317 318 func renderAbiArgs(args abi.Arguments, vals []any, styler func(addr common.Address) lipgloss.Style) (s string) { 319 for i, val := range vals { 320 arg := args[i] 321 s += renderAbiTyp(&arg.Type, arg.Name, val, styler) 322 if i < len(vals)-1 { 323 s += styleDim.Render(",") + " " 324 } 325 } 326 return 327 } 328 329 func renderAbiTyp(typ *abi.Type, name string, val any, styler func(addr common.Address) lipgloss.Style) (s string) { 330 if name != "" { 331 s += styleDim.Render(name+":") + " " 332 } 333 334 if styler == nil { 335 styler = func(addr common.Address) lipgloss.Style { return lipgloss.NewStyle() } 336 } 337 338 switch val := val.(type) { 339 case []byte: 340 s += "0x" + hex.EncodeToString(val) 341 case [32]byte: 342 s += "0x" + hex.EncodeToString(val[:]) 343 case [31]byte: 344 s += "0x" + hex.EncodeToString(val[:]) 345 case [30]byte: 346 s += "0x" + hex.EncodeToString(val[:]) 347 case [29]byte: 348 s += "0x" + hex.EncodeToString(val[:]) 349 case [28]byte: 350 s += "0x" + hex.EncodeToString(val[:]) 351 case [27]byte: 352 s += "0x" + hex.EncodeToString(val[:]) 353 case [26]byte: 354 s += "0x" + hex.EncodeToString(val[:]) 355 case [25]byte: 356 s += "0x" + hex.EncodeToString(val[:]) 357 case [24]byte: 358 s += "0x" + hex.EncodeToString(val[:]) 359 case [23]byte: 360 s += "0x" + hex.EncodeToString(val[:]) 361 case [22]byte: 362 s += "0x" + hex.EncodeToString(val[:]) 363 case [21]byte: 364 s += "0x" + hex.EncodeToString(val[:]) 365 case [20]byte: 366 s += "0x" + hex.EncodeToString(val[:]) 367 case [19]byte: 368 s += "0x" + hex.EncodeToString(val[:]) 369 case [18]byte: 370 s += "0x" + hex.EncodeToString(val[:]) 371 case [17]byte: 372 s += "0x" + hex.EncodeToString(val[:]) 373 case [16]byte: 374 s += "0x" + hex.EncodeToString(val[:]) 375 case [15]byte: 376 s += "0x" + hex.EncodeToString(val[:]) 377 case [14]byte: 378 s += "0x" + hex.EncodeToString(val[:]) 379 case [13]byte: 380 s += "0x" + hex.EncodeToString(val[:]) 381 case [12]byte: 382 s += "0x" + hex.EncodeToString(val[:]) 383 case [11]byte: 384 s += "0x" + hex.EncodeToString(val[:]) 385 case [10]byte: 386 s += "0x" + hex.EncodeToString(val[:]) 387 case [9]byte: 388 s += "0x" + hex.EncodeToString(val[:]) 389 case [8]byte: 390 s += "0x" + hex.EncodeToString(val[:]) 391 case [7]byte: 392 s += "0x" + hex.EncodeToString(val[:]) 393 case [6]byte: 394 s += "0x" + hex.EncodeToString(val[:]) 395 case [5]byte: 396 s += "0x" + hex.EncodeToString(val[:]) 397 case [4]byte: 398 s += "0x" + hex.EncodeToString(val[:]) 399 case [3]byte: 400 s += "0x" + hex.EncodeToString(val[:]) 401 case [2]byte: 402 s += "0x" + hex.EncodeToString(val[:]) 403 case [1]byte: 404 s += "0x" + hex.EncodeToString(val[:]) 405 case common.Address: 406 style := styler(val) 407 s += style.Render(val.Hex()) 408 case common.Hash: 409 s += val.Hex() 410 case any: // tuple, array, or slice 411 switch refVal := reflect.ValueOf(val); refVal.Kind() { 412 case reflect.Slice: 413 s += "[" 414 for i := range refVal.Len() { 415 s += renderAbiTyp(typ.Elem, "", refVal.Index(i).Interface(), styler) 416 417 if i < refVal.Len()-1 { 418 s += styleDim.Render(",") + " " 419 } 420 } 421 s += "]" 422 case reflect.Array: 423 s += "[" 424 for i := range refVal.Len() { 425 s += renderAbiTyp(typ.Elem, "", refVal.Index(i).Interface(), styler) 426 427 if i < refVal.Len()-1 { 428 s += styleDim.Render(",") + " " 429 } 430 } 431 s += "]" 432 case reflect.Struct: 433 s += "(" 434 for i := range refVal.NumField() { 435 s += renderAbiTyp(typ.TupleElems[i], typ.TupleRawNames[i], refVal.Field(i).Interface(), styler) 436 437 if i < refVal.NumField()-1 { 438 s += styleDim.Render(",") + " " 439 } 440 } 441 s += ")" 442 default: 443 s += fmt.Sprintf("%v", val) 444 } 445 default: 446 s += fmt.Sprintf("%v", val) 447 } 448 return s 449 } 450 451 func renderOp(op byte, style func(byte) lipgloss.Style, pc uint64, scope tracing.OpContext) string { 452 const maxStackDepth = 7 453 sb := new(strings.Builder) 454 sb.WriteString(styleDim.Render(fmt.Sprintf("0x%04x ", pc))) 455 sb.WriteString(style(op).Render(fmt.Sprintf("%-12s ", vm.OpCode(op)))) 456 457 stack := scope.StackData() 458 for i, j := len(stack)-1, 0; i >= 0 && i >= len(stack)-maxStackDepth; i, j = i-1, j+1 { 459 notLast := i > 0 && i > len(stack)-maxStackDepth 460 if isAccessed := opAccessesStackElem(op, j); isAccessed { 461 sb.WriteString(stack[i].Hex()) 462 } else { 463 sb.WriteString(styleDim.Render(stack[i].Hex())) 464 } 465 if notLast { 466 sb.WriteString(" ") 467 } 468 } 469 470 if len(stack) > maxStackDepth { 471 sb.WriteString(styleDim.Render(fmt.Sprintf(" …%d", len(stack)-maxStackDepth))) 472 } 473 474 return sb.String() 475 } 476 477 // call stores state of the current call in execution. 478 type call struct { 479 Type vm.OpCode 480 Target common.Address 481 Func *w3.Func 482 } 483 484 // opAccessesStackElem returns true, if the given opcode accesses the stack at 485 // index i, otherwise false. 486 func opAccessesStackElem(op byte, i int) bool { 487 switch { 488 case byte(vm.SWAP1) <= op && op <= byte(vm.SWAP16): 489 return i == 0 || i == int(op)-int(vm.SWAP1)+1 490 case byte(vm.DUP1) <= op && op <= byte(vm.DUP16): 491 return i == int(op)-int(vm.DUP1) 492 default: 493 return i < pops[op] 494 } 495 } 496 497 var pops = [256]int{ 498 vm.STOP: 0, vm.ADD: 2, vm.MUL: 2, vm.SUB: 2, vm.DIV: 2, vm.SDIV: 2, vm.MOD: 2, vm.SMOD: 2, vm.ADDMOD: 3, vm.MULMOD: 3, vm.EXP: 2, vm.SIGNEXTEND: 2, 499 vm.LT: 2, vm.GT: 2, vm.SLT: 2, vm.SGT: 2, vm.EQ: 2, vm.ISZERO: 1, vm.AND: 2, vm.OR: 2, vm.XOR: 2, vm.NOT: 1, vm.BYTE: 2, vm.SHL: 2, vm.SHR: 2, vm.SAR: 2, 500 vm.KECCAK256: 2, 501 vm.BALANCE: 1, vm.CALLDATALOAD: 1, vm.CALLDATACOPY: 3, vm.CODECOPY: 3, vm.EXTCODESIZE: 1, vm.EXTCODECOPY: 4, vm.RETURNDATACOPY: 3, vm.EXTCODEHASH: 1, 502 vm.BLOCKHASH: 1, vm.BLOBHASH: 1, 503 vm.POP: 1, vm.MLOAD: 1, vm.MSTORE: 2, vm.MSTORE8: 2, vm.SLOAD: 1, vm.SSTORE: 2, vm.JUMP: 1, vm.JUMPI: 2, vm.TLOAD: 1, vm.TSTORE: 2, vm.MCOPY: 3, 504 vm.LOG0: 2, vm.LOG1: 3, vm.LOG2: 4, vm.LOG3: 5, vm.LOG4: 6, 505 vm.CREATE: 3, vm.CALL: 7, vm.CALLCODE: 7, vm.RETURN: 2, vm.DELEGATECALL: 6, vm.CREATE2: 4, vm.STATICCALL: 6, vm.REVERT: 2, vm.SELFDESTRUCT: 1, 506 }