@ -21,6 +21,7 @@ import (
"encoding/json"
"encoding/json"
"fmt"
"fmt"
"io"
"io"
"maps"
"math/big"
"math/big"
"strings"
"strings"
"sync/atomic"
"sync/atomic"
@ -38,15 +39,6 @@ import (
// Storage represents a contract's storage.
// Storage represents a contract's storage.
type Storage map [ common . Hash ] common . Hash
type Storage map [ common . Hash ] common . Hash
// Copy duplicates the current storage.
func ( s Storage ) Copy ( ) Storage {
cpy := make ( Storage , len ( s ) )
for key , value := range s {
cpy [ key ] = value
}
return cpy
}
// Config are the configuration options for structured logger the EVM
// Config are the configuration options for structured logger the EVM
type Config struct {
type Config struct {
EnableMemory bool // enable memory capture
EnableMemory bool // enable memory capture
@ -54,15 +46,16 @@ type Config struct {
DisableStorage bool // disable storage capture
DisableStorage bool // disable storage capture
EnableReturnData bool // enable return data capture
EnableReturnData bool // enable return data capture
Debug bool // print output during capture end
Debug bool // print output during capture end
Limit int // maximum length of output, but zero means unlimited
Limit int // maximum size of output, but zero means unlimited
// Chain overrides, can be used to execute a trace using future fork rules
// Chain overrides, can be used to execute a trace using future fork rules
Overrides * params . ChainConfig ` json:"overrides,omitempty" `
Overrides * params . ChainConfig ` json:"overrides,omitempty" `
}
}
//go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
//go:generate go run github.com/fjl/gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
// StructLog is emitted to the EVM each cycle and lists information about the current internal state
// StructLog is emitted to the EVM each cycle and lists information about the
// prior to the execution of the statement.
// current internal state prior to the execution of the statement.
type StructLog struct {
type StructLog struct {
Pc uint64 ` json:"pc" `
Pc uint64 ` json:"pc" `
Op vm . OpCode ` json:"op" `
Op vm . OpCode ` json:"op" `
@ -102,29 +95,144 @@ func (s *StructLog) ErrorString() string {
return ""
return ""
}
}
// WriteTo writes the human-readable log data into the supplied writer.
func ( s * StructLog ) WriteTo ( writer io . Writer ) {
fmt . Fprintf ( writer , "%-16spc=%08d gas=%v cost=%v" , s . Op , s . Pc , s . Gas , s . GasCost )
if s . Err != nil {
fmt . Fprintf ( writer , " ERROR: %v" , s . Err )
}
fmt . Fprintln ( writer )
if len ( s . Stack ) > 0 {
fmt . Fprintln ( writer , "Stack:" )
for i := len ( s . Stack ) - 1 ; i >= 0 ; i -- {
fmt . Fprintf ( writer , "%08d %s\n" , len ( s . Stack ) - i - 1 , s . Stack [ i ] . Hex ( ) )
}
}
if len ( s . Memory ) > 0 {
fmt . Fprintln ( writer , "Memory:" )
fmt . Fprint ( writer , hex . Dump ( s . Memory ) )
}
if len ( s . Storage ) > 0 {
fmt . Fprintln ( writer , "Storage:" )
for h , item := range s . Storage {
fmt . Fprintf ( writer , "%x: %x\n" , h , item )
}
}
if len ( s . ReturnData ) > 0 {
fmt . Fprintln ( writer , "ReturnData:" )
fmt . Fprint ( writer , hex . Dump ( s . ReturnData ) )
}
fmt . Fprintln ( writer )
}
// structLogLegacy stores a structured log emitted by the EVM while replaying a
// transaction in debug mode. It's the legacy format used in tracer. The differences
// between the structLog json and the 'legacy' json are:
//
// op:
// Legacy uses string (e.g. "SSTORE"), non-legacy uses a byte.
// non-legacy has an 'opName' field containing the op name.
//
// gas, gasCost:
// Legacy uses integers, non-legacy hex-strings
//
// memory:
// Legacy uses a list of 64-char strings, each representing 32-byte chunks
// of evm memory. Non-legacy just uses a string of hexdata, no chunking.
//
// storage:
// Legacy has a storage field while non-legacy doesn't.
type structLogLegacy struct {
Pc uint64 ` json:"pc" `
Op string ` json:"op" `
Gas uint64 ` json:"gas" `
GasCost uint64 ` json:"gasCost" `
Depth int ` json:"depth" `
Error string ` json:"error,omitempty" `
Stack * [ ] string ` json:"stack,omitempty" `
ReturnData string ` json:"returnData,omitempty" `
Memory * [ ] string ` json:"memory,omitempty" `
Storage * map [ string ] string ` json:"storage,omitempty" `
RefundCounter uint64 ` json:"refund,omitempty" `
}
// toLegacyJSON converts the structLog to legacy json-encoded legacy form.
func ( s * StructLog ) toLegacyJSON ( ) json . RawMessage {
msg := structLogLegacy {
Pc : s . Pc ,
Op : s . Op . String ( ) ,
Gas : s . Gas ,
GasCost : s . GasCost ,
Depth : s . Depth ,
Error : s . ErrorString ( ) ,
RefundCounter : s . RefundCounter ,
}
if s . Stack != nil {
stack := make ( [ ] string , len ( s . Stack ) )
for i , stackValue := range s . Stack {
stack [ i ] = stackValue . Hex ( )
}
msg . Stack = & stack
}
if len ( s . ReturnData ) > 0 {
msg . ReturnData = hexutil . Bytes ( s . ReturnData ) . String ( )
}
if s . Memory != nil {
memory := make ( [ ] string , 0 , ( len ( s . Memory ) + 31 ) / 32 )
for i := 0 ; i + 32 <= len ( s . Memory ) ; i += 32 {
memory = append ( memory , fmt . Sprintf ( "%x" , s . Memory [ i : i + 32 ] ) )
}
msg . Memory = & memory
}
if s . Storage != nil {
storage := make ( map [ string ] string )
for i , storageValue := range s . Storage {
storage [ fmt . Sprintf ( "%x" , i ) ] = fmt . Sprintf ( "%x" , storageValue )
}
msg . Storage = & storage
}
element , _ := json . Marshal ( msg )
return element
}
// StructLogger is an EVM state logger and implements EVMLogger.
// StructLogger is an EVM state logger and implements EVMLogger.
//
//
// StructLogger can capture state based on the given Log configuration and also keeps
// StructLogger can capture state based on the given Log configuration and also keeps
// a track record of modified storage which is used in reporting snapshots of the
// a track record of modified storage which is used in reporting snapshots of the
// contract their storage.
// contract their storage.
//
// A StructLogger can either yield it's output immediately (streaming) or store for
// later output.
type StructLogger struct {
type StructLogger struct {
cfg Config
cfg Config
env * tracing . VMContext
env * tracing . VMContext
storage map [ common . Address ] Storage
storage map [ common . Address ] Storage
logs [ ] StructLog
output [ ] byte
output [ ] byte
err error
err error
usedGas uint64
usedGas uint64
writer io . Writer // If set, the logger will stream instead of store logs
logs [ ] json . RawMessage // buffer of json-encoded logs
resultSize int
interrupt atomic . Bool // Atomic flag to signal execution interruption
interrupt atomic . Bool // Atomic flag to signal execution interruption
reason error // Textual reason for the interruption
reason error // Textual reason for the interruption
}
}
// NewStructLogger returns a new logger
// NewStreamingStructLogger returns a new streaming logger.
func NewStreamingStructLogger ( cfg * Config , writer io . Writer ) * StructLogger {
l := NewStructLogger ( cfg )
l . writer = writer
return l
}
// NewStructLogger construct a new (non-streaming) struct logger.
func NewStructLogger ( cfg * Config ) * StructLogger {
func NewStructLogger ( cfg * Config ) * StructLogger {
logger := & StructLogger {
logger := & StructLogger {
storage : make ( map [ common . Address ] Storage ) ,
storage : make ( map [ common . Address ] Storage ) ,
logs : make ( [ ] json . RawMessage , 0 ) ,
}
}
if cfg != nil {
if cfg != nil {
logger . cfg = * cfg
logger . cfg = * cfg
@ -141,44 +249,36 @@ func (l *StructLogger) Hooks() *tracing.Hooks {
}
}
}
}
// Reset clears the data held by the logger.
func ( l * StructLogger ) Reset ( ) {
l . storage = make ( map [ common . Address ] Storage )
l . output = make ( [ ] byte , 0 )
l . logs = l . logs [ : 0 ]
l . err = nil
}
// OnOpcode logs a new structured log message and pushes it out to the environment
// OnOpcode logs a new structured log message and pushes it out to the environment
//
//
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
// OnOpcode also tracks SLOAD/SSTORE ops to track storage change.
func ( l * StructLogger ) OnOpcode ( pc uint64 , opcode byte , gas , cost uint64 , scope tracing . OpContext , rData [ ] byte , depth int , err error ) {
func ( l * StructLogger ) OnOpcode ( pc uint64 , opcode byte , gas , cost uint64 , scope tracing . OpContext , rData [ ] byte , depth int , err error ) {
// If tracing was interrupted, set the error and stop
// If tracing was interrupted, exit
if l . interrupt . Load ( ) {
if l . interrupt . Load ( ) {
return
return
}
}
// check if already accumulated the specified number of logs
// check if already accumulated the size of the response.
if l . cfg . Limit != 0 && l . cfg . Limit <= len ( l . logs ) {
if l . cfg . Limit != 0 && l . resultSize > l . cfg . Limit {
return
return
}
}
var (
op := vm . OpCode ( opcode )
op = vm . OpCode ( opcode )
memory := scope . MemoryData ( )
memory = scope . MemoryData ( )
stack := scope . StackData ( )
contractAddr = scope . Address ( )
// Copy a snapshot of the current memory state to a new buffer
stack = scope . StackData ( )
var mem [ ] byte
stackLen = len ( stack )
)
log := StructLog { pc , op , gas , cost , nil , len ( memory ) , nil , nil , nil , depth , l . env . StateDB . GetRefund ( ) , err }
if l . cfg . EnableMemory {
if l . cfg . EnableMemory {
mem = make ( [ ] byte , len ( memory ) )
log . Memory = memory
copy ( mem , memory )
}
}
// Copy a snapshot of the current stack state to a new buffer
var stck [ ] uint256 . Int
if ! l . cfg . DisableStack {
if ! l . cfg . DisableStack {
stck = make ( [ ] uint256 . Int , len ( stack ) )
log . Stack = scope . StackData ( )
copy ( stck , stack )
}
if l . cfg . EnableReturnData {
log . ReturnData = rData
}
}
contractAddr := scope . Address ( )
stackLen := len ( stack )
// Copy a snapshot of the current storage to a new container
// Copy a snapshot of the current storage to a new container
var storage Storage
var storage Storage
if ! l . cfg . DisableStorage && ( op == vm . SLOAD || op == vm . SSTORE ) {
if ! l . cfg . DisableStorage && ( op == vm . SLOAD || op == vm . SSTORE ) {
@ -194,7 +294,7 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
value = l . env . StateDB . GetState ( contractAddr , address )
value = l . env . StateDB . GetState ( contractAddr , address )
)
)
l . storage [ contractAddr ] [ address ] = value
l . storage [ contractAddr ] [ address ] = value
storage = l . storage [ contractAddr ] . Copy ( )
storage = maps . Clone ( l . storage [ contractAddr ] )
} else if op == vm . SSTORE && stackLen >= 2 {
} else if op == vm . SSTORE && stackLen >= 2 {
// capture SSTORE opcodes and record the written entry in the local storage.
// capture SSTORE opcodes and record the written entry in the local storage.
var (
var (
@ -202,17 +302,19 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope
address = common . Hash ( stack [ stackLen - 1 ] . Bytes32 ( ) )
address = common . Hash ( stack [ stackLen - 1 ] . Bytes32 ( ) )
)
)
l . storage [ contractAddr ] [ address ] = value
l . storage [ contractAddr ] [ address ] = value
storage = l . storage [ contractAddr ] . Copy ( )
storage = maps . Clone ( l . storage [ contractAddr ] )
}
}
}
}
var rdata [ ] byte
log . Storage = storage
if l . cfg . EnableReturnData {
rdata = make ( [ ] byte , len ( rData ) )
// create a log
copy ( rdata , rData )
if l . writer == nil {
entry := log . toLegacyJSON ( )
l . resultSize += len ( entry )
l . logs = append ( l . logs , entry )
return
}
}
// create a new snapshot of the EVM.
log . WriteTo ( l . writer )
log := StructLog { pc , op , gas , cost , mem , len ( memory ) , stck , rdata , storage , depth , l . env . StateDB . GetRefund ( ) , err }
l . logs = append ( l . logs , log )
}
}
// OnExit is called a call frame finishes processing.
// OnExit is called a call frame finishes processing.
@ -246,7 +348,7 @@ func (l *StructLogger) GetResult() (json.RawMessage, error) {
Gas : l . usedGas ,
Gas : l . usedGas ,
Failed : failed ,
Failed : failed ,
ReturnValue : returnVal ,
ReturnValue : returnVal ,
StructLogs : formatLogs ( l . StructLogs ( ) ) ,
StructLogs : l . logs ,
} )
} )
}
}
@ -273,9 +375,6 @@ func (l *StructLogger) OnTxEnd(receipt *types.Receipt, err error) {
}
}
}
}
// StructLogs returns the captured log entries.
func ( l * StructLogger ) StructLogs ( ) [ ] StructLog { return l . logs }
// Error returns the VM error captured by the trace.
// Error returns the VM error captured by the trace.
func ( l * StructLogger ) Error ( ) error { return l . err }
func ( l * StructLogger ) Error ( ) error { return l . err }
@ -283,49 +382,10 @@ func (l *StructLogger) Error() error { return l.err }
func ( l * StructLogger ) Output ( ) [ ] byte { return l . output }
func ( l * StructLogger ) Output ( ) [ ] byte { return l . output }
// WriteTrace writes a formatted trace to the given writer
// WriteTrace writes a formatted trace to the given writer
// @deprecated
func WriteTrace ( writer io . Writer , logs [ ] StructLog ) {
func WriteTrace ( writer io . Writer , logs [ ] StructLog ) {
for _ , log := range logs {
for _ , log := range logs {
fmt . Fprintf ( writer , "%-16spc=%08d gas=%v cost=%v" , log . Op , log . Pc , log . Gas , log . GasCost )
log . WriteTo ( writer )
if log . Err != nil {
fmt . Fprintf ( writer , " ERROR: %v" , log . Err )
}
fmt . Fprintln ( writer )
if len ( log . Stack ) > 0 {
fmt . Fprintln ( writer , "Stack:" )
for i := len ( log . Stack ) - 1 ; i >= 0 ; i -- {
fmt . Fprintf ( writer , "%08d %s\n" , len ( log . Stack ) - i - 1 , log . Stack [ i ] . Hex ( ) )
}
}
if len ( log . Memory ) > 0 {
fmt . Fprintln ( writer , "Memory:" )
fmt . Fprint ( writer , hex . Dump ( log . Memory ) )
}
if len ( log . Storage ) > 0 {
fmt . Fprintln ( writer , "Storage:" )
for h , item := range log . Storage {
fmt . Fprintf ( writer , "%x: %x\n" , h , item )
}
}
if len ( log . ReturnData ) > 0 {
fmt . Fprintln ( writer , "ReturnData:" )
fmt . Fprint ( writer , hex . Dump ( log . ReturnData ) )
}
fmt . Fprintln ( writer )
}
}
// WriteLogs writes vm logs in a readable format to the given writer
func WriteLogs ( writer io . Writer , logs [ ] * types . Log ) {
for _ , log := range logs {
fmt . Fprintf ( writer , "LOG%d: %x bn=%d txi=%x\n" , len ( log . Topics ) , log . Address , log . BlockNumber , log . TxIndex )
for i , topic := range log . Topics {
fmt . Fprintf ( writer , "%08d %x\n" , i , topic )
}
fmt . Fprint ( writer , hex . Dump ( log . Data ) )
fmt . Fprintln ( writer )
}
}
}
}
@ -428,62 +488,5 @@ type ExecutionResult struct {
Gas uint64 ` json:"gas" `
Gas uint64 ` json:"gas" `
Failed bool ` json:"failed" `
Failed bool ` json:"failed" `
ReturnValue string ` json:"returnValue" `
ReturnValue string ` json:"returnValue" `
StructLogs [ ] StructLogRes ` json:"structLogs" `
StructLogs [ ] json . RawMessage ` json:"structLogs" `
}
// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 ` json:"pc" `
Op string ` json:"op" `
Gas uint64 ` json:"gas" `
GasCost uint64 ` json:"gasCost" `
Depth int ` json:"depth" `
Error string ` json:"error,omitempty" `
Stack * [ ] string ` json:"stack,omitempty" `
ReturnData string ` json:"returnData,omitempty" `
Memory * [ ] string ` json:"memory,omitempty" `
Storage * map [ string ] string ` json:"storage,omitempty" `
RefundCounter uint64 ` json:"refund,omitempty" `
}
// formatLogs formats EVM returned structured logs for json output
func formatLogs ( logs [ ] StructLog ) [ ] StructLogRes {
formatted := make ( [ ] StructLogRes , len ( logs ) )
for index , trace := range logs {
formatted [ index ] = StructLogRes {
Pc : trace . Pc ,
Op : trace . Op . String ( ) ,
Gas : trace . Gas ,
GasCost : trace . GasCost ,
Depth : trace . Depth ,
Error : trace . ErrorString ( ) ,
RefundCounter : trace . RefundCounter ,
}
if trace . Stack != nil {
stack := make ( [ ] string , len ( trace . Stack ) )
for i , stackValue := range trace . Stack {
stack [ i ] = stackValue . Hex ( )
}
formatted [ index ] . Stack = & stack
}
if len ( trace . ReturnData ) > 0 {
formatted [ index ] . ReturnData = hexutil . Bytes ( trace . ReturnData ) . String ( )
}
if trace . Memory != nil {
memory := make ( [ ] string , 0 , ( len ( trace . Memory ) + 31 ) / 32 )
for i := 0 ; i + 32 <= len ( trace . Memory ) ; i += 32 {
memory = append ( memory , fmt . Sprintf ( "%x" , trace . Memory [ i : i + 32 ] ) )
}
formatted [ index ] . Memory = & memory
}
if trace . Storage != nil {
storage := make ( map [ string ] string )
for i , storageValue := range trace . Storage {
storage [ fmt . Sprintf ( "%x" , i ) ] = fmt . Sprintf ( "%x" , storageValue )
}
formatted [ index ] . Storage = & storage
}
}
return formatted
}
}