From 4f572c3b8bd5aaedac7187dbc946388d5118946f Mon Sep 17 00:00:00 2001 From: sjnam Date: Thu, 12 Oct 2023 10:27:20 +0900 Subject: [PATCH 1/3] call frame tracing --- blockchain/vm/evm.go | 95 +++++++-- blockchain/vm/instructions.go | 41 ++-- blockchain/vm/internaltx_tracer.go | 8 +- blockchain/vm/logger.go | 17 +- blockchain/vm/logger_json.go | 7 +- node/cn/tracers/api.go | 2 +- node/cn/tracers/tracer.go | 317 +++++++++++++++++++++++------ node/cn/tracers/tracer_test.go | 61 +++++- node/cn/tracers/tracers_test.go | 14 +- 9 files changed, 438 insertions(+), 124 deletions(-) diff --git a/blockchain/vm/evm.go b/blockchain/vm/evm.go index 2fe5f21780..7f74f6b1da 100644 --- a/blockchain/vm/evm.go +++ b/blockchain/vm/evm.go @@ -242,9 +242,14 @@ func (evm *EVM) Call(caller types.ContractRef, addr common.Address, input []byte precompiles := evm.GetPrecompiledContractMap(caller.Address()) if precompiles[addr] == nil || value.Sign() != 0 { // Return an error if an enabled precompiled address is called or a value is transferred to a precompiled address. - if debug && evm.depth == 0 { - evm.Config.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) - evm.Config.Tracer.CaptureEnd(ret, 0, nil) + if debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + evm.Config.Tracer.CaptureEnd(ret, 0, nil) + } else { + evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureExit(ret, 0, nil) + } } return nil, gas, kerrors.ErrPrecompiledContractAddress } @@ -259,9 +264,14 @@ func (evm *EVM) Call(caller types.ContractRef, addr common.Address, input []byte if !evm.StateDB.Exist(addr) { if value.Sign() == 0 { // Calling a non-existing account (probably contract), don't do anything, but ping the tracer - if debug && evm.depth == 0 { - evm.Config.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) - evm.Config.Tracer.CaptureEnd(ret, 0, nil) + if debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + evm.Config.Tracer.CaptureEnd(ret, 0, nil) + } else { + evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + evm.Config.Tracer.CaptureExit(ret, 0, nil) + } } return nil, gas, nil } @@ -270,11 +280,19 @@ func (evm *EVM) Call(caller types.ContractRef, addr common.Address, input []byte } evm.Context.Transfer(evm.StateDB, caller.Address(), to.Address(), value) - if debug && evm.depth == 0 { - evm.Config.Tracer.CaptureStart(caller.Address(), addr, false, input, gas, value) - defer func(startGas uint64) { - evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err) - }(gas) + if debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + defer func(startGas uint64) { // Lazy evaluation of the parameters + evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err) + }(gas) + } else { + // Handle tracer events for entering and exiting a call frame + evm.Config.Tracer.CaptureEnter(CALL, caller.Address(), addr, input, gas, value) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } } if isProgramAccount(evm, caller.Address(), addr, evm.StateDB) { @@ -331,6 +349,15 @@ func (evm *EVM) CallCode(caller types.ContractRef, addr common.Address, input [] snapshot = evm.StateDB.Snapshot() to = AccountRef(caller.Address()) ) + + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Debug { + evm.Config.Tracer.CaptureEnter(CALLCODE, caller.Address(), addr, input, gas, value) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } + // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, to, value, gas) @@ -370,6 +397,18 @@ func (evm *EVM) DelegateCall(caller types.ContractRef, addr common.Address, inpu to = AccountRef(caller.Address()) ) + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Debug { + // NOTE: caller must, at all times be a contract. It should never happen + // that caller is something other than a Contract. + parent := caller.(*Contract) + // DELEGATECALL inherits value from parent call + evm.Config.Tracer.CaptureEnter(DELEGATECALL, caller.Address(), addr, input, gas, parent.value) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } + // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, to, nil, gas).AsDelegate() contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) @@ -413,6 +452,15 @@ func (evm *EVM) StaticCall(caller types.ContractRef, addr common.Address, input to = AccountRef(addr) snapshot = evm.StateDB.Snapshot() ) + + // Invoke tracer hooks that signal entering/exiting a call frame + if evm.Config.Debug { + evm.Config.Tracer.CaptureEnter(STATICCALL, caller.Address(), addr, input, gas, nil) + defer func(startGas uint64) { + evm.Config.Tracer.CaptureExit(ret, startGas-gas, err) + }(gas) + } + // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. contract := NewContract(caller, to, new(big.Int), gas) @@ -444,7 +492,7 @@ func (c *codeAndHash) Hash() common.Hash { } // Create creates a new contract using code as deployment code. -func (evm *EVM) create(caller types.ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, humanReadable bool, codeFormat params.CodeFormat) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) create(caller types.ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode, humanReadable bool, codeFormat params.CodeFormat) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { // Depth check execution. Fail if we're trying to execute above the // limit. if evm.depth > int(params.CallCreateDepth) { @@ -502,8 +550,12 @@ func (evm *EVM) create(caller types.ContractRef, codeAndHash *codeAndHash, gas u return nil, address, gas, nil } - if evm.Config.Debug && evm.depth == 0 { - evm.Config.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value) + if evm.Config.Debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) + } else { + evm.Config.Tracer.CaptureEnter(typ, caller.Address(), address, codeAndHash.code, gas, value) + } } ret, err = evm.interpreter.Run(contract, nil) @@ -549,9 +601,14 @@ func (evm *EVM) create(caller types.ContractRef, codeAndHash *codeAndHash, gas u err = ErrInvalidCode } - if evm.Config.Debug && evm.depth == 0 { - evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, err) + if evm.Config.Debug { + if evm.depth == 0 { + evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, err) + } else { + evm.Config.Tracer.CaptureExit(ret, gas-contract.Gas, err) + } } + return ret, address, contract.Gas, err } @@ -559,7 +616,7 @@ func (evm *EVM) create(caller types.ContractRef, codeAndHash *codeAndHash, gas u func (evm *EVM) Create(caller types.ContractRef, code []byte, gas uint64, value *big.Int, codeFormat params.CodeFormat) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address())) - return evm.create(caller, codeAndHash, gas, value, contractAddr, false, codeFormat) + return evm.create(caller, codeAndHash, gas, value, contractAddr, CREATE, false, codeFormat) } // Create2 creates a new contract using code as deployment code. @@ -569,14 +626,14 @@ func (evm *EVM) Create(caller types.ContractRef, code []byte, gas uint64, value func (evm *EVM) Create2(caller types.ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int, codeFormat params.CodeFormat) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes()) - return evm.create(caller, codeAndHash, gas, endowment, contractAddr, false, codeFormat) + return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2, false, codeFormat) } // CreateWithAddress creates a new contract using code as deployment code with given address and humanReadable. func (evm *EVM) CreateWithAddress(caller types.ContractRef, code []byte, gas uint64, value *big.Int, contractAddr common.Address, humanReadable bool, codeFormat params.CodeFormat) ([]byte, common.Address, uint64, error) { codeAndHash := &codeAndHash{code: code} codeAndHash.Hash() - return evm.create(caller, codeAndHash, gas, value, contractAddr, humanReadable, codeFormat) + return evm.create(caller, codeAndHash, gas, value, contractAddr, CREATE, humanReadable, codeFormat) } func (evm *EVM) GetPrecompiledContractMap(addr common.Address) map[common.Address]PrecompiledContract { diff --git a/blockchain/vm/instructions.go b/blockchain/vm/instructions.go index a07f9caeb2..2e864edbb7 100644 --- a/blockchain/vm/instructions.go +++ b/blockchain/vm/instructions.go @@ -884,21 +884,6 @@ func opStop(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { return nil, nil } -func opSelfdestruct(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { - // TODO-klaytn: call frame tracing https://github.com/ethereum/go-ethereum/pull/23087 - // beneficiary := scope.Stack.pop() - balance := evm.StateDB.GetBalance(scope.Contract.Address()) - evm.StateDB.AddBalance(common.BigToAddress(scope.Stack.pop()), balance) - evm.StateDB.SelfDestruct(scope.Contract.Address()) - - // if evm.interpreter.cfg.Debug { - // evm.interpreter.cfg.Tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), common.BigToAddress(beneficiary), []byte{}, 0, balance) - // evm.interpreter.cfg.Tracer.CaptureExit([]byte{}, 0, nil) - // } - - return nil, nil -} - // opPush1 is a specialized version of pushN func opPush1(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { var ( @@ -914,6 +899,21 @@ func opPush1(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { return nil, nil } +func opSelfdestruct(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { + if evm.interpreter.readOnly { + return nil, ErrWriteProtection + } + beneficiary := scope.Stack.pop() + balance := evm.StateDB.GetBalance(scope.Contract.Address()) + evm.StateDB.AddBalance(common.BigToAddress(beneficiary), balance) + evm.StateDB.SelfDestruct(scope.Contract.Address()) + if tracer := evm.Config.Tracer; tracer != nil { + tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), common.BigToAddress(beneficiary), []byte{}, 0, balance) + tracer.CaptureExit([]byte{}, 0, nil) + } + return nil, nil +} + func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { if evm.interpreter.readOnly { return nil, ErrWriteProtection @@ -923,13 +923,10 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro evm.StateDB.SubBalance(scope.Contract.Address(), balance) evm.StateDB.AddBalance(common.BigToAddress(beneficiary), balance) evm.StateDB.SelfDestruct6780(scope.Contract.Address()) - - // TODO-klaytn: call frame tracing https://github.com/ethereum/go-ethereum/pull/23087 - // if tracer := interpreter.evm.Config.Tracer; tracer != nil { - // tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), common.BigToAddress(beneficiary), []byte{}, 0, balance) - // tracer.CaptureExit([]byte{}, 0, nil) - // } - + if tracer := evm.Config.Tracer; tracer != nil { + tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), common.BigToAddress(beneficiary), []byte{}, 0, balance) + tracer.CaptureExit([]byte{}, 0, nil) + } return nil, nil } diff --git a/blockchain/vm/internaltx_tracer.go b/blockchain/vm/internaltx_tracer.go index f71f9d1c9f..5be72aedd1 100644 --- a/blockchain/vm/internaltx_tracer.go +++ b/blockchain/vm/internaltx_tracer.go @@ -159,7 +159,7 @@ type RevertedInfo struct { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (this *InternalTxTracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (this *InternalTxTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { this.ctx["type"] = CALL.String() if create { this.ctx["type"] = CREATE.String() @@ -168,6 +168,7 @@ func (this *InternalTxTracer) CaptureStart(from common.Address, to common.Addres this.ctx["to"] = to this.ctx["input"] = hexutil.Encode(input) this.ctx["gas"] = gas + this.ctx["gasPrice"] = env.TxContext.GasPrice this.ctx["value"] = value } @@ -418,6 +419,11 @@ func (this *InternalTxTracer) CaptureEnd(output []byte, gasUsed uint64, err erro } } +func (this *InternalTxTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +func (this *InternalTxTracer) CaptureExit(output []byte, gasUsed uint64, err error) {} + func (this *InternalTxTracer) CaptureTxStart(gasLimit uint64) { this.gasLimit = gasLimit } diff --git a/blockchain/vm/logger.go b/blockchain/vm/logger.go index e744885bd6..de2d69a192 100644 --- a/blockchain/vm/logger.go +++ b/blockchain/vm/logger.go @@ -101,12 +101,18 @@ func (s *StructLog) ErrorString() string { // Note that reference types are actual VM data structures; make copies // if you need to retain them beyond the current call. type Tracer interface { + // Transaction level CaptureTxStart(gasLimit uint64) CaptureTxEnd(restGas uint64) - CaptureStart(from common.Address, to common.Address, call bool, input []byte, gas uint64, value *big.Int) + // Top call frame + CaptureStart(env *EVM, from common.Address, to common.Address, call bool, input []byte, gas uint64, value *big.Int) + CaptureEnd(output []byte, gasUsed uint64, err error) + // 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) + // Opcode level CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) - CaptureEnd(output []byte, gasUsed uint64, err error) } // StructLogger is an EVM state logger and implements Tracer. @@ -135,7 +141,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (l *StructLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { } // CaptureState logs a new structured log message and pushes it out to the environment @@ -207,6 +213,11 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { } } +func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} + func (l *StructLogger) CaptureTxStart(gasLimit uint64) {} func (l *StructLogger) CaptureTxEnd(restGas uint64) {} diff --git a/blockchain/vm/logger_json.go b/blockchain/vm/logger_json.go index 1aa60a3757..c510d27a96 100644 --- a/blockchain/vm/logger_json.go +++ b/blockchain/vm/logger_json.go @@ -40,7 +40,7 @@ func NewJSONLogger(cfg *LogConfig, writer io.Writer) *JSONLogger { return &JSONLogger{json.NewEncoder(writer), cfg} } -func (l *JSONLogger) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (l *JSONLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { } // CaptureState outputs state information on the logger. @@ -85,6 +85,11 @@ func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, err error) { l.encoder.Encode(endLog{common.Bytes2Hex(output), math.HexOrDecimal64(gasUsed), errMsg}) } +func (l *JSONLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +} + +func (l *JSONLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} + func (l *JSONLogger) CaptureTxStart(gasLimit uint64) {} func (l *JSONLogger) CaptureTxEnd(restGas uint64) {} diff --git a/node/cn/tracers/api.go b/node/cn/tracers/api.go index 1a46f7c3fe..ad7953bcde 100644 --- a/node/cn/tracers/api.go +++ b/node/cn/tracers/api.go @@ -908,7 +908,7 @@ func (api *API) traceTx(ctx context.Context, message blockchain.Message, blockCt tracer = vm.NewInternalTxTracer() } else { // Construct the JavaScript tracer to execute with - if tracer, err = New(*config.Tracer, api.unsafeTrace); err != nil { + if tracer, err = New(*config.Tracer, new(Context), api.unsafeTrace); err != nil { return nil, err } } diff --git a/node/cn/tracers/tracer.go b/node/cn/tracers/tracer.go index 7810c5660c..ff46e457f9 100644 --- a/node/cn/tracers/tracer.go +++ b/node/cn/tracers/tracer.go @@ -286,6 +286,85 @@ func (cw *contractWrapper) pushObject(vm *duktape.Context) { vm.PutPropString(obj, "getInput") } +type frame struct { + typ *string + from *common.Address + to *common.Address + input []byte + gas *uint + value *big.Int +} + +func newFrame() *frame { + return &frame{ + typ: new(string), + from: new(common.Address), + to: new(common.Address), + gas: new(uint), + } +} + +func (f *frame) pushObject(vm *duktape.Context) { + obj := vm.PushObject() + + vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.typ); return 1 }) + vm.PutPropString(obj, "getType") + + vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.from); return 1 }) + vm.PutPropString(obj, "getFrom") + + vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.to); return 1 }) + vm.PutPropString(obj, "getTo") + + vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, f.input); return 1 }) + vm.PutPropString(obj, "getInput") + + vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.gas); return 1 }) + vm.PutPropString(obj, "getGas") + + vm.PushGoFunction(func(ctx *duktape.Context) int { + if f.value != nil { + pushValue(ctx, f.value) + } else { + ctx.PushUndefined() + } + return 1 + }) + vm.PutPropString(obj, "getValue") +} + +type frameResult struct { + gasUsed *uint + output []byte + errorValue *string +} + +func newFrameResult() *frameResult { + return &frameResult{ + gasUsed: new(uint), + } +} + +func (r *frameResult) pushObject(vm *duktape.Context) { + obj := vm.PushObject() + + vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *r.gasUsed); return 1 }) + vm.PutPropString(obj, "getGasUsed") + + vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, r.output); return 1 }) + vm.PutPropString(obj, "getOutput") + + vm.PushGoFunction(func(ctx *duktape.Context) int { + if r.errorValue != nil { + pushValue(ctx, *r.errorValue) + } else { + ctx.PushUndefined() + } + return 1 + }) + vm.PutPropString(obj, "getError") +} + // Tracer provides an implementation of Tracer that evaluates a Javascript // function for each VM execution step. type Tracer struct { @@ -309,13 +388,26 @@ type Tracer struct { errorValue *string // Swappable error value wrapped by a log accessor refundValue *uint // Swappable refund value wrapped by a log accessor + frame *frame // Represents entry into call frame. Fields are swappable + frameResult *frameResult // Represents exit from a call frame. Fields are swappable + ctx map[string]interface{} // Transaction context gathered throughout execution err error // Error, if one has occurred interrupt uint32 // Atomic flag to signal execution interruption reason error // Textual reason for the interruption - gasLimit uint64 // Amount of gas bought for the whole tx + gasLimit uint64 // Amount of gas bought for the whole tx + traceSteps bool // When true, will invoke step() on each opcode + traceCallFrames bool // When true, will invoke enter() and exit() js funcs +} + +// Context contains some contextual infos for a transaction execution that is not +// available from within the EVM object. +type Context struct { + BlockHash common.Hash // Hash of the block the tx is contained within (zero if dangling tx or call) + TxIndex int // Index of the transaction within a block (zero if dangling tx or call) + TxHash common.Hash // Hash of the transaction being traced (zero if dangling call) } // New instantiates a new tracer instance. code specifies either a predefined @@ -323,7 +415,7 @@ type Tracer struct { // returning an object with 'step', 'fault' and 'result' functions. // However, if unsafeTrace is false, code should specify predefined tracer name, // otherwise error is returned. -func New(code string, unsafeTrace bool) (*Tracer, error) { +func New(code string, ctx *Context, unsafeTrace bool) (*Tracer, error) { // Resolve any tracers by name and assemble the tracer object foundTracer, ok := tracer(code) if ok { @@ -344,6 +436,16 @@ func New(code string, unsafeTrace bool) (*Tracer, error) { costValue: new(uint), depthValue: new(uint), refundValue: new(uint), + frame: newFrame(), + frameResult: newFrameResult(), + } + if ctx.BlockHash != (common.Hash{}) { + tracer.ctx["blockHash"] = ctx.BlockHash + + if ctx.TxHash != (common.Hash{}) { + tracer.ctx["txIndex"] = ctx.TxIndex + tracer.ctx["txHash"] = ctx.TxHash + } } // Set up builtins for this environment tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int { @@ -437,9 +539,7 @@ func New(code string, unsafeTrace bool) (*Tracer, error) { } tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself - if !tracer.vm.GetPropString(tracer.tracerObject, "step") { - return nil, fmt.Errorf("Trace object must expose a function step()") - } + hasStep := tracer.vm.GetPropString(tracer.tracerObject, "step") tracer.vm.Pop() if !tracer.vm.GetPropString(tracer.tracerObject, "fault") { @@ -452,6 +552,23 @@ func New(code string, unsafeTrace bool) (*Tracer, error) { } tracer.vm.Pop() + hasEnter := tracer.vm.GetPropString(tracer.tracerObject, "enter") + tracer.vm.Pop() + hasExit := tracer.vm.GetPropString(tracer.tracerObject, "exit") + tracer.vm.Pop() + + if hasEnter != hasExit { + return nil, fmt.Errorf("trace object must expose either both or none of enter() and exit()") + } + if !hasStep { + // If there's no step function, the enter and exit must be present + if !hasEnter { + return nil, fmt.Errorf("trace object must expose either step() or both enter() and exit()") + } + } + tracer.traceCallFrames = hasEnter + tracer.traceSteps = hasStep + // Tracer is valid, inject the big int library to access large numbers tracer.vm.EvalString(bigIntegerJS) tracer.vm.PutGlobalString("bigInt") @@ -500,6 +617,12 @@ func New(code string, unsafeTrace bool) (*Tracer, error) { tracer.vm.PutPropString(tracer.stateObject, "log") + tracer.frame.pushObject(tracer.vm) + tracer.vm.PutPropString(tracer.stateObject, "frame") + + tracer.frameResult.pushObject(tracer.vm) + tracer.vm.PutPropString(tracer.stateObject, "frameResult") + tracer.dbWrapper.pushObject(tracer.vm) tracer.vm.PutPropString(tracer.stateObject, "db") @@ -554,7 +677,7 @@ func wrapError(context string, err error) error { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (jst *Tracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { jst.ctx["type"] = "CALL" if create { jst.ctx["type"] = "CREATE" @@ -563,44 +686,50 @@ func (jst *Tracer) CaptureStart(from common.Address, to common.Address, create b jst.ctx["to"] = to jst.ctx["input"] = input jst.ctx["gas"] = gas + jst.ctx["gasPrice"] = env.TxContext.GasPrice jst.ctx["value"] = value jst.ctx["intrinsicGas"] = jst.gasLimit - gas } // CaptureState implements the Tracer interface to trace a single step of VM execution. func (jst *Tracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { - if jst.err == nil { - // Initialize the context if it wasn't done yet - if !jst.inited { - jst.ctx["block"] = env.Context.BlockNumber.Uint64() - jst.inited = true - } - // If tracing was interrupted, set the error and stop - if atomic.LoadUint32(&jst.interrupt) > 0 { - jst.err = jst.reason - return - } - jst.opWrapper.op = op - jst.stackWrapper.stack = scope.Stack - jst.memoryWrapper.memory = scope.Memory - jst.contractWrapper.contract = scope.Contract - jst.dbWrapper.db = env.StateDB - - *jst.pcValue = uint(pc) - *jst.gasValue = uint(gas) - *jst.costValue = uint(cost) - *jst.depthValue = uint(depth) - *jst.refundValue = uint(env.StateDB.GetRefund()) - - jst.errorValue = nil - if err != nil { - jst.errorValue = new(string) - *jst.errorValue = err.Error() - } - _, err := jst.call(true, "step", "log", "db") - if err != nil { - jst.err = wrapError("step", err) - } + if !jst.traceSteps { + return + } + if jst.err != nil { + return + } + // Initialize the context if it wasn't done yet + if !jst.inited { + jst.ctx["block"] = env.Context.BlockNumber.Uint64() + jst.inited = true + } + // If tracing was interrupted, set the error and stop + if atomic.LoadUint32(&jst.interrupt) > 0 { + jst.err = jst.reason + // env.Cancel() + return + } + jst.opWrapper.op = op + jst.stackWrapper.stack = scope.Stack + jst.memoryWrapper.memory = scope.Memory + jst.contractWrapper.contract = scope.Contract + jst.dbWrapper.db = env.StateDB + + *jst.pcValue = uint(pc) + *jst.gasValue = uint(gas) + *jst.costValue = uint(cost) + *jst.depthValue = uint(depth) + *jst.refundValue = uint(env.StateDB.GetRefund()) + + jst.errorValue = nil + if err != nil { + jst.errorValue = new(string) + *jst.errorValue = err.Error() + } + _, err = jst.call(true, "step", "log", "db") + if err != nil { + jst.err = wrapError("step", err) } } @@ -629,6 +758,63 @@ func (jst *Tracer) CaptureEnd(output []byte, gasUsed uint64, err error) { } } +// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). +func (jst *Tracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if !jst.traceCallFrames { + return + } + if jst.err != nil { + return + } + // If tracing was interrupted, set the error and stop + if atomic.LoadUint32(&jst.interrupt) > 0 { + jst.err = jst.reason + return + } + + *jst.frame.typ = typ.String() + *jst.frame.from = from + *jst.frame.to = to + jst.frame.input = common.CopyBytes(input) + *jst.frame.gas = uint(gas) + jst.frame.value = nil + if value != nil { + jst.frame.value = new(big.Int).SetBytes(value.Bytes()) + } + + if _, err := jst.call(true, "enter", "frame"); err != nil { + jst.err = wrapError("enter", err) + } +} + +// CaptureExit is called when EVM exits a scope, even if the scope didn't +// execute any code. +func (jst *Tracer) CaptureExit(output []byte, gasUsed uint64, err error) { + if !jst.traceCallFrames { + return + } + if jst.err != nil { + return + } + // If tracing was interrupted, set the error and stop + if atomic.LoadUint32(&jst.interrupt) > 0 { + jst.err = jst.reason + return + } + + jst.frameResult.output = common.CopyBytes(output) + *jst.frameResult.gasUsed = uint(gasUsed) + jst.frameResult.errorValue = nil + if err != nil { + jst.frameResult.errorValue = new(string) + *jst.frameResult.errorValue = err.Error() + } + + if _, err := jst.call(true, "exit", "frameResult"); err != nil { + jst.err = wrapError("exit", err) + } +} + func (jst *Tracer) CaptureTxStart(gasLimit uint64) { jst.gasLimit = gasLimit } @@ -643,28 +829,7 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) { obj := jst.vm.PushObject() for key, val := range jst.ctx { - switch val := val.(type) { - case uint64: - jst.vm.PushUint(uint(val)) - - case string: - jst.vm.PushString(val) - - case []byte: - ptr := jst.vm.PushFixedBuffer(len(val)) - copy(makeSlice(ptr, uint(len(val))), val[:]) - - case common.Address: - ptr := jst.vm.PushFixedBuffer(20) - copy(makeSlice(ptr, 20), val[:]) - - case *big.Int: - pushBigInt(val, jst.vm) - - default: - panic(fmt.Sprintf("unsupported type: %T", val)) - } - jst.vm.PutPropString(obj, key) + jst.addToObj(obj, key, val) } jst.vm.PutPropString(jst.stateObject, "ctx") @@ -679,3 +844,35 @@ func (jst *Tracer) GetResult() (json.RawMessage, error) { return result, jst.err } + +// addToObj pushes a field to a JS object. +func (jst *Tracer) addToObj(obj int, key string, val interface{}) { + pushValue(jst.vm, val) + jst.vm.PutPropString(obj, key) +} + +func pushValue(ctx *duktape.Context, val interface{}) { + switch val := val.(type) { + case uint64: + ctx.PushUint(uint(val)) + case string: + ctx.PushString(val) + case []byte: + ptr := ctx.PushFixedBuffer(len(val)) + copy(makeSlice(ptr, uint(len(val))), val) + case common.Address: + ptr := ctx.PushFixedBuffer(20) + copy(makeSlice(ptr, 20), val[:]) + case *big.Int: + pushBigInt(val, ctx) + case int: + ctx.PushInt(val) + case uint: + ctx.PushUint(val) + case common.Hash: + ptr := ctx.PushFixedBuffer(32) + copy(makeSlice(ptr, 32), val[:]) + default: + panic(fmt.Sprintf("unsupported type: %T", val)) + } +} diff --git a/node/cn/tracers/tracer_test.go b/node/cn/tracers/tracer_test.go index 6b6486d3c4..65b4952689 100644 --- a/node/cn/tracers/tracer_test.go +++ b/node/cn/tracers/tracer_test.go @@ -56,6 +56,15 @@ type dummyStatedb struct { func (*dummyStatedb) GetRefund() uint64 { return 1337 } +type vmContext struct { + blockCtx vm.BlockContext + txCtx vm.TxContext +} + +func testCtx() *vmContext { + return &vmContext{blockCtx: vm.BlockContext{BlockNumber: big.NewInt(1)}, txCtx: vm.TxContext{GasPrice: big.NewInt(100000)}} +} + func runTrace(tracer *Tracer) (json.RawMessage, error) { env := vm.NewEVM(vm.BlockContext{BlockNumber: big.NewInt(1)}, vm.TxContext{}, &dummyStatedb{}, params.TestChainConfig, &vm.Config{Debug: true, Tracer: tracer}) @@ -71,7 +80,7 @@ func runTrace(tracer *Tracer) (json.RawMessage, error) { // TestRegressionPanicSlice tests that we don't panic on bad arguments to memory access func TestRegressionPanicSlice(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", true) + tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.memory.slice(-1,-2)); }, fault: function() {}, result: function() { return this.depths; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -82,7 +91,7 @@ func TestRegressionPanicSlice(t *testing.T) { // TestRegressionPanicSlice tests that we don't panic on bad arguments to stack peeks func TestRegressionPanicPeek(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", true) + tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.peek(-1)); }, fault: function() {}, result: function() { return this.depths; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -93,7 +102,7 @@ func TestRegressionPanicPeek(t *testing.T) { // TestRegressionPanicSlice tests that we don't panic on bad arguments to memory getUint func TestRegressionPanicGetUint(t *testing.T) { - tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", true) + tracer, err := New("{ depths: [], step: function(log, db) { this.depths.push(log.memory.getUint(-64));}, fault: function() {}, result: function() { return this.depths; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -104,7 +113,7 @@ func TestRegressionPanicGetUint(t *testing.T) { // TestTracingDeepObject tests if it returns an expected error when the json object has too many recursive children func TestTracingDeepObject(t *testing.T) { - tracer, err := New("{step: function() {}, fault: function() {}, result: function() { var o={}; var x=o; for (var i=0; i<1000; i++){ o.foo={}; o=o.foo; } return x; }}", true) + tracer, err := New("{step: function() {}, fault: function() {}, result: function() { var o={}; var x=o; for (var i=0; i<1000; i++){ o.foo={}; o=o.foo; } return x; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -117,7 +126,7 @@ func TestTracingDeepObject(t *testing.T) { } func TestTracing(t *testing.T) { - tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", true) + tracer, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -132,14 +141,14 @@ func TestTracing(t *testing.T) { } func TestUnsafeTracingDisabled(t *testing.T) { - _, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", false) + _, err := New("{count: 0, step: function() { this.count += 1; }, fault: function() {}, result: function() { return this.count; }}", new(Context), false) if err == nil || err.Error() != "Only predefined tracers are supported" { t.Fatal("Must disable JS code based tracers if unsafe") } } func TestStack(t *testing.T) { - tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", true) + tracer, err := New("{depths: [], step: function(log) { this.depths.push(log.stack.length()); }, fault: function() {}, result: function() { return this.depths; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -154,7 +163,7 @@ func TestStack(t *testing.T) { } func TestOpcodes(t *testing.T) { - tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", true) + tracer, err := New("{opcodes: [], step: function(log) { this.opcodes.push(log.op.toString()); }, fault: function() {}, result: function() { return this.opcodes; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -172,7 +181,7 @@ func TestHalt(t *testing.T) { t.Skip("duktape doesn't support abortion") timeout := errors.New("stahp") - tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", true) + tracer, err := New("{step: function() { while(1); }, result: function() { return null; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -188,7 +197,7 @@ func TestHalt(t *testing.T) { } func TestHaltBetweenSteps(t *testing.T) { - tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", true) + tracer, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }}", new(Context), true) if err != nil { t.Fatal(err) } @@ -205,3 +214,35 @@ func TestHaltBetweenSteps(t *testing.T) { t.Errorf("Expected timeout error, got %v", err) } } + +func TestEnterExit(t *testing.T) { + // test that either both or none of enter() and exit() are defined + if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}}", new(Context), true); err == nil { + t.Fatal("tracer creation should've failed without exit() definition") + } + if _, err := New("{step: function() {}, fault: function() {}, result: function() { return null; }, enter: function() {}, exit: function() {}}", new(Context), true); err != nil { + t.Fatal(err) + } + + // test that the enter and exit method are correctly invoked and the values passed + tracer, err := New("{enters: 0, exits: 0, enterGas: 0, gasUsed: 0, step: function() {}, fault: function() {}, result: function() { return {enters: this.enters, exits: this.exits, enterGas: this.enterGas, gasUsed: this.gasUsed} }, enter: function(frame) { this.enters++; this.enterGas = frame.getGas(); }, exit: function(res) { this.exits++; this.gasUsed = res.getGasUsed(); }}", new(Context), true) + if err != nil { + t.Fatal(err) + } + + scope := &vm.ScopeContext{ + 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) + + have, err := tracer.GetResult() + if err != nil { + t.Fatal(err) + } + want := `{"enters":1,"exits":1,"enterGas":1000,"gasUsed":400}` + if string(have) != want { + t.Errorf("Number of invocations of enter() and exit() is wrong. Have %s, want %s\n", have, want) + } +} diff --git a/node/cn/tracers/tracers_test.go b/node/cn/tracers/tracers_test.go index ac8d14cdd7..cedc62e034 100644 --- a/node/cn/tracers/tracers_test.go +++ b/node/cn/tracers/tracers_test.go @@ -24,8 +24,8 @@ import ( "crypto/ecdsa" "crypto/rand" "encoding/json" - "io/ioutil" "math/big" + "os" "path/filepath" "strings" "testing" @@ -135,7 +135,7 @@ func TestPrestateTracerCreate2(t *testing.T) { } statedb := tests.MakePreState(database.NewMemoryDBManager(), alloc) // Create the tracer, the EVM environment and run it - tracer, err := New("prestateTracer", false) + tracer, err := New("prestateTracer", new(Context), false) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -234,7 +234,7 @@ func covertToCallTrace(t *testing.T, internalTx *vm.InternalTxTrace) *callTrace // Iterates over all the input-output datasets in the tracer test harness and // runs the JavaScript tracers against them. func TestCallTracer(t *testing.T) { - files, err := ioutil.ReadDir("testdata") + files, err := os.ReadDir("testdata") if err != nil { t.Fatalf("failed to retrieve tracer test suite: %v", err) } @@ -247,7 +247,7 @@ func TestCallTracer(t *testing.T) { // t.Parallel() // Call tracer test found, read if from disk - blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name())) + blob, err := os.ReadFile(filepath.Join("testdata", file.Name())) if err != nil { t.Fatalf("failed to read testcase: %v", err) } @@ -304,7 +304,7 @@ func TestCallTracer(t *testing.T) { statedb := tests.MakePreState(database.NewMemoryDBManager(), test.Genesis.Alloc) // Create the tracer, the EVM environment and run it - tracer, err := New("callTracer", false) + tracer, err := New("callTracer", new(Context), false) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } @@ -347,7 +347,7 @@ func jsonEqual(t *testing.T, x, y interface{}) { // Iterates over all the input-output datasets in the tracer test harness and // runs the InternalCallTracer against them. func TestInternalCallTracer(t *testing.T) { - files, err := ioutil.ReadDir("testdata") + files, err := os.ReadDir("testdata") if err != nil { t.Fatalf("failed to retrieve tracer test suite: %v", err) } @@ -360,7 +360,7 @@ func TestInternalCallTracer(t *testing.T) { // t.Parallel() // Call tracer test found, read if from disk - blob, err := ioutil.ReadFile(filepath.Join("testdata", file.Name())) + blob, err := os.ReadFile(filepath.Join("testdata", file.Name())) if err != nil { t.Fatalf("failed to read testcase: %v", err) } From bb910021bb9b6e8e156ee41ca43fcb1dc7fc2884 Mon Sep 17 00:00:00 2001 From: sjnam Date: Thu, 12 Oct 2023 17:21:17 +0900 Subject: [PATCH 2/3] add js call frame tracer --- .../tracers/internal/tracers/call_tracer.js | 355 ++++-------------- 1 file changed, 77 insertions(+), 278 deletions(-) diff --git a/node/cn/tracers/internal/tracers/call_tracer.js b/node/cn/tracers/internal/tracers/call_tracer.js index 9f3a22ff49..2e0ca79e1c 100644 --- a/node/cn/tracers/internal/tracers/call_tracer.js +++ b/node/cn/tracers/internal/tracers/call_tracer.js @@ -18,17 +18,11 @@ // This file is derived from eth/tracers/internal/tracers/call_tracer.js (2019/01/15). // Modified and improved for the klaytn development. // -// callTracer is a full blown transaction tracer that extracts and reports all -// the internal calls made by a transaction, along with any useful information. +// callFrameTracer uses the new call frame tracing methods to report useful information +// about internal messages of a transaction. { - // callstack is the current recursive call stack of the EVM execution. - callstack: [{}], + callstack: [{}], revertedContract: "", - - // descended tracks whether we've just descended from an outer transaction into - // an inner call. - descended: false, - toAscii: function(hex) { var str = ""; var i = 0, l = hex.length; @@ -42,271 +36,76 @@ return str; }, + fault: function(log, db) { + var len = this.callstack.length + if (len > 1) { + var call = this.callstack.pop() + if (this.callstack[len-1].calls === undefined) { + this.callstack[len-1].calls = [] + } + this.callstack[len-1].calls.push(call) + } + }, + result: function(ctx, db) { + // Prepare outer message info + var result = { + type: ctx.type, + from: toHex(ctx.from), + to: toHex(ctx.to), + value: '0x' + ctx.value.toString(16), + gas: '0x' + bigInt(ctx.gas).toString(16), + gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16), + input: toHex(ctx.input), + output: toHex(ctx.output), + } + if (this.callstack[0].calls !== undefined) { + result.calls = this.callstack[0].calls + } + if (this.callstack[0].error !== undefined) { + result.error = this.callstack[0].error + } else if (ctx.error !== undefined) { + result.error = ctx.error + } + if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) { + delete result.output + } - // step is invoked for every opcode that the VM executes. - step: function(log, db) { - // Capture any errors immediately - var error = log.getError(); - if (error !== undefined) { - this.fault(log, db); - return; - } - // We only care about system opcodes, faster if we pre-check once - var syscall = (log.op.toNumber() & 0xf0) == 0xf0; - if (syscall) { - var op = log.op.toString(); - } - // If a new contract is being created, add to the call stack - if (syscall && (op == 'CREATE' || op == 'CREATE2')) { - var inOff = log.stack.peek(1).valueOf(); - var inEnd = inOff + log.stack.peek(2).valueOf(); - - // Assemble the internal call report and store for completion - var call = { - type: op, - from: toHex(log.contract.getAddress()), - input: toHex(log.memory.slice(inOff, inEnd)), - gasIn: log.getGas(), - gasCost: log.getCost(), - value: '0x' + log.stack.peek(0).toString(16) - }; - this.callstack.push(call); - this.descended = true - return; - } - // If a contract is being self destructed, gather that as a subcall too - if (syscall && op == 'SELFDESTRUCT') { - var left = this.callstack.length; - if (this.callstack[left-1] === undefined) { - this.callstack[left-1] = {}; - } - if (this.callstack[left-1].calls === undefined) { - this.callstack[left-1].calls = []; - } - this.callstack[left-1].calls.push({ - type: op, - from: toHex(log.contract.getAddress()), - to: toHex(toAddress(log.stack.peek(0).toString(16))), - gasIn: log.getGas(), - gasCost: log.getCost(), - value: '0x' + db.getBalance(log.contract.getAddress()).toString(16), - }); - return - } - // If a new method invocation is being done, add to the call stack - if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) { - // Skip any pre-compile invocations, those are just fancy opcodes - var to = toAddress(log.stack.peek(1).toString(16)); - if (isPrecompiled(to)) { - return - } - var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1); - - var inOff = log.stack.peek(2 + off).valueOf(); - var inEnd = inOff + log.stack.peek(3 + off).valueOf(); - - // Assemble the internal call report and store for completion - var call = { - type: op, - from: toHex(log.contract.getAddress()), - to: toHex(to), - input: toHex(log.memory.slice(inOff, inEnd)), - gasIn: log.getGas(), - gasCost: log.getCost(), - outOff: log.stack.peek(4 + off).valueOf(), - outLen: log.stack.peek(5 + off).valueOf() - }; - if (op != 'DELEGATECALL' && op != 'STATICCALL') { - call.value = '0x' + log.stack.peek(2).toString(16); - } - this.callstack.push(call); - this.descended = true - return; - } - // If we've just descended into an inner call, retrieve it's true allowance. We - // need to extract if from within the call as there may be funky gas dynamics - // with regard to requested and actually given gas (2300 stipend, 63/64 rule). - if (this.descended) { - if (log.getDepth() >= this.callstack.length) { - this.callstack[this.callstack.length - 1].gas = log.getGas(); - } else { - // TODO(karalabe): The call was made to a plain account. We currently don't - // have access to the true gas amount inside the call and so any amount will - // mostly be wrong since it depends on a lot of input args. Skip gas for now. - } - this.descended = false; - } - // If an existing call is returning, pop off the call stack - if (syscall && op == 'REVERT') { - // TODO-klaytn: sort the revert error out. - this.callstack[this.callstack.length - 1].error = "execution reverted"; - if(this.revertedContract == "") - { - if(this.callstack[this.callstack.length-1].to === undefined) { - this.revertedContract = toHex(log.contract.getAddress()); - } else { - this.revertedContract = this.callstack[this.callstack.length-1].to; - } - } - return; - } - if (log.getDepth() == this.callstack.length - 1) { - // Pop off the last call and get the execution results - var call = this.callstack.pop(); - - if (call.type == 'CREATE' || call.type == 'CREATE2') { - // If the call was a CREATE, retrieve the contract address and output code - call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16); - delete call.gasIn; delete call.gasCost; - - var ret = log.stack.peek(0); - if (!ret.equals(0)) { - call.to = toHex(toAddress(ret.toString(16))); - call.output = toHex(db.getCode(toAddress(ret.toString(16)))); - } else if (call.error === undefined) { - call.error = "internal failure"; // TODO(karalabe): surface these faults somehow - } - } else { - // If the call was a contract call, retrieve the gas usage and output - if (call.gas !== undefined) { - call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16); - } - var ret = log.stack.peek(0); - if (!ret.equals(0)) { - call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen)); - } else if (call.error === undefined) { - call.error = "internal failure"; // TODO(karalabe): surface these faults somehow - } - delete call.gasIn; delete call.gasCost; - delete call.outOff; delete call.outLen; - } - if (call.gas !== undefined) { - call.gas = '0x' + bigInt(call.gas).toString(16); - } - // Inject the call into the previous one - var left = this.callstack.length; - if (this.callstack[left-1] === undefined) { - this.callstack[left-1] = {}; - } - if (this.callstack[left-1].calls === undefined) { - this.callstack[left-1].calls = []; - } - this.callstack[left-1].calls.push(call); - } - }, - - // fault is invoked when the actual execution of an opcode fails. - fault: function(log, db) { - // If the topmost call already reverted, don't handle the additional fault again - if (this.callstack[this.callstack.length - 1].error !== undefined) { - return; - } - // Pop off the just failed call - var call = this.callstack.pop(); - call.error = log.getError(); - - // Consume all available gas and clean any leftovers - if (call.gas !== undefined) { - call.gas = '0x' + bigInt(call.gas).toString(16); - call.gasUsed = call.gas - } - delete call.gasIn; delete call.gasCost; - delete call.outOff; delete call.outLen; - - // Flatten the failed call into its parent - var left = this.callstack.length; - if (left > 0) { - if (this.callstack[left-1] === undefined) { - this.callstack[left-1] = {} - } - if (this.callstack[left-1].calls === undefined) { - this.callstack[left-1].calls = []; - } - this.callstack[left-1].calls.push(call); - return; - } - // Last call failed too, leave it in the stack - this.callstack.push(call); - }, - - // result is invoked when all the opcodes have been iterated over and returns - // the final result of the tracing. - result: function(ctx, db) { - // initialize undefined values - if (ctx.type === undefined) { - ctx.type = 0 - } - if (ctx.from === undefined) { - ctx.from = "" - } - if (ctx.to === undefined) { - ctx.to = "" - } - if (ctx.value === undefined) { - ctx.value = 0 - } - if (ctx.gas === undefined) { - ctx.gas = 0 - } - if (ctx.gasUsed === undefined) { - ctx.gasUsed = 0 - } - if (ctx.input === undefined) { - ctx.input = "" - } - if (ctx.output === undefined) { - ctx.output = "" - } - if (this.callstack[0] == undefined) { - this.callstack[0] = {} - } - var result = { - type: ctx.type, - from: toHex(ctx.from), - to: toHex(ctx.to), - value: '0x' + ctx.value.toString(16), - gas: '0x' + bigInt(ctx.gas).toString(16), - gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16), - input: toHex(ctx.input), - output: toHex(ctx.output), - }; - if (this.callstack[0].calls !== undefined) { - result.calls = this.callstack[0].calls; - } - if (this.callstack[0].error !== undefined) { - result.error = this.callstack[0].error; - } else if (ctx.error !== undefined) { - result.error = ctx.error; - } - if (result.error !== undefined && (result.error !== "execution reverted" || result.output === "0x")) { - delete result.output; - } - // Revert output example: - // 0x08c379a0 - // 0000000000000000000000000000000000000000000000000000000000000020 stringOffset - // 0000000000000000000000000000000000000000000000000000000000000008 stringLength - // 4141414141414141 - if (ctx.error == "evm: execution reverted") { - outputHex = toHex(ctx.output); - if (outputHex.slice(2,10) == "08c379a0") { - defaultOffset = 10; - stringOffsetHex = "0x"+outputHex.slice(defaultOffset, defaultOffset + 32*2); - stringLengthHex = "0x"+outputHex.slice(defaultOffset + 32*2, defaultOffset + 32*2 + 32*2); - try { - stringOffset = parseInt(bigInt(stringOffsetHex).toString()); - stringLength = parseInt(bigInt(stringLengthHex).toString()); - start = defaultOffset + 32*2 + stringOffset*2; - end = start + stringLength*2; - this.revertString = this.toAscii(outputHex.slice(start,end)); - } catch (e) { - this.revertString = ""; - } - } - result.reverted = {"contract": this.revertedContract, "message": this.revertString}; - } - return this.finalize(result); - }, - + return this.finalize(result) + }, + enter: function(frame) { + var call = { + type: frame.getType(), + from: toHex(frame.getFrom()), + to: toHex(frame.getTo()), + input: toHex(frame.getInput()), + gas: '0x' + bigInt(frame.getGas()).toString('16'), + } + if (frame.getValue() !== undefined){ + call.value='0x' + bigInt(frame.getValue()).toString(16) + } + this.callstack.push(call) + }, + exit: function(frameResult) { + var len = this.callstack.length + if (len > 1) { + var call = this.callstack.pop() + call.gasUsed = '0x' + bigInt(frameResult.getGasUsed()).toString('16') + var error = frameResult.getError() + if (error === undefined) { + call.output = toHex(frameResult.getOutput()) + } else { + call.error = error + if (call.type === 'CREATE' || call.type === 'CREATE2') { + delete call.to + } + } + len -= 1 + if (this.callstack[len-1].calls === undefined) { + this.callstack[len-1].calls = [] + } + this.callstack[len-1].calls.push(call) + } + }, // finalize recreates a call object using the final desired field oder for json // serialization. This is a nicety feature to pass meaningfully ordered results // to users who don't interpret it, just display it. @@ -321,19 +120,19 @@ input: call.input, output: call.output, error: call.error, + time: call.time, calls: call.calls, - reverted: call.reverted, } for (var key in sorted) { if (sorted[key] === undefined) { - delete sorted[key]; + delete sorted[key] } } if (sorted.calls !== undefined) { for (var i=0; i Date: Thu, 12 Oct 2023 22:40:04 +0900 Subject: [PATCH 3/3] don't use generic name such as 'this' for a receiver name --- blockchain/vm/internaltx_tracer.go | 276 ++++++++++++++--------------- 1 file changed, 138 insertions(+), 138 deletions(-) diff --git a/blockchain/vm/internaltx_tracer.go b/blockchain/vm/internaltx_tracer.go index 5be72aedd1..aff52028d0 100644 --- a/blockchain/vm/internaltx_tracer.go +++ b/blockchain/vm/internaltx_tracer.go @@ -159,17 +159,17 @@ type RevertedInfo struct { } // CaptureStart implements the Tracer interface to initialize the tracing operation. -func (this *InternalTxTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { - this.ctx["type"] = CALL.String() +func (t *InternalTxTracer) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { + t.ctx["type"] = CALL.String() if create { - this.ctx["type"] = CREATE.String() - } - this.ctx["from"] = from - this.ctx["to"] = to - this.ctx["input"] = hexutil.Encode(input) - this.ctx["gas"] = gas - this.ctx["gasPrice"] = env.TxContext.GasPrice - this.ctx["value"] = value + t.ctx["type"] = CREATE.String() + } + t.ctx["from"] = from + t.ctx["to"] = to + t.ctx["input"] = hexutil.Encode(input) + t.ctx["gas"] = gas + t.ctx["gasPrice"] = env.TxContext.GasPrice + t.ctx["value"] = value } // tracerLog is used to help comparing codes between this and call_tracer.js @@ -192,22 +192,22 @@ func wrapError(context string, err error) error { } // Stop terminates execution of the tracer at the first opportune moment. -func (this *InternalTxTracer) Stop(err error) { - this.reason = err - atomic.StoreUint32(&this.interrupt, 1) +func (t *InternalTxTracer) Stop(err error) { + t.reason = err + atomic.StoreUint32(&t.interrupt, 1) } // CaptureState implements the Tracer interface to trace a single step of VM execution. -func (this *InternalTxTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { - if this.err == nil { +func (t *InternalTxTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { + if t.err == nil { // Initialize the context if it wasn't done yet - if !this.initialized { - this.ctx["block"] = env.Context.BlockNumber.Uint64() - this.initialized = true + if !t.initialized { + t.ctx["block"] = env.Context.BlockNumber.Uint64() + t.initialized = true } // If tracing was interrupted, set the error and stop - if atomic.LoadUint32(&this.interrupt) > 0 { - this.err = this.reason + if atomic.LoadUint32(&t.interrupt) > 0 { + t.err = t.reason return } @@ -215,17 +215,17 @@ func (this *InternalTxTracer) CaptureState(env *EVM, pc uint64, op OpCode, gas, env, pc, op, gas, cost, scope.Memory, scope.Stack, scope.Contract, depth, err, } - err := this.step(log) + err := t.step(log) if err != nil { - this.err = wrapError("step", err) + t.err = wrapError("step", err) } } } -func (this *InternalTxTracer) step(log *tracerLog) error { +func (t *InternalTxTracer) step(log *tracerLog) error { // Capture any errors immediately if log.err != nil { - this.fault(log) + t.fault(log) return nil } @@ -247,24 +247,24 @@ func (this *InternalTxTracer) step(log *tracerLog) error { GasCost: log.cost, Value: "0x" + log.stack.Peek().Text(16), // '0x' + tracerLog.stack.peek(0).toString(16) } - this.callStack = append(this.callStack, call) - this.descended = true + t.callStack = append(t.callStack, call) + t.descended = true return nil } // If a contract is being self destructed, gather that as a subcall too if sysCall && op == SELFDESTRUCT { - left := this.callStackLength() - if this.callStack[left-1] == nil { - this.callStack[left-1] = &InternalCall{} + left := t.callStackLength() + if t.callStack[left-1] == nil { + t.callStack[left-1] = &InternalCall{} } - if this.callStack[left-1].Calls == nil { - this.callStack[left-1].Calls = []*InternalCall{} + if t.callStack[left-1].Calls == nil { + t.callStack[left-1].Calls = []*InternalCall{} } contractAddr := log.contract.Address() ret := log.stack.Peek() toAddr := common.HexToAddress(ret.Text(16)) - this.callStack[left-1].Calls = append( - this.callStack[left-1].Calls, + t.callStack[left-1].Calls = append( + t.callStack[left-1].Calls, &InternalCall{ Type: op.String(), From: &contractAddr, @@ -308,39 +308,39 @@ func (this *InternalTxTracer) step(log *tracerLog) error { if op != DELEGATECALL && op != STATICCALL { call.Value = "0x" + log.stack.Back(2).Text(16) } - this.callStack = append(this.callStack, call) - this.descended = true + t.callStack = append(t.callStack, call) + t.descended = true return nil } // If we've just descended into an inner call, retrieve it's true allowance. We // need to extract if from within the call as there may be funky gas dynamics // with regard to requested and actually given gas (2300 stipend, 63/64 rule). - if this.descended { - if log.depth >= this.callStackLength() { - this.callStack[this.callStackLength()-1].Gas = log.gas + if t.descended { + if log.depth >= t.callStackLength() { + t.callStack[t.callStackLength()-1].Gas = log.gas } else { // TODO(karalabe): The call was made to a plain account. We currently don't // have access to the true gas amount inside the call and so any amount will // mostly be wrong since it depends on a lot of input args. Skip gas for now. } - this.descended = false + t.descended = false } // If an existing call is returning, pop off the call stack - if sysCall && op == REVERT && this.callStackLength() > 0 { - this.callStack[this.callStackLength()-1].Error = errExecutionReverted - if this.revertedContract == emptyAddr { - if this.callStack[this.callStackLength()-1].To == nil { - this.revertedContract = log.contract.Address() + if sysCall && op == REVERT && t.callStackLength() > 0 { + t.callStack[t.callStackLength()-1].Error = errExecutionReverted + if t.revertedContract == emptyAddr { + if t.callStack[t.callStackLength()-1].To == nil { + t.revertedContract = log.contract.Address() } else { - this.revertedContract = *this.callStack[this.callStackLength()-1].To + t.revertedContract = *t.callStack[t.callStackLength()-1].To } } return nil } - if log.depth == this.callStackLength()-1 { + if log.depth == t.callStackLength()-1 { // Pop off the last call and get the execution results - call := this.callStackPop() + call := t.callStackPop() if call.Type == CREATE.String() || call.Type == CREATE2.String() { // If the call was a CREATE, retrieve the contract address and output code @@ -376,18 +376,18 @@ func (this *InternalTxTracer) step(log *tracerLog) error { // call.gas = '0x' + bigInt(call.gas).toString(16); } // Inject the call into the previous one - left := this.callStackLength() + left := t.callStackLength() if left == 0 { left = 1 // added to avoid index out of range in golang - this.callStack = []*InternalCall{{}} + t.callStack = []*InternalCall{{}} } - if this.callStack[left-1] == nil { - this.callStack[left-1] = &InternalCall{} + if t.callStack[left-1] == nil { + t.callStack[left-1] = &InternalCall{} } - if len(this.callStack[left-1].Calls) == 0 { - this.callStack[left-1].Calls = []*InternalCall{} + if len(t.callStack[left-1].Calls) == 0 { + t.callStack[left-1].Calls = []*InternalCall{} } - this.callStack[left-1].Calls = append(this.callStack[left-1].Calls, call) + t.callStack[left-1].Calls = append(t.callStack[left-1].Calls, call) } return nil @@ -395,159 +395,159 @@ func (this *InternalTxTracer) step(log *tracerLog) error { // CaptureFault implements the Tracer interface to trace an execution fault // while running an opcode. -func (this *InternalTxTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { - if this.err == nil { +func (t *InternalTxTracer) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { + if t.err == nil { // Apart from the error, everything matches the previous invocation - this.errValue = err.Error() + t.errValue = err.Error() log := &tracerLog{ env, pc, op, gas, cost, scope.Memory, scope.Stack, scope.Contract, depth, err, } // fault does not return an error - this.fault(log) + t.fault(log) } } // CaptureEnd is called after the call finishes to finalize the tracing. -func (this *InternalTxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { - this.ctx["output"] = hexutil.Encode(output) - this.ctx["gasUsed"] = gasUsed +func (t *InternalTxTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { + t.ctx["output"] = hexutil.Encode(output) + t.ctx["gasUsed"] = gasUsed if err != nil { - this.ctx["error"] = err + t.ctx["error"] = err } } -func (this *InternalTxTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { +func (t *InternalTxTracer) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { } -func (this *InternalTxTracer) CaptureExit(output []byte, gasUsed uint64, err error) {} +func (t *InternalTxTracer) CaptureExit(output []byte, gasUsed uint64, err error) {} -func (this *InternalTxTracer) CaptureTxStart(gasLimit uint64) { - this.gasLimit = gasLimit +func (t *InternalTxTracer) CaptureTxStart(gasLimit uint64) { + t.gasLimit = gasLimit } -func (this *InternalTxTracer) CaptureTxEnd(restGas uint64) { - this.ctx["gasUsed"] = this.gasLimit - restGas +func (t *InternalTxTracer) CaptureTxEnd(restGas uint64) { + t.ctx["gasUsed"] = t.gasLimit - restGas } -func (this *InternalTxTracer) GetResult() (*InternalTxTrace, error) { - result := this.result() +func (t *InternalTxTracer) GetResult() (*InternalTxTrace, error) { + result := t.result() // Clean up the JavaScript environment - this.reset() - return result, this.err + t.reset() + return result, t.err } // reset clears data collected during the previous tracing. // It should act like calling jst.vm.DestroyHeap() and jst.vm.Destroy() at tracers.Tracer -func (this *InternalTxTracer) reset() { - this.callStack = []*InternalCall{} - this.output = nil - - this.descended = false - this.revertedContract = common.Address{} - this.initialized = false - this.revertString = "" +func (t *InternalTxTracer) reset() { + t.callStack = []*InternalCall{} + t.output = nil + + t.descended = false + t.revertedContract = common.Address{} + t.initialized = false + t.revertString = "" } // result is invoked when all the opcodes have been iterated over and returns // the final result of the tracing. -func (this *InternalTxTracer) result() *InternalTxTrace { - if _, exist := this.ctx["type"]; !exist { - this.ctx["type"] = "" +func (t *InternalTxTracer) result() *InternalTxTrace { + if _, exist := t.ctx["type"]; !exist { + t.ctx["type"] = "" } - if _, exist := this.ctx["from"]; !exist { - this.ctx["from"] = nil + if _, exist := t.ctx["from"]; !exist { + t.ctx["from"] = nil } - if _, exist := this.ctx["to"]; !exist { - this.ctx["to"] = nil + if _, exist := t.ctx["to"]; !exist { + t.ctx["to"] = nil } - if _, exist := this.ctx["value"]; !exist { - this.ctx["value"] = big.NewInt(0) + if _, exist := t.ctx["value"]; !exist { + t.ctx["value"] = big.NewInt(0) } - if _, exist := this.ctx["gas"]; !exist { - this.ctx["gas"] = uint64(0) + if _, exist := t.ctx["gas"]; !exist { + t.ctx["gas"] = uint64(0) } - if _, exist := this.ctx["gasUsed"]; !exist { - this.ctx["gasUsed"] = uint64(0) + if _, exist := t.ctx["gasUsed"]; !exist { + t.ctx["gasUsed"] = uint64(0) } - if _, exist := this.ctx["input"]; !exist { - this.ctx["input"] = "" + if _, exist := t.ctx["input"]; !exist { + t.ctx["input"] = "" } - if _, exist := this.ctx["output"]; !exist { - this.ctx["output"] = "" + if _, exist := t.ctx["output"]; !exist { + t.ctx["output"] = "" } - if _, exist := this.ctx["time"]; !exist { - this.ctx["time"] = time.Duration(0) + if _, exist := t.ctx["time"]; !exist { + t.ctx["time"] = time.Duration(0) } - if this.callStackLength() == 0 { - this.callStack = []*InternalCall{{}} + if t.callStackLength() == 0 { + t.callStack = []*InternalCall{{}} } var from, to *common.Address - if addr, ok := this.ctx["from"].(common.Address); ok { + if addr, ok := t.ctx["from"].(common.Address); ok { from = &addr } - if addr, ok := this.ctx["to"].(common.Address); ok { + if addr, ok := t.ctx["to"].(common.Address); ok { to = &addr } result := &InternalTxTrace{ - Type: this.ctx["type"].(string), + Type: t.ctx["type"].(string), From: from, To: to, - Value: "0x" + this.ctx["value"].(*big.Int).Text(16), - Gas: this.ctx["gas"].(uint64), - GasUsed: this.ctx["gasUsed"].(uint64), - Input: this.ctx["input"].(string), - Output: this.ctx["output"].(string), - Time: this.ctx["time"].(time.Duration), + Value: "0x" + t.ctx["value"].(*big.Int).Text(16), + Gas: t.ctx["gas"].(uint64), + GasUsed: t.ctx["gasUsed"].(uint64), + Input: t.ctx["input"].(string), + Output: t.ctx["output"].(string), + Time: t.ctx["time"].(time.Duration), } nestedCalls := []*InternalTxTrace{} - for _, call := range this.callStack[0].Calls { + for _, call := range t.callStack[0].Calls { nestedCalls = append(nestedCalls, call.ToTrace()) } result.Calls = nestedCalls - if this.callStack[0].Error != nil { - result.Error = this.callStack[0].Error - } else if ctxErr, _ := this.ctx["error"]; ctxErr != nil { + if t.callStack[0].Error != nil { + result.Error = t.callStack[0].Error + } else if ctxErr := t.ctx["error"]; ctxErr != nil { result.Error = ctxErr.(error) } if result.Error != nil && (result.Error.Error() != errExecutionReverted.Error() || result.Output == "0x") { result.Output = "" // delete result.output; } - if err := this.ctx["error"]; err != nil && err.(error).Error() == ErrExecutionReverted.Error() { - outputHex := this.ctx["output"].(string) // it is already a hex string + if err := t.ctx["error"]; err != nil && err.(error).Error() == ErrExecutionReverted.Error() { + outputHex := t.ctx["output"].(string) // it is already a hex string if s, err := abi.UnpackRevert(common.FromHex(outputHex)); err == nil { - this.revertString = s + t.revertString = s } else { - this.revertString = "" + t.revertString = "" } - contract := this.revertedContract - message := this.revertString + contract := t.revertedContract + message := t.revertString result.Reverted = &RevertedInfo{Contract: &contract, Message: message} } return result } // InternalTxLogs returns the captured tracerLog entries. -func (this *InternalTxTracer) InternalTxLogs() []*InternalCall { return this.callStack } +func (t *InternalTxTracer) InternalTxLogs() []*InternalCall { return t.callStack } // fault is invoked when the actual execution of an opcode fails. -func (this *InternalTxTracer) fault(log *tracerLog) { - if this.callStackLength() == 0 { +func (t *InternalTxTracer) fault(log *tracerLog) { + if t.callStackLength() == 0 { return } // If the topmost call already reverted, don't handle the additional fault again - if this.callStack[this.callStackLength()-1].Error != nil { + if t.callStack[t.callStackLength()-1].Error != nil { return } // Pop off the just failed call - call := this.callStackPop() + call := t.callStackPop() call.Error = log.err // Consume all available gas and clean any leftovers @@ -558,31 +558,31 @@ func (this *InternalTxTracer) fault(log *tracerLog) { call.OutOff, call.OutLen = nil, nil // Flatten the failed call into its parent - left := this.callStackLength() + left := t.callStackLength() if left > 0 { - if this.callStack[left-1] == nil { - this.callStack[left-1] = &InternalCall{} + if t.callStack[left-1] == nil { + t.callStack[left-1] = &InternalCall{} } - if len(this.callStack[left-1].Calls) == 0 { - this.callStack[left-1].Calls = []*InternalCall{} + if len(t.callStack[left-1].Calls) == 0 { + t.callStack[left-1].Calls = []*InternalCall{} } - this.callStack[left-1].Calls = append(this.callStack[left-1].Calls, call) + t.callStack[left-1].Calls = append(t.callStack[left-1].Calls, call) return } // Last call failed too, leave it in the stack - this.callStack = append(this.callStack, call) + t.callStack = append(t.callStack, call) } -func (this *InternalTxTracer) callStackLength() int { - return len(this.callStack) +func (t *InternalTxTracer) callStackLength() int { + return len(t.callStack) } -func (this *InternalTxTracer) callStackPop() *InternalCall { - if this.callStackLength() == 0 { +func (t *InternalTxTracer) callStackPop() *InternalCall { + if t.callStackLength() == 0 { return &InternalCall{} } - topItem := this.callStack[this.callStackLength()-1] - this.callStack = this.callStack[:this.callStackLength()-1] + topItem := t.callStack[t.callStackLength()-1] + t.callStack = t.callStack[:t.callStackLength()-1] return topItem }