From e1f22f4f992b813259c1f530e3d9e5dcc719b6dd Mon Sep 17 00:00:00 2001 From: Jerry Date: Mon, 25 Mar 2024 10:43:48 -0700 Subject: [PATCH] Handle edge cases of gas checking in zero tracer --- core/state/intra_block_state.go | 21 +++++++++++++ core/vm/evmtypes/evmtypes.go | 2 ++ eth/tracers/native/zero.go | 55 ++++++++++++++++++++++++++------- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/core/state/intra_block_state.go b/core/state/intra_block_state.go index cb0936f5e3a..728ee544f65 100644 --- a/core/state/intra_block_state.go +++ b/core/state/intra_block_state.go @@ -22,6 +22,7 @@ import ( "sort" "encoding/hex" + "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" types2 "github.com/ledgerwatch/erigon-lib/types" @@ -399,6 +400,26 @@ func (sdb *IntraBlockState) GetIncarnation(addr libcommon.Address) uint64 { return 0 } +func (sdb *IntraBlockState) HasLiveAccount(addr libcommon.Address) bool { + if stateObject := sdb.stateObjects[addr]; stateObject != nil { + return true + } + return false +} + +func (sdb *IntraBlockState) HasLiveState(addr libcommon.Address, key *libcommon.Hash) bool { + if stateObject := sdb.stateObjects[addr]; stateObject != nil { + if _, ok := stateObject.originStorage[*key]; ok { + return true + } + + if _, ok := stateObject.dirtyStorage[*key]; ok { + return true + } + } + return false +} + // Selfdestruct marks the given account as suicided. // This clears the account balance. // diff --git a/core/vm/evmtypes/evmtypes.go b/core/vm/evmtypes/evmtypes.go index fce3b92a39d..5827640882a 100644 --- a/core/vm/evmtypes/evmtypes.go +++ b/core/vm/evmtypes/evmtypes.go @@ -79,6 +79,8 @@ type IntraBlockState interface { GetCommittedState(libcommon.Address, *libcommon.Hash, *uint256.Int) GetState(address libcommon.Address, slot *libcommon.Hash, outValue *uint256.Int) SetState(libcommon.Address, *libcommon.Hash, uint256.Int) + HasLiveAccount(addr libcommon.Address) bool + HasLiveState(addr libcommon.Address, key *libcommon.Hash) bool Selfdestruct(libcommon.Address) bool HasSelfdestructed(libcommon.Address) bool diff --git a/eth/tracers/native/zero.go b/eth/tracers/native/zero.go index 40f228ef29e..7cdf5e62862 100644 --- a/eth/tracers/native/zero.go +++ b/eth/tracers/native/zero.go @@ -90,7 +90,8 @@ func (t *zeroTracer) CaptureTxStart(gasLimit uint64) { // CaptureState implements the EVMLogger interface to trace a single step of VM execution. func (t *zeroTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - if err != nil { + // Only continue if the error is nil or if the error is out of gas and the opcode is SSTORE, CALL, or SELFDESTRUCT + if !(err == nil || (err == vm.ErrOutOfGas && (op == vm.SSTORE || op == vm.CALL || op == vm.SELFDESTRUCT))) { return } @@ -107,16 +108,42 @@ func (t *zeroTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco switch { case stackLen >= 1 && op == vm.SLOAD: slot := libcommon.Hash(stackData[stackLen-1].Bytes32()) + t.addAccountToTrace(caller) t.addSLOADToAccount(caller, slot) case stackLen >= 1 && op == vm.SSTORE: slot := libcommon.Hash(stackData[stackLen-1].Bytes32()) + + // If the SSTORE is out of gas and the slot is in live state, we will add the slot to account read + if err == vm.ErrOutOfGas { + if t.env.IntraBlockState().HasLiveState(caller, &slot) { + t.addAccountToTrace(caller) + t.addSLOADToAccount(caller, slot) + } + return + } + t.addAccountToTrace(caller) t.addSSTOREToAccount(caller, slot, stackData[stackLen-2].Clone()) case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): addr := libcommon.Address(stackData[stackLen-1].Bytes20()) + + if err == vm.ErrOutOfGas && op == vm.SELFDESTRUCT { + if t.env.IntraBlockState().HasLiveAccount(addr) { + t.addAccountToTrace(addr) + } + return + } t.addAccountToTrace(addr) t.addOpCodeToAccount(addr, op) case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): addr := libcommon.Address(stackData[stackLen-2].Bytes20()) + + // If the call is out of gas, we will add account but not the opcode + if err == vm.ErrOutOfGas && op == vm.CALL { + if t.env.IntraBlockState().HasLiveAccount(addr) { + t.addAccountToTrace(addr) + } + return + } t.addAccountToTrace(addr) t.addOpCodeToAccount(addr, op) case op == vm.CREATE: @@ -228,17 +255,23 @@ func (t *zeroTracer) CaptureTxEnd(restGas uint64) { // We don't need to provide the actual bytecode UNLESS the opcode is the following: // DELEGATECALL, CALL, STATICCALL, CALLCODE, EXTCODECOPY, EXTCODEHASH, EXTCODESIZE - if trace.CodeUsage != nil && trace.CodeUsage.Read != nil && t.addrOpCodes[addr] != nil { - opCodes := []vm.OpCode{vm.DELEGATECALL, vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.EXTCODECOPY, - vm.EXTCODEHASH, vm.EXTCODESIZE} - keep := false - for _, opCode := range opCodes { - if _, ok := t.addrOpCodes[addr][opCode]; ok { - keep = true - break + if trace.CodeUsage != nil && trace.CodeUsage.Read != nil { + if t.addrOpCodes[addr] != nil { + // We don't need to provide the actual bytecode UNLESS the opcode is the following: + // DELEGATECALL, CALL, STATICCALL, CALLCODE, EXTCODECOPY, EXTCODEHASH, EXTCODESIZE + opCodes := []vm.OpCode{vm.DELEGATECALL, vm.CALL, vm.STATICCALL, vm.CALLCODE, vm.EXTCODECOPY, + vm.EXTCODEHASH, vm.EXTCODESIZE} + keep := false + for _, opCode := range opCodes { + if _, ok := t.addrOpCodes[addr][opCode]; ok { + keep = true + break + } } - } - if !keep { + if !keep { + trace.CodeUsage = nil + } + } else { trace.CodeUsage = nil } }