diff --git a/turbo/jsonrpc/otterscan_trace_transaction.go b/turbo/jsonrpc/otterscan_trace_transaction.go index bbf1488acdc..c6df5d79f67 100644 --- a/turbo/jsonrpc/otterscan_trace_transaction.go +++ b/turbo/jsonrpc/otterscan_trace_transaction.go @@ -2,9 +2,10 @@ package jsonrpc import ( "context" - "github.com/ledgerwatch/erigon-lib/common/hexutil" "math/big" + "github.com/ledgerwatch/erigon-lib/common/hexutil" + "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/common" @@ -29,12 +30,13 @@ func (api *OtterscanAPIImpl) TraceTransaction(ctx context.Context, hash common.H } type TraceEntry struct { - Type string `json:"type"` - Depth int `json:"depth"` - From common.Address `json:"from"` - To common.Address `json:"to"` - Value *hexutil.Big `json:"value"` - Input hexutility.Bytes `json:"input"` + Type string `json:"type"` + Depth int `json:"depth"` + From common.Address `json:"from"` + To common.Address `json:"to"` + Value *hexutil.Big `json:"value"` + Input hexutility.Bytes `json:"input"` + Output hexutility.Bytes `json:"output"` } type TransactionTracer struct { @@ -42,55 +44,54 @@ type TransactionTracer struct { ctx context.Context Results []*TraceEntry depth int // computed from CaptureStart, CaptureEnter, and CaptureExit calls + stack []*TraceEntry } func NewTransactionTracer(ctx context.Context) *TransactionTracer { return &TransactionTracer{ ctx: ctx, Results: make([]*TraceEntry, 0), + stack: make([]*TraceEntry, 0), } } func (t *TransactionTracer) captureStartOrEnter(typ vm.OpCode, from, to common.Address, precompile bool, input []byte, value *uint256.Int) { - if precompile { - return - } - inputCopy := make([]byte, len(input)) copy(inputCopy, input) _value := new(big.Int) if value != nil { _value.Set(value.ToBig()) } + + var entry *TraceEntry if typ == vm.CALL { - t.Results = append(t.Results, &TraceEntry{"CALL", t.depth, from, to, (*hexutil.Big)(_value), inputCopy}) - return - } - if typ == vm.STATICCALL { - t.Results = append(t.Results, &TraceEntry{"STATICCALL", t.depth, from, to, nil, inputCopy}) - return - } - if typ == vm.DELEGATECALL { - t.Results = append(t.Results, &TraceEntry{"DELEGATECALL", t.depth, from, to, nil, inputCopy}) - return - } - if typ == vm.CALLCODE { - t.Results = append(t.Results, &TraceEntry{"CALLCODE", t.depth, from, to, (*hexutil.Big)(_value), inputCopy}) - return - } - if typ == vm.CREATE { - t.Results = append(t.Results, &TraceEntry{"CREATE", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy}) - return - } - if typ == vm.CREATE2 { - t.Results = append(t.Results, &TraceEntry{"CREATE2", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy}) - return + entry = &TraceEntry{"CALL", t.depth, from, to, (*hexutil.Big)(_value), inputCopy, nil} + } else if typ == vm.STATICCALL { + entry = &TraceEntry{"STATICCALL", t.depth, from, to, nil, inputCopy, nil} + } else if typ == vm.DELEGATECALL { + entry = &TraceEntry{"DELEGATECALL", t.depth, from, to, nil, inputCopy, nil} + } else if typ == vm.CALLCODE { + entry = &TraceEntry{"CALLCODE", t.depth, from, to, (*hexutil.Big)(_value), inputCopy, nil} + } else if typ == vm.CREATE { + entry = &TraceEntry{"CREATE", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy, nil} + } else if typ == vm.CREATE2 { + entry = &TraceEntry{"CREATE2", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy, nil} + } else if typ == vm.SELFDESTRUCT { + last := t.Results[len(t.Results)-1] + entry = &TraceEntry{"SELFDESTRUCT", last.Depth + 1, from, to, (*hexutil.Big)(value.ToBig()), nil, nil} + } else { + // safeguard in case new CALL-like opcodes are introduced but not handled, + // otherwise CaptureExit/stack will get out of sync + entry = &TraceEntry{"UNKNOWN", t.depth, from, to, (*hexutil.Big)(value.ToBig()), inputCopy, nil} } - if typ == vm.SELFDESTRUCT { - last := t.Results[len(t.Results)-1] - t.Results = append(t.Results, &TraceEntry{"SELFDESTRUCT", last.Depth + 1, from, to, (*hexutil.Big)(value.ToBig()), nil}) + // Ignore precompiles in the returned trace (maybe we shouldn't?) + if !precompile { + t.Results = append(t.Results, entry) } + + // stack precompiles in order to match captureEndOrExit + t.stack = append(t.stack, entry) } func (t *TransactionTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, precompile bool, create bool, input []byte, gas uint64, value *uint256.Int, code []byte) { @@ -103,6 +104,22 @@ func (t *TransactionTracer) CaptureEnter(typ vm.OpCode, from common.Address, to t.captureStartOrEnter(typ, from, to, precompile, input, value) } -func (t *TransactionTracer) CaptureExit(output []byte, usedGas uint64, err error) { +func (t *TransactionTracer) captureEndOrExit(output []byte, usedGas uint64, err error) { t.depth-- + + lastIdx := len(t.stack) - 1 + pop := t.stack[lastIdx] + t.stack = t.stack[:lastIdx] + + outputCopy := make([]byte, len(output)) + copy(outputCopy, output) + pop.Output = outputCopy +} + +func (t *TransactionTracer) CaptureExit(output []byte, usedGas uint64, err error) { + t.captureEndOrExit(output, usedGas, err) +} + +func (t *TransactionTracer) CaptureEnd(output []byte, usedGas uint64, err error) { + t.captureEndOrExit(output, usedGas, err) }