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
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)
}
}

View File

@ -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
}

View File

@ -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)

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.
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) {}

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.
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
}

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.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 {

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.
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)
}

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.
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)
}

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.
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"`

View File

@ -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)
}

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.
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.

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.
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)
}
}