From 580f02527e65dfb5252f681c6d5507a5874d97f1 Mon Sep 17 00:00:00 2001 From: Sina Mahmoodi Date: Fri, 12 Jan 2024 17:53:12 +0330 Subject: [PATCH] has call reverted to snapshot --- core/vm/evm.go | 13 ++++++++++--- core/vm/instructions.go | 4 ++-- core/vm/logger.go | 12 ++++++++++-- eth/tracers/directory/noop.go | 4 ++-- eth/tracers/js/goja.go | 4 ++-- eth/tracers/js/tracer_test.go | 6 +++--- eth/tracers/live/printer.go | 4 ++-- eth/tracers/logger/logger.go | 4 ++-- eth/tracers/logger/logger_json.go | 2 +- eth/tracers/native/call.go | 16 +++++++++------- eth/tracers/native/call_flat.go | 8 ++++---- eth/tracers/native/mux.go | 8 ++++---- 12 files changed, 51 insertions(+), 34 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index e003084c6b..d2617283dd 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,6 +17,7 @@ package vm import ( + "errors" "math/big" "sync/atomic" @@ -536,10 +537,16 @@ func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas if leftOverGas != 0 { tracer.OnGasChange(leftOverGas, 0, GasChangeCallLeftOverReturned) } - + var reverted bool + if err != nil { + reverted = true + } + if !evm.chainRules.IsHomestead && errors.Is(err, ErrCodeStoreOutOfGas) { + reverted = false + } if isRoot { - tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err)) + tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) } else { - tracer.CaptureExit(ret, startGas-leftOverGas, VMErrorFromErr(err)) + tracer.CaptureExit(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) } } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index a70b4bea77..b9c5b153b7 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -859,7 +859,7 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) if tracer := interpreter.evm.Config.Tracer; tracer != nil { tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) - tracer.CaptureExit([]byte{}, 0, nil) + tracer.CaptureExit([]byte{}, 0, nil, false) } return nil, errStopToken } @@ -875,7 +875,7 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) if tracer := interpreter.evm.Config.Tracer; tracer != nil { tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) - tracer.CaptureExit([]byte{}, 0, nil) + tracer.CaptureExit([]byte{}, 0, nil, false) } return nil, errStopToken } diff --git a/core/vm/logger.go b/core/vm/logger.go index 94c499b0a4..e86bb5d98e 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -36,10 +36,18 @@ type EVMLogger interface { CaptureTxEnd(receipt *types.Receipt, err error) // Top call frame CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) - CaptureEnd(output []byte, gasUsed uint64, err error) + // CaptureEnd is invoked when the processing of the top call ends. + // See docs for `CaptureExit` for info on the `reverted` parameter. + CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) // Rest of call frames CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) - CaptureExit(output []byte, gasUsed uint64, err error) + // CaptureExit is invoked when the processing of a message ends. + // `revert` is true when there was an error during the execution. + // Exceptionally, before the homestead hardfork a contract creation that + // ran out of gas when attempting to persist the code to database did not + // count as a call failure and did not cause a revert of the call. This will + // be indicated by `reverted == false` and `err == ErrCodeStoreOutOfGas`. + CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) // Opcode level CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) diff --git a/eth/tracers/directory/noop.go b/eth/tracers/directory/noop.go index f02884e33b..4aeb15ae30 100644 --- a/eth/tracers/directory/noop.go +++ b/eth/tracers/directory/noop.go @@ -44,7 +44,7 @@ func (t *NoopTracer) CaptureStart(from common.Address, to common.Address, create } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *NoopTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { +func (t *NoopTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. @@ -67,7 +67,7 @@ func (t *NoopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +func (t *NoopTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { } func (*NoopTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {} diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index 4d45e4fdc7..73b106f4ee 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -331,7 +331,7 @@ func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { +func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { if err != nil { t.ctx["error"] = t.vm.ToValue(err.Error()) } @@ -369,7 +369,7 @@ func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Ad // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { if !t.traceFrame { return } diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index a247b99318..11c44fbcc4 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -77,7 +77,7 @@ func runTrace(tracer directory.Tracer, vmctx *vmContext, chaincfg *params.ChainC tracer.CaptureTxStart(env, types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller()) tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value) ret, err := env.Interpreter().Run(contract, []byte{}, false) - tracer.CaptureEnd(ret, startGas-contract.Gas, err) + tracer.CaptureEnd(ret, startGas-contract.Gas, err, true) // Rest gas assumes no refund tracer.CaptureTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) if err != nil { @@ -209,7 +209,7 @@ func TestNoStepExec(t *testing.T) { env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{GasPrice: big.NewInt(100)}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: tracer}) tracer.CaptureTxStart(env, types.NewTx(&types.LegacyTx{}), common.Address{}) tracer.CaptureStart(common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0)) - tracer.CaptureEnd(nil, 0, nil) + tracer.CaptureEnd(nil, 0, nil, false) ret, err := tracer.GetResult() if err != nil { t.Fatal(err) @@ -279,7 +279,7 @@ func TestEnterExit(t *testing.T) { Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), } tracer.CaptureEnter(vm.CALL, scope.Contract.Caller(), scope.Contract.Address(), []byte{}, 1000, new(big.Int)) - tracer.CaptureExit([]byte{}, 400, nil) + tracer.CaptureExit([]byte{}, 400, nil, false) have, err := tracer.GetResult() if err != nil { diff --git a/eth/tracers/live/printer.go b/eth/tracers/live/printer.go index 727dc3dc16..55da7352e0 100644 --- a/eth/tracers/live/printer.go +++ b/eth/tracers/live/printer.go @@ -31,7 +31,7 @@ func (p *Printer) CaptureStart(from common.Address, to common.Address, create bo } // CaptureEnd is called after the call finishes to finalize the tracing. -func (p *Printer) CaptureEnd(output []byte, gasUsed uint64, err error) { +func (p *Printer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { fmt.Printf("CaptureEnd: output=%s, gasUsed=%v, err=%v\n", hexutil.Bytes(output), gasUsed, err) } @@ -55,7 +55,7 @@ func (p *Printer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Add // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (p *Printer) CaptureExit(output []byte, gasUsed uint64, err error) { +func (p *Printer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { fmt.Printf("CaptureExit: output=%s, gasUsed=%v, err=%v\n", hexutil.Bytes(output), gasUsed, err) } diff --git a/eth/tracers/logger/logger.go b/eth/tracers/logger/logger.go index a56dc47c72..c3bc494280 100644 --- a/eth/tracers/logger/logger.go +++ b/eth/tracers/logger/logger.go @@ -210,7 +210,7 @@ func (l *StructLogger) CaptureState(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) { +func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { l.output = output l.err = err if l.cfg.Debug { @@ -377,7 +377,7 @@ func (t *mdLogger) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) } -func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { +func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n", output, gasUsed, err) } diff --git a/eth/tracers/logger/logger_json.go b/eth/tracers/logger/logger_json.go index 96d6f1a101..1bf88f8899 100644 --- a/eth/tracers/logger/logger_json.go +++ b/eth/tracers/logger/logger_json.go @@ -77,7 +77,7 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco } // CaptureEnd is triggered at end of execution. -func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { +func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { type endLog struct { Output string `json:"output"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` diff --git a/eth/tracers/native/call.go b/eth/tracers/native/call.go index f2515f4dbd..43bd0198da 100644 --- a/eth/tracers/native/call.go +++ b/eth/tracers/native/call.go @@ -59,7 +59,8 @@ type callFrame struct { Logs []callLog `json:"logs,omitempty" rlp:"optional"` // Placed at end on purpose. The RLP will be decoded to 0 instead of // nil if there are non-empty elements after in the struct. - Value *big.Int `json:"value,omitempty" rlp:"optional"` + Value *big.Int `json:"value,omitempty" rlp:"optional"` + revertedSnapshot bool } func (f callFrame) TypeString() string { @@ -67,16 +68,17 @@ func (f callFrame) TypeString() string { } func (f callFrame) failed() bool { - return len(f.Error) > 0 + return len(f.Error) > 0 && f.revertedSnapshot } -func (f *callFrame) processOutput(output []byte, err error) { +func (f *callFrame) processOutput(output []byte, err error, reverted bool) { output = common.CopyBytes(output) if err == nil { f.Output = output return } f.Error = err.Error() + f.revertedSnapshot = reverted if f.Type == vm.CREATE || f.Type == vm.CREATE2 { f.To = nil } @@ -147,8 +149,8 @@ func (t *callTracer) CaptureStart(from common.Address, to common.Address, create } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { - t.callstack[0].processOutput(output, err) +func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { + t.callstack[0].processOutput(output, err, reverted) } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. @@ -180,7 +182,7 @@ func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common. // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { t.depth-- if t.config.OnlyTopCall { return @@ -195,7 +197,7 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) { size -= 1 call.GasUsed = gasUsed - call.processOutput(output, err) + call.processOutput(output, err, reverted) t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) } diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index dbbe6901a3..26f114f44f 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -151,8 +151,8 @@ func (t *flatCallTracer) CaptureStart(from common.Address, to common.Address, cr } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { - t.tracer.CaptureEnd(output, gasUsed, err) +func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { + t.tracer.CaptureEnd(output, gasUsed, err, reverted) } // CaptureState implements the EVMLogger interface to trace a single step of VM execution. @@ -178,8 +178,8 @@ func (t *flatCallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to com // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { - t.tracer.CaptureExit(output, gasUsed, err) +func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { + t.tracer.CaptureExit(output, gasUsed, err, reverted) // Parity traces don't include CALL/STATICCALLs to precompiles. // By default we remove them from the callstack. diff --git a/eth/tracers/native/mux.go b/eth/tracers/native/mux.go index c39d840876..62657fe8cc 100644 --- a/eth/tracers/native/mux.go +++ b/eth/tracers/native/mux.go @@ -68,9 +68,9 @@ func (t *muxTracer) CaptureStart(from common.Address, to common.Address, create } // CaptureEnd is called after the call finishes to finalize the tracing. -func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { +func (t *muxTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) { for _, t := range t.tracers { - t.CaptureEnd(output, gasUsed, err) + t.CaptureEnd(output, gasUsed, err, reverted) } } @@ -111,9 +111,9 @@ func (t *muxTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.A // CaptureExit is called when EVM exits a scope, even if the scope didn't // execute any code. -func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error) { +func (t *muxTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) { for _, t := range t.tracers { - t.CaptureExit(output, gasUsed, err) + t.CaptureExit(output, gasUsed, err, reverted) } }