-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
374 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
// Modifications Copyright 2024 The Kaia Authors | ||
// Copyright 2021 The go-ethereum Authors | ||
// This file is part of the go-ethereum library. | ||
// | ||
// The go-ethereum library is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Lesser General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// The go-ethereum library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Lesser General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public License | ||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package native | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"math/big" | ||
"sync/atomic" | ||
|
||
"github.com/klaytn/klaytn/accounts/abi" | ||
"github.com/klaytn/klaytn/blockchain/vm" | ||
"github.com/klaytn/klaytn/common" | ||
"github.com/klaytn/klaytn/common/hexutil" | ||
) | ||
|
||
var _ vm.Tracer = (*CallTracer)(nil) | ||
|
||
//go:generate go run github.com/fjl/gencodec -type CallFrame -field-override callFrameMarshaling -out gen_callframe_json.go | ||
type CallFrame struct { | ||
Type vm.OpCode `json:"-"` // e.g. CALL, DELEGATECALL, CREATE | ||
From common.Address `json:"from"` | ||
Gas uint64 `json:"gas"` // gasLeft. for top-level call, tx.gasLimit | ||
GasUsed uint64 `json:"gasUsed"` // gasUsed so far. for top-level call, tx.gasLimit - gasLeft = receipt.gasUsed | ||
To *common.Address `json:"to,omitempty"` // recipient address, created contract address, or nil for failed contract creation, | ||
Input []byte `json:"input"` | ||
Output []byte `json:"output,omitempty"` // result of an internal call or revert message or runtime bytecode | ||
Error string `json:"error,omitempty"` | ||
RevertReason string `json:"revertReason,omitempty"` // decoded revert message in geth style. | ||
Reverted *RevertedInfo `json:"reverted,omitempty"` // decoded revert message and reverted contract address in klaytn style. | ||
Calls []CallFrame `json:"calls,omitempty"` // child calls | ||
Value *big.Int `json:"value,omitempty"` | ||
} | ||
|
||
type RevertedInfo struct { | ||
Contract *common.Address `json:"contract,omitempty"` | ||
Message string `json:"message,omitempty"` | ||
} | ||
|
||
func (f CallFrame) TypeString() string { // to satisfy gencodec | ||
return f.Type.String() | ||
} | ||
|
||
// FieldType overrides for callFrame that's used for JSON encoding | ||
// Must rerun gencodec after modifying this struct | ||
type callFrameMarshaling struct { | ||
TypeString string `json:"type"` | ||
Gas hexutil.Uint64 | ||
GasUsed hexutil.Uint64 | ||
Value *hexutil.Big | ||
Input hexutil.Bytes | ||
Output hexutil.Bytes | ||
} | ||
|
||
// Populate output, error, and revert-related fields | ||
// 1. no error: {output} | ||
// 2. non-revert error: {to: nil if CREATE, error} | ||
// 3. revert error without message: {to: nil if CREATE, output, error, reverted{contract}} | ||
// 4. revert error with message: {to: nil if CREATE, output, error, reverted{contract, message}, revertReason} | ||
func (c *CallFrame) processOutput(output []byte, err error) { | ||
// 1: return output | ||
if err == nil { | ||
c.Output = common.CopyBytes(output) | ||
return | ||
} | ||
|
||
// 2,3,4: to = nil if CREATE failed | ||
if c.Type == vm.CREATE || c.Type == vm.CREATE2 { | ||
c.To = nil | ||
} | ||
|
||
// 2: do not return output | ||
if !errors.Is(err, vm.ErrExecutionReverted) { // non-revert error | ||
c.Error = err.Error() | ||
return | ||
} | ||
|
||
// 3,4: return output and revert info | ||
c.Output = common.CopyBytes(output) | ||
c.Error = "execution reverted" | ||
c.Reverted = &RevertedInfo{Contract: c.To} // 'To' was recorded when entering this call frame | ||
|
||
// 4: attach revert reason | ||
if reason, unpackErr := abi.UnpackRevert(output); unpackErr == nil { | ||
c.RevertReason = reason | ||
c.Reverted.Message = reason | ||
} | ||
} | ||
|
||
// Implements vm.Tracer interface | ||
type CallTracer struct { | ||
callstack []CallFrame | ||
gasLimit uint64 // saved tx.gasLimit | ||
interrupt atomic.Bool | ||
interruptReason error | ||
} | ||
|
||
func NewCallTracer() *CallTracer { | ||
return &CallTracer{ | ||
callstack: make([]CallFrame, 1), // empty top-level frame | ||
} | ||
} | ||
|
||
// Transaction start | ||
func (t *CallTracer) CaptureTxStart(gasLimit uint64) { | ||
t.gasLimit = gasLimit | ||
} | ||
|
||
// Transaction end | ||
func (t *CallTracer) CaptureTxEnd(gasLeft uint64) { | ||
t.callstack[0].GasUsed = t.callstack[0].Gas - gasLeft | ||
} | ||
|
||
// Enter top-level call frame | ||
func (t *CallTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { | ||
toCopy := to | ||
t.callstack[0] = CallFrame{ | ||
Type: vm.CALL, | ||
From: from, | ||
To: &toCopy, | ||
Input: common.CopyBytes(input), | ||
Gas: t.gasLimit, // ignore 'gas' supplied from EVM. Use tx.gasLimit that includes intrinsic gas. | ||
Value: value, | ||
} | ||
if create { | ||
t.callstack[0].Type = vm.CREATE | ||
} | ||
} | ||
|
||
// Exit top-level call frame | ||
func (t *CallTracer) CaptureEnd(output []byte, gasUsed uint64, err error) { | ||
// gasUsed will be filled by CaptureTxEnd; just process the output | ||
t.callstack[0].processOutput(output, err) | ||
} | ||
|
||
// Enter nested call frame | ||
func (t *CallTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { | ||
if t.interrupt.Load() { | ||
return | ||
} | ||
|
||
toCopy := to | ||
call := CallFrame{ | ||
Type: typ, | ||
From: from, | ||
Gas: gas, | ||
To: &toCopy, | ||
Value: value, | ||
Input: common.CopyBytes(input), | ||
} | ||
t.callstack = append(t.callstack, call) | ||
} | ||
|
||
// Exit nested call frame | ||
func (t *CallTracer) CaptureExit(output []byte, gasUsed uint64, err error) { | ||
size := len(t.callstack) | ||
if size <= 1 { // just in case; should never happen though because CaptureExit is only called when depth > 0 | ||
return | ||
} | ||
|
||
// process output into the currently exiting call | ||
call := t.callstack[size-1] | ||
call.GasUsed = gasUsed | ||
call.processOutput(output, err) | ||
|
||
// pop current frame | ||
t.callstack = t.callstack[:size-1] | ||
|
||
// append it to the parent frame's Calls | ||
t.callstack[size-2].Calls = append(t.callstack[size-2].Calls, call) | ||
} | ||
|
||
// Each opcode | ||
func (t *CallTracer) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost, ccLeft, ccOpcode uint64, scope *vm.ScopeContext, depth int, err error) { | ||
} | ||
|
||
// Fault during opcode execution | ||
func (t *CallTracer) CaptureFault(env *vm.EVM, pc uint64, op vm.OpCode, gas, cost, ccLeft, ccOpcode uint64, scope *vm.ScopeContext, depth int, err error) { | ||
} | ||
|
||
func (t *CallTracer) GetResult() (json.RawMessage, error) { | ||
if len(t.callstack) != 1 { | ||
return nil, errors.New("incorrect number of top-level calls") | ||
} | ||
|
||
result, err := json.Marshal(t.callstack[0]) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Return with interrupt reason if any | ||
return result, t.interruptReason | ||
} | ||
|
||
// Stop terminates execution of the tracer at the first opportune moment. | ||
// For CallTracer, it stops at CaptureEnter, which is the most repetitive operation. | ||
func (t *CallTracer) Stop(err error) { | ||
t.interrupt.Store(true) | ||
t.interruptReason = err | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.