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  }