has call reverted to snapshot

This commit is contained in:
Sina Mahmoodi 2024-01-12 17:53:12 +03:30
parent f241a76f4a
commit 580f02527e
12 changed files with 51 additions and 34 deletions

View file

@ -17,6 +17,7 @@
package vm package vm
import ( import (
"errors"
"math/big" "math/big"
"sync/atomic" "sync/atomic"
@ -536,10 +537,16 @@ func (evm *EVM) captureEnd(isRoot bool, typ OpCode, startGas uint64, leftOverGas
if leftOverGas != 0 { if leftOverGas != 0 {
tracer.OnGasChange(leftOverGas, 0, GasChangeCallLeftOverReturned) 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 { if isRoot {
tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err)) tracer.CaptureEnd(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted)
} else { } else {
tracer.CaptureExit(ret, startGas-leftOverGas, VMErrorFromErr(err)) tracer.CaptureExit(ret, startGas-leftOverGas, VMErrorFromErr(err), reverted)
} }
} }

View file

@ -859,7 +859,7 @@ func opSelfdestruct(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext
interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address()) interpreter.evm.StateDB.SelfDestruct(scope.Contract.Address())
if tracer := interpreter.evm.Config.Tracer; tracer != nil { if tracer := interpreter.evm.Config.Tracer; tracer != nil {
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) 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 return nil, errStopToken
} }
@ -875,7 +875,7 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon
interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address()) interpreter.evm.StateDB.Selfdestruct6780(scope.Contract.Address())
if tracer := interpreter.evm.Config.Tracer; tracer != nil { if tracer := interpreter.evm.Config.Tracer; tracer != nil {
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) 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 return nil, errStopToken
} }

View file

@ -36,10 +36,18 @@ type EVMLogger interface {
CaptureTxEnd(receipt *types.Receipt, err error) CaptureTxEnd(receipt *types.Receipt, err error)
// Top call frame // Top call frame
CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) 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 // Rest of call frames
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) 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 // Opcode level
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) 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) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)

View file

@ -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. // 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. // 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 // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // 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) {} func (*NoopTracer) CaptureTxStart(env *vm.EVM, tx *types.Transaction, from common.Address) {}

View file

@ -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. // 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 { if err != nil {
t.ctx["error"] = t.vm.ToValue(err.Error()) 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 // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // 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 { if !t.traceFrame {
return return
} }

View file

@ -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.CaptureTxStart(env, types.NewTx(&types.LegacyTx{Gas: gasLimit}), contract.Caller())
tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value) tracer.CaptureStart(contract.Caller(), contract.Address(), false, []byte{}, startGas, value)
ret, err := env.Interpreter().Run(contract, []byte{}, false) 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 // Rest gas assumes no refund
tracer.CaptureTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil) tracer.CaptureTxEnd(&types.Receipt{GasUsed: gasLimit - contract.Gas}, nil)
if err != 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}) 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.CaptureTxStart(env, types.NewTx(&types.LegacyTx{}), common.Address{})
tracer.CaptureStart(common.Address{}, common.Address{}, false, []byte{}, 1000, big.NewInt(0)) 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() ret, err := tracer.GetResult()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -279,7 +279,7 @@ func TestEnterExit(t *testing.T) {
Contract: vm.NewContract(&account{}, &account{}, big.NewInt(0), 0), 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.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() have, err := tracer.GetResult()
if err != nil { if err != nil {

View file

@ -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. // 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) 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 // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // 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) fmt.Printf("CaptureExit: output=%s, gasUsed=%v, err=%v\n", hexutil.Bytes(output), gasUsed, err)
} }

View file

@ -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. // 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.output = output
l.err = err l.err = err
if l.cfg.Debug { 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) 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", fmt.Fprintf(t.out, "\nOutput: `%#x`\nConsumed gas: `%d`\nError: `%v`\n",
output, gasUsed, err) output, gasUsed, err)
} }

View file

@ -77,7 +77,7 @@ func (l *JSONLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
} }
// CaptureEnd is triggered at end of execution. // 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 { type endLog struct {
Output string `json:"output"` Output string `json:"output"`
GasUsed math.HexOrDecimal64 `json:"gasUsed"` GasUsed math.HexOrDecimal64 `json:"gasUsed"`

View file

@ -59,7 +59,8 @@ type callFrame struct {
Logs []callLog `json:"logs,omitempty" rlp:"optional"` Logs []callLog `json:"logs,omitempty" rlp:"optional"`
// Placed at end on purpose. The RLP will be decoded to 0 instead of // 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. // 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 { func (f callFrame) TypeString() string {
@ -67,16 +68,17 @@ func (f callFrame) TypeString() string {
} }
func (f callFrame) failed() bool { 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) output = common.CopyBytes(output)
if err == nil { if err == nil {
f.Output = output f.Output = output
return return
} }
f.Error = err.Error() f.Error = err.Error()
f.revertedSnapshot = reverted
if f.Type == vm.CREATE || f.Type == vm.CREATE2 { if f.Type == vm.CREATE || f.Type == vm.CREATE2 {
f.To = nil 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. // CaptureEnd is called after the call finishes to finalize the tracing.
func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
t.callstack[0].processOutput(output, err) t.callstack[0].processOutput(output, err, reverted)
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. // 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 // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // 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-- t.depth--
if t.config.OnlyTopCall { if t.config.OnlyTopCall {
return return
@ -195,7 +197,7 @@ func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
size -= 1 size -= 1
call.GasUsed = gasUsed call.GasUsed = gasUsed
call.processOutput(output, err) call.processOutput(output, err, reverted)
t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call) t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
} }

View file

@ -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. // CaptureEnd is called after the call finishes to finalize the tracing.
func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { func (t *flatCallTracer) CaptureEnd(output []byte, gasUsed uint64, err error, reverted bool) {
t.tracer.CaptureEnd(output, gasUsed, err) t.tracer.CaptureEnd(output, gasUsed, err, reverted)
} }
// CaptureState implements the EVMLogger interface to trace a single step of VM execution. // 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 // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // execute any code.
func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { func (t *flatCallTracer) CaptureExit(output []byte, gasUsed uint64, err error, reverted bool) {
t.tracer.CaptureExit(output, gasUsed, err) t.tracer.CaptureExit(output, gasUsed, err, reverted)
// Parity traces don't include CALL/STATICCALLs to precompiles. // Parity traces don't include CALL/STATICCALLs to precompiles.
// By default we remove them from the callstack. // By default we remove them from the callstack.

View file

@ -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. // 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 { 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 // CaptureExit is called when EVM exits a scope, even if the scope didn't
// execute any code. // 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 { for _, t := range t.tracers {
t.CaptureExit(output, gasUsed, err) t.CaptureExit(output, gasUsed, err, reverted)
} }
} }