github.com/ethereum/go-ethereum@v1.16.1/eth/tracers/native/call_flat.go (about)

     1  // Copyright 2023 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  	"fmt"
    23  	"math/big"
    24  	"slices"
    25  	"strings"
    26  	"sync/atomic"
    27  
    28  	"github.com/ethereum/go-ethereum/common"
    29  	"github.com/ethereum/go-ethereum/common/hexutil"
    30  	"github.com/ethereum/go-ethereum/core/tracing"
    31  	"github.com/ethereum/go-ethereum/core/types"
    32  	"github.com/ethereum/go-ethereum/core/vm"
    33  	"github.com/ethereum/go-ethereum/eth/tracers"
    34  	"github.com/ethereum/go-ethereum/params"
    35  )
    36  
    37  //go:generate go run github.com/fjl/gencodec -type flatCallAction -field-override flatCallActionMarshaling -out gen_flatcallaction_json.go
    38  //go:generate go run github.com/fjl/gencodec -type flatCallResult -field-override flatCallResultMarshaling -out gen_flatcallresult_json.go
    39  
    40  func init() {
    41  	tracers.DefaultDirectory.Register("flatCallTracer", newFlatCallTracer, false)
    42  }
    43  
    44  var parityErrorMapping = map[string]string{
    45  	"contract creation code storage out of gas": "Out of gas",
    46  	"out of gas":                      "Out of gas",
    47  	"gas uint64 overflow":             "Out of gas",
    48  	"max code size exceeded":          "Out of gas",
    49  	"invalid jump destination":        "Bad jump destination",
    50  	"execution reverted":              "Reverted",
    51  	"return data out of bounds":       "Out of bounds",
    52  	"stack limit reached 1024 (1023)": "Out of stack",
    53  	"precompiled failed":              "Built-in failed",
    54  	"invalid input length":            "Built-in failed",
    55  }
    56  
    57  var parityErrorMappingStartingWith = map[string]string{
    58  	"out of gas:":     "Out of gas", // convert OOG wrapped errors, eg `out of gas: not enough gas for reentrancy sentry`
    59  	"invalid opcode:": "Bad instruction",
    60  	"stack underflow": "Stack underflow",
    61  }
    62  
    63  // flatCallFrame is a standalone callframe.
    64  type flatCallFrame struct {
    65  	Action              flatCallAction  `json:"action"`
    66  	BlockHash           *common.Hash    `json:"blockHash"`
    67  	BlockNumber         uint64          `json:"blockNumber"`
    68  	Error               string          `json:"error,omitempty"`
    69  	Result              *flatCallResult `json:"result,omitempty"`
    70  	Subtraces           int             `json:"subtraces"`
    71  	TraceAddress        []int           `json:"traceAddress"`
    72  	TransactionHash     *common.Hash    `json:"transactionHash"`
    73  	TransactionPosition uint64          `json:"transactionPosition"`
    74  	Type                string          `json:"type"`
    75  }
    76  
    77  type flatCallAction struct {
    78  	Author         *common.Address `json:"author,omitempty"`
    79  	RewardType     string          `json:"rewardType,omitempty"`
    80  	SelfDestructed *common.Address `json:"address,omitempty"`
    81  	Balance        *big.Int        `json:"balance,omitempty"`
    82  	CallType       string          `json:"callType,omitempty"`
    83  	CreationMethod string          `json:"creationMethod,omitempty"`
    84  	From           *common.Address `json:"from,omitempty"`
    85  	Gas            *uint64         `json:"gas,omitempty"`
    86  	Init           *[]byte         `json:"init,omitempty"`
    87  	Input          *[]byte         `json:"input,omitempty"`
    88  	RefundAddress  *common.Address `json:"refundAddress,omitempty"`
    89  	To             *common.Address `json:"to,omitempty"`
    90  	Value          *big.Int        `json:"value,omitempty"`
    91  }
    92  
    93  type flatCallActionMarshaling struct {
    94  	Balance *hexutil.Big
    95  	Gas     *hexutil.Uint64
    96  	Init    *hexutil.Bytes
    97  	Input   *hexutil.Bytes
    98  	Value   *hexutil.Big
    99  }
   100  
   101  type flatCallResult struct {
   102  	Address *common.Address `json:"address,omitempty"`
   103  	Code    *[]byte         `json:"code,omitempty"`
   104  	GasUsed *uint64         `json:"gasUsed,omitempty"`
   105  	Output  *[]byte         `json:"output,omitempty"`
   106  }
   107  
   108  type flatCallResultMarshaling struct {
   109  	Code    *hexutil.Bytes
   110  	GasUsed *hexutil.Uint64
   111  	Output  *hexutil.Bytes
   112  }
   113  
   114  // flatCallTracer reports call frame information of a tx in a flat format, i.e.
   115  // as opposed to the nested format of `callTracer`.
   116  type flatCallTracer struct {
   117  	tracer            *callTracer
   118  	config            flatCallTracerConfig
   119  	chainConfig       *params.ChainConfig
   120  	ctx               *tracers.Context // Holds tracer context data
   121  	interrupt         atomic.Bool      // Atomic flag to signal execution interruption
   122  	activePrecompiles []common.Address // Updated on tx start based on given rules
   123  }
   124  
   125  type flatCallTracerConfig struct {
   126  	ConvertParityErrors bool `json:"convertParityErrors"` // If true, call tracer converts errors to parity format
   127  	IncludePrecompiles  bool `json:"includePrecompiles"`  // If true, call tracer includes calls to precompiled contracts
   128  }
   129  
   130  // newFlatCallTracer returns a new flatCallTracer.
   131  func newFlatCallTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *params.ChainConfig) (*tracers.Tracer, error) {
   132  	var config flatCallTracerConfig
   133  	if err := json.Unmarshal(cfg, &config); err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	// Create inner call tracer with default configuration, don't forward
   138  	// the OnlyTopCall or WithLog to inner for now
   139  	t, err := newCallTracerObject(ctx, json.RawMessage("{}"))
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	ft := &flatCallTracer{tracer: t, ctx: ctx, config: config, chainConfig: chainConfig}
   145  	return &tracers.Tracer{
   146  		Hooks: &tracing.Hooks{
   147  			OnTxStart: ft.OnTxStart,
   148  			OnTxEnd:   ft.OnTxEnd,
   149  			OnEnter:   ft.OnEnter,
   150  			OnExit:    ft.OnExit,
   151  		},
   152  		Stop:      ft.Stop,
   153  		GetResult: ft.GetResult,
   154  	}, nil
   155  }
   156  
   157  // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct).
   158  func (t *flatCallTracer) OnEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
   159  	if t.interrupt.Load() {
   160  		return
   161  	}
   162  	t.tracer.OnEnter(depth, typ, from, to, input, gas, value)
   163  
   164  	if depth == 0 {
   165  		return
   166  	}
   167  	// Child calls must have a value, even if it's zero.
   168  	// Practically speaking, only STATICCALL has nil value. Set it to zero.
   169  	if t.tracer.callstack[len(t.tracer.callstack)-1].Value == nil && value == nil {
   170  		t.tracer.callstack[len(t.tracer.callstack)-1].Value = big.NewInt(0)
   171  	}
   172  }
   173  
   174  // OnExit is called when EVM exits a scope, even if the scope didn't
   175  // execute any code.
   176  func (t *flatCallTracer) OnExit(depth int, output []byte, gasUsed uint64, err error, reverted bool) {
   177  	if t.interrupt.Load() {
   178  		return
   179  	}
   180  	t.tracer.OnExit(depth, output, gasUsed, err, reverted)
   181  
   182  	if depth == 0 {
   183  		return
   184  	}
   185  	// Parity traces don't include CALL/STATICCALLs to precompiles.
   186  	// By default we remove them from the callstack.
   187  	if t.config.IncludePrecompiles {
   188  		return
   189  	}
   190  	var (
   191  		// call has been nested in parent
   192  		parent = t.tracer.callstack[len(t.tracer.callstack)-1]
   193  		call   = parent.Calls[len(parent.Calls)-1]
   194  		typ    = call.Type
   195  		to     = call.To
   196  	)
   197  	if typ == vm.CALL || typ == vm.STATICCALL {
   198  		if t.isPrecompiled(*to) {
   199  			t.tracer.callstack[len(t.tracer.callstack)-1].Calls = parent.Calls[:len(parent.Calls)-1]
   200  		}
   201  	}
   202  }
   203  
   204  func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) {
   205  	if t.interrupt.Load() {
   206  		return
   207  	}
   208  	t.tracer.OnTxStart(env, tx, from)
   209  	// Update list of precompiles based on current block
   210  	rules := t.chainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time)
   211  	t.activePrecompiles = vm.ActivePrecompiles(rules)
   212  }
   213  
   214  func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) {
   215  	if t.interrupt.Load() {
   216  		return
   217  	}
   218  	t.tracer.OnTxEnd(receipt, err)
   219  }
   220  
   221  // GetResult returns an empty json object.
   222  func (t *flatCallTracer) GetResult() (json.RawMessage, error) {
   223  	if len(t.tracer.callstack) < 1 {
   224  		return nil, errors.New("invalid number of calls")
   225  	}
   226  
   227  	flat, err := flatFromNested(&t.tracer.callstack[0], []int{}, t.config.ConvertParityErrors, t.ctx)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  
   232  	res, err := json.Marshal(flat)
   233  	if err != nil {
   234  		return nil, err
   235  	}
   236  	return res, t.tracer.reason
   237  }
   238  
   239  // Stop terminates execution of the tracer at the first opportune moment.
   240  func (t *flatCallTracer) Stop(err error) {
   241  	t.tracer.Stop(err)
   242  	t.interrupt.Store(true)
   243  }
   244  
   245  // isPrecompiled returns whether the addr is a precompile.
   246  func (t *flatCallTracer) isPrecompiled(addr common.Address) bool {
   247  	return slices.Contains(t.activePrecompiles, addr)
   248  }
   249  
   250  func flatFromNested(input *callFrame, traceAddress []int, convertErrs bool, ctx *tracers.Context) (output []flatCallFrame, err error) {
   251  	var frame *flatCallFrame
   252  	switch input.Type {
   253  	case vm.CREATE, vm.CREATE2:
   254  		frame = newFlatCreate(input)
   255  	case vm.SELFDESTRUCT:
   256  		frame = newFlatSelfdestruct(input)
   257  	case vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.DELEGATECALL:
   258  		frame = newFlatCall(input)
   259  	default:
   260  		return nil, fmt.Errorf("unrecognized call frame type: %s", input.Type)
   261  	}
   262  
   263  	frame.TraceAddress = traceAddress
   264  	frame.Error = input.Error
   265  	frame.Subtraces = len(input.Calls)
   266  	fillCallFrameFromContext(frame, ctx)
   267  	if convertErrs {
   268  		convertErrorToParity(frame)
   269  	}
   270  
   271  	// Revert output contains useful information (revert reason).
   272  	// Otherwise discard result.
   273  	if input.Error != "" && input.Error != vm.ErrExecutionReverted.Error() {
   274  		frame.Result = nil
   275  	}
   276  
   277  	output = append(output, *frame)
   278  	for i, childCall := range input.Calls {
   279  		childAddr := childTraceAddress(traceAddress, i)
   280  		childCallCopy := childCall
   281  		flat, err := flatFromNested(&childCallCopy, childAddr, convertErrs, ctx)
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		output = append(output, flat...)
   286  	}
   287  
   288  	return output, nil
   289  }
   290  
   291  func newFlatCreate(input *callFrame) *flatCallFrame {
   292  	var (
   293  		actionInit = input.Input[:]
   294  		resultCode = input.Output[:]
   295  	)
   296  
   297  	return &flatCallFrame{
   298  		Type: strings.ToLower(vm.CREATE.String()),
   299  		Action: flatCallAction{
   300  			CreationMethod: strings.ToLower(input.Type.String()),
   301  			From:           &input.From,
   302  			Gas:            &input.Gas,
   303  			Value:          input.Value,
   304  			Init:           &actionInit,
   305  		},
   306  		Result: &flatCallResult{
   307  			GasUsed: &input.GasUsed,
   308  			Address: input.To,
   309  			Code:    &resultCode,
   310  		},
   311  	}
   312  }
   313  
   314  func newFlatCall(input *callFrame) *flatCallFrame {
   315  	var (
   316  		actionInput  = input.Input[:]
   317  		resultOutput = input.Output[:]
   318  	)
   319  
   320  	return &flatCallFrame{
   321  		Type: strings.ToLower(vm.CALL.String()),
   322  		Action: flatCallAction{
   323  			From:     &input.From,
   324  			To:       input.To,
   325  			Gas:      &input.Gas,
   326  			Value:    input.Value,
   327  			CallType: strings.ToLower(input.Type.String()),
   328  			Input:    &actionInput,
   329  		},
   330  		Result: &flatCallResult{
   331  			GasUsed: &input.GasUsed,
   332  			Output:  &resultOutput,
   333  		},
   334  	}
   335  }
   336  
   337  func newFlatSelfdestruct(input *callFrame) *flatCallFrame {
   338  	return &flatCallFrame{
   339  		Type: "suicide",
   340  		Action: flatCallAction{
   341  			SelfDestructed: &input.From,
   342  			Balance:        input.Value,
   343  			RefundAddress:  input.To,
   344  		},
   345  	}
   346  }
   347  
   348  func fillCallFrameFromContext(callFrame *flatCallFrame, ctx *tracers.Context) {
   349  	if ctx == nil {
   350  		return
   351  	}
   352  	if ctx.BlockHash != (common.Hash{}) {
   353  		callFrame.BlockHash = &ctx.BlockHash
   354  	}
   355  	if ctx.BlockNumber != nil {
   356  		callFrame.BlockNumber = ctx.BlockNumber.Uint64()
   357  	}
   358  	if ctx.TxHash != (common.Hash{}) {
   359  		callFrame.TransactionHash = &ctx.TxHash
   360  	}
   361  	callFrame.TransactionPosition = uint64(ctx.TxIndex)
   362  }
   363  
   364  func convertErrorToParity(call *flatCallFrame) {
   365  	if call.Error == "" {
   366  		return
   367  	}
   368  
   369  	if parityError, ok := parityErrorMapping[call.Error]; ok {
   370  		call.Error = parityError
   371  	} else {
   372  		for gethError, parityError := range parityErrorMappingStartingWith {
   373  			if strings.HasPrefix(call.Error, gethError) {
   374  				call.Error = parityError
   375  				break
   376  			}
   377  		}
   378  	}
   379  }
   380  
   381  func childTraceAddress(a []int, i int) []int {
   382  	child := make([]int, 0, len(a)+1)
   383  	child = append(child, a...)
   384  	child = append(child, i)
   385  	return child
   386  }