diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index c7f171c5bdf92..5c43ab2597fac 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -100,12 +100,22 @@ func (s *StructLog) ErrorString() string { return "" } +type wrappedLog struct { + parent *wrappedLog + error error + log StructLog + children []*wrappedLog +} + // StructLogger is an EVM state logger and implements EVMLogger. // // StructLogger can capture state based on the given Log configuration and also keeps // a track record of modified storage which is used in reporting snapshots of the // contract their storage. type StructLogger struct { + current *wrappedLog + depth int + cfg Config env *vm.EVM @@ -142,6 +152,8 @@ func (l *StructLogger) Reset() { // CaptureStart implements the EVMLogger interface to initialize the tracing operation. func (l *StructLogger) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { l.env = env + l.depth = 0 + l.current = &wrappedLog{} } // CaptureState logs a new structured log message and pushes it out to the environment @@ -160,6 +172,35 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s memory := scope.Memory stack := scope.Stack contract := scope.Contract + for ; l.depth > depth-1; l.depth = l.depth - 1 { + i := l.depth - (depth - 1) + if l.current.error == nil { + switch stack.Data()[len(stack.Data())-i].Bytes32()[31] { + case 0x00: + l.current.error = fmt.Errorf("call failed") + } + } + l.current = l.current.parent + } + if err != nil { + l.current.error = err + } + switch op { + case vm.CALL, vm.DELEGATECALL, vm.STATICCALL, vm.CALLCODE: + l.depth = l.depth + 1 + wl := &wrappedLog{ + parent: l.current, + error: l.current.error, + } + l.current.children = append(l.current.children, wl) + l.current = wl + case vm.REVERT: + l.current.error = vm.ErrExecutionReverted + return + default: + return + } + // Copy a snapshot of the current memory state to a new buffer var mem []byte if l.cfg.EnableMemory { @@ -209,7 +250,7 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s } // create a new snapshot of the EVM. log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} - l.logs = append(l.logs, log) + l.current.log = log } // CaptureFault implements the EVMLogger interface to trace an execution fault @@ -219,6 +260,17 @@ func (l *StructLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, s // CaptureEnd is called after the call finishes to finalize the tracing. func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { + for ; l.depth > 1; l.depth-- { + l.current = l.current.parent + } + l.current.log = StructLog{ + Op: vm.CALL, + GasCost: gasUsed, + ReturnData: output, + Depth: 0, + Err: err, + } + l.output = output l.err = err if l.cfg.Debug { @@ -269,8 +321,19 @@ func (l *StructLogger) CaptureTxEnd(restGas uint64) { l.usedGas = l.gasLimit - restGas } +// Depth first append for all children (stack max depth is 1024) +func (l *wrappedLog) getLogs() []StructLog { + var logs []StructLog + l.log.Err = l.error + logs = append(logs, l.log) + for _, child := range l.children { + logs = append(logs, child.getLogs()...) + } + return logs +} + // StructLogs returns the captured log entries. -func (l *StructLogger) StructLogs() []StructLog { return l.logs } +func (l *StructLogger) StructLogs() []StructLog { return l.current.getLogs() } // Error returns the VM error captured by the trace. func (l *StructLogger) Error() error { return l.err } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 69b5bf1304bbe..9b7169003fa21 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1223,6 +1223,71 @@ func (s *BlockChainAPI) EstimateGas(ctx context.Context, args TransactionArgs, b return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) } +// ExecutionResult groups all structured logs emitted by the EVM +// while replaying a transaction in debug mode as well as transaction +// execution status, the amount of gas used and the return value +type ExecutionResult struct { + Gas uint64 `json:"gas"` + Failed bool `json:"failed"` + ReturnValue string `json:"returnValue"` + StructLogs []StructLogRes `json:"structLogs"` +} + +// StructLogRes stores a structured log emitted by the EVM while replaying a +// transaction in debug mode +type StructLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas uint64 `json:"gas"` + GasCost uint64 `json:"gasCost"` + Depth int `json:"depth"` + Error string `json:"error,omitempty"` + Stack *[]string `json:"stack,omitempty"` + Memory *[]string `json:"memory,omitempty"` + Storage *map[string]string `json:"storage,omitempty"` +} + +// FormatLogs formats EVM returned structured logs for json output +func FormatLogs(logs []logger.StructLog) []StructLogRes { + formatted := make([]StructLogRes, len(logs)) + for index, trace := range logs { + var errString string + if trace.Err != nil { + errString = trace.Err.Error() + } + formatted[index] = StructLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Depth: trace.Depth, + Error: errString, + } + if trace.Stack != nil { + stack := make([]string, len(trace.Stack)) + for i, stackValue := range trace.Stack { + stack[i] = stackValue.Hex() + } + formatted[index].Stack = &stack + } + if trace.Memory != nil { + memory := make([]string, 0, (len(trace.Memory)+31)/32) + for i := 0; i+32 <= len(trace.Memory); i += 32 { + memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) + } + formatted[index].Memory = &memory + } + if trace.Storage != nil { + storage := make(map[string]string) + for i, storageValue := range trace.Storage { + storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) + } + formatted[index].Storage = &storage + } + } + return formatted +} + // RPCMarshalHeader converts the given header to the RPC output . func RPCMarshalHeader(head *types.Header) map[string]interface{} { result := map[string]interface{}{