github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/eth/tracers/js/tracer.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // package js is a collection of tracers written in javascript.
    18  package js
    19  
    20  import (
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"math/big"
    25  	"strings"
    26  	"sync/atomic"
    27  	"time"
    28  	"unicode"
    29  	"unsafe"
    30  
    31  	"gopkg.in/olebedev/go-duktape.v3"
    32  
    33  	"github.com/scroll-tech/go-ethereum/common"
    34  	"github.com/scroll-tech/go-ethereum/common/hexutil"
    35  	"github.com/scroll-tech/go-ethereum/core"
    36  	"github.com/scroll-tech/go-ethereum/core/vm"
    37  	"github.com/scroll-tech/go-ethereum/crypto"
    38  	tracers2 "github.com/scroll-tech/go-ethereum/eth/tracers"
    39  	"github.com/scroll-tech/go-ethereum/eth/tracers/js/internal/tracers"
    40  	"github.com/scroll-tech/go-ethereum/log"
    41  )
    42  
    43  // camel converts a snake cased input string into a camel cased output.
    44  func camel(str string) string {
    45  	pieces := strings.Split(str, "_")
    46  	for i := 1; i < len(pieces); i++ {
    47  		pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
    48  	}
    49  	return strings.Join(pieces, "")
    50  }
    51  
    52  var assetTracers = make(map[string]string)
    53  
    54  // init retrieves the JavaScript transaction tracers included in go-ethereum.
    55  func init() {
    56  	for _, file := range tracers.AssetNames() {
    57  		name := camel(strings.TrimSuffix(file, ".js"))
    58  		assetTracers[name] = string(tracers.MustAsset(file))
    59  	}
    60  	tracers2.RegisterLookup(true, newJsTracer)
    61  }
    62  
    63  // makeSlice convert an unsafe memory pointer with the given type into a Go byte
    64  // slice.
    65  //
    66  // Note, the returned slice uses the same memory area as the input arguments.
    67  // If those are duktape stack items, popping them off **will** make the slice
    68  // contents change.
    69  func makeSlice(ptr unsafe.Pointer, size uint) []byte {
    70  	var sl = struct {
    71  		addr uintptr
    72  		len  int
    73  		cap  int
    74  	}{uintptr(ptr), int(size), int(size)}
    75  
    76  	return *(*[]byte)(unsafe.Pointer(&sl))
    77  }
    78  
    79  // popSlice pops a buffer off the JavaScript stack and returns it as a slice.
    80  func popSlice(ctx *duktape.Context) []byte {
    81  	blob := common.CopyBytes(makeSlice(ctx.GetBuffer(-1)))
    82  	ctx.Pop()
    83  	return blob
    84  }
    85  
    86  // pushBigInt create a JavaScript BigInteger in the VM.
    87  func pushBigInt(n *big.Int, ctx *duktape.Context) {
    88  	ctx.GetGlobalString("bigInt")
    89  	ctx.PushString(n.String())
    90  	ctx.Call(1)
    91  }
    92  
    93  // opWrapper provides a JavaScript wrapper around OpCode.
    94  type opWrapper struct {
    95  	op vm.OpCode
    96  }
    97  
    98  // pushObject assembles a JSVM object wrapping a swappable opcode and pushes it
    99  // onto the VM stack.
   100  func (ow *opWrapper) pushObject(vm *duktape.Context) {
   101  	obj := vm.PushObject()
   102  
   103  	vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(int(ow.op)); return 1 })
   104  	vm.PutPropString(obj, "toNumber")
   105  
   106  	vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushString(ow.op.String()); return 1 })
   107  	vm.PutPropString(obj, "toString")
   108  
   109  	vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushBoolean(ow.op.IsPush()); return 1 })
   110  	vm.PutPropString(obj, "isPush")
   111  }
   112  
   113  // memoryWrapper provides a JavaScript wrapper around vm.Memory.
   114  type memoryWrapper struct {
   115  	memory *vm.Memory
   116  }
   117  
   118  // slice returns the requested range of memory as a byte slice.
   119  func (mw *memoryWrapper) slice(begin, end int64) []byte {
   120  	if end == begin {
   121  		return []byte{}
   122  	}
   123  	if end < begin || begin < 0 {
   124  		// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
   125  		// runtime goes belly up https://github.com/golang/go/issues/15639.
   126  		log.Warn("Tracer accessed out of bound memory", "offset", begin, "end", end)
   127  		return nil
   128  	}
   129  	if mw.memory.Len() < int(end) {
   130  		// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
   131  		// runtime goes belly up https://github.com/golang/go/issues/15639.
   132  		log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", begin, "size", end-begin)
   133  		return nil
   134  	}
   135  	return mw.memory.GetCopy(begin, end-begin)
   136  }
   137  
   138  // getUint returns the 32 bytes at the specified address interpreted as a uint.
   139  func (mw *memoryWrapper) getUint(addr int64) *big.Int {
   140  	if mw.memory.Len() < int(addr)+32 || addr < 0 {
   141  		// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
   142  		// runtime goes belly up https://github.com/golang/go/issues/15639.
   143  		log.Warn("Tracer accessed out of bound memory", "available", mw.memory.Len(), "offset", addr, "size", 32)
   144  		return new(big.Int)
   145  	}
   146  	return new(big.Int).SetBytes(mw.memory.GetPtr(addr, 32))
   147  }
   148  
   149  // pushObject assembles a JSVM object wrapping a swappable memory and pushes it
   150  // onto the VM stack.
   151  func (mw *memoryWrapper) pushObject(vm *duktape.Context) {
   152  	obj := vm.PushObject()
   153  
   154  	// Generate the `slice` method which takes two ints and returns a buffer
   155  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   156  		blob := mw.slice(int64(ctx.GetInt(-2)), int64(ctx.GetInt(-1)))
   157  		ctx.Pop2()
   158  
   159  		ptr := ctx.PushFixedBuffer(len(blob))
   160  		copy(makeSlice(ptr, uint(len(blob))), blob)
   161  		return 1
   162  	})
   163  	vm.PutPropString(obj, "slice")
   164  
   165  	// Generate the `getUint` method which takes an int and returns a bigint
   166  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   167  		offset := int64(ctx.GetInt(-1))
   168  		ctx.Pop()
   169  
   170  		pushBigInt(mw.getUint(offset), ctx)
   171  		return 1
   172  	})
   173  	vm.PutPropString(obj, "getUint")
   174  }
   175  
   176  // stackWrapper provides a JavaScript wrapper around vm.Stack.
   177  type stackWrapper struct {
   178  	stack *vm.Stack
   179  }
   180  
   181  // peek returns the nth-from-the-top element of the stack.
   182  func (sw *stackWrapper) peek(idx int) *big.Int {
   183  	if len(sw.stack.Data()) <= idx || idx < 0 {
   184  		// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
   185  		// runtime goes belly up https://github.com/golang/go/issues/15639.
   186  		log.Warn("Tracer accessed out of bound stack", "size", len(sw.stack.Data()), "index", idx)
   187  		return new(big.Int)
   188  	}
   189  	return sw.stack.Back(idx).ToBig()
   190  }
   191  
   192  // pushObject assembles a JSVM object wrapping a swappable stack and pushes it
   193  // onto the VM stack.
   194  func (sw *stackWrapper) pushObject(vm *duktape.Context) {
   195  	obj := vm.PushObject()
   196  
   197  	vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushInt(len(sw.stack.Data())); return 1 })
   198  	vm.PutPropString(obj, "length")
   199  
   200  	// Generate the `peek` method which takes an int and returns a bigint
   201  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   202  		offset := ctx.GetInt(-1)
   203  		ctx.Pop()
   204  
   205  		pushBigInt(sw.peek(offset), ctx)
   206  		return 1
   207  	})
   208  	vm.PutPropString(obj, "peek")
   209  }
   210  
   211  // dbWrapper provides a JavaScript wrapper around vm.Database.
   212  type dbWrapper struct {
   213  	db vm.StateDB
   214  }
   215  
   216  // pushObject assembles a JSVM object wrapping a swappable database and pushes it
   217  // onto the VM stack.
   218  func (dw *dbWrapper) pushObject(vm *duktape.Context) {
   219  	obj := vm.PushObject()
   220  
   221  	// Push the wrapper for statedb.GetBalance
   222  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   223  		pushBigInt(dw.db.GetBalance(common.BytesToAddress(popSlice(ctx))), ctx)
   224  		return 1
   225  	})
   226  	vm.PutPropString(obj, "getBalance")
   227  
   228  	// Push the wrapper for statedb.GetNonce
   229  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   230  		ctx.PushInt(int(dw.db.GetNonce(common.BytesToAddress(popSlice(ctx)))))
   231  		return 1
   232  	})
   233  	vm.PutPropString(obj, "getNonce")
   234  
   235  	// Push the wrapper for statedb.GetCode
   236  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   237  		code := dw.db.GetCode(common.BytesToAddress(popSlice(ctx)))
   238  
   239  		ptr := ctx.PushFixedBuffer(len(code))
   240  		copy(makeSlice(ptr, uint(len(code))), code)
   241  		return 1
   242  	})
   243  	vm.PutPropString(obj, "getCode")
   244  
   245  	// Push the wrapper for statedb.GetState
   246  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   247  		hash := popSlice(ctx)
   248  		addr := popSlice(ctx)
   249  
   250  		state := dw.db.GetState(common.BytesToAddress(addr), common.BytesToHash(hash))
   251  
   252  		ptr := ctx.PushFixedBuffer(len(state))
   253  		copy(makeSlice(ptr, uint(len(state))), state[:])
   254  		return 1
   255  	})
   256  	vm.PutPropString(obj, "getState")
   257  
   258  	// Push the wrapper for statedb.Exists
   259  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   260  		ctx.PushBoolean(dw.db.Exist(common.BytesToAddress(popSlice(ctx))))
   261  		return 1
   262  	})
   263  	vm.PutPropString(obj, "exists")
   264  }
   265  
   266  // contractWrapper provides a JavaScript wrapper around vm.Contract
   267  type contractWrapper struct {
   268  	contract *vm.Contract
   269  }
   270  
   271  // pushObject assembles a JSVM object wrapping a swappable contract and pushes it
   272  // onto the VM stack.
   273  func (cw *contractWrapper) pushObject(vm *duktape.Context) {
   274  	obj := vm.PushObject()
   275  
   276  	// Push the wrapper for contract.Caller
   277  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   278  		ptr := ctx.PushFixedBuffer(20)
   279  		copy(makeSlice(ptr, 20), cw.contract.Caller().Bytes())
   280  		return 1
   281  	})
   282  	vm.PutPropString(obj, "getCaller")
   283  
   284  	// Push the wrapper for contract.Address
   285  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   286  		ptr := ctx.PushFixedBuffer(20)
   287  		copy(makeSlice(ptr, 20), cw.contract.Address().Bytes())
   288  		return 1
   289  	})
   290  	vm.PutPropString(obj, "getAddress")
   291  
   292  	// Push the wrapper for contract.Value
   293  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   294  		pushBigInt(cw.contract.Value(), ctx)
   295  		return 1
   296  	})
   297  	vm.PutPropString(obj, "getValue")
   298  
   299  	// Push the wrapper for contract.Input
   300  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   301  		blob := cw.contract.Input
   302  
   303  		ptr := ctx.PushFixedBuffer(len(blob))
   304  		copy(makeSlice(ptr, uint(len(blob))), blob)
   305  		return 1
   306  	})
   307  	vm.PutPropString(obj, "getInput")
   308  }
   309  
   310  type frame struct {
   311  	typ   *string
   312  	from  *common.Address
   313  	to    *common.Address
   314  	input []byte
   315  	gas   *uint
   316  	value *big.Int
   317  }
   318  
   319  func newFrame() *frame {
   320  	return &frame{
   321  		typ:  new(string),
   322  		from: new(common.Address),
   323  		to:   new(common.Address),
   324  		gas:  new(uint),
   325  	}
   326  }
   327  
   328  func (f *frame) pushObject(vm *duktape.Context) {
   329  	obj := vm.PushObject()
   330  
   331  	vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.typ); return 1 })
   332  	vm.PutPropString(obj, "getType")
   333  
   334  	vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.from); return 1 })
   335  	vm.PutPropString(obj, "getFrom")
   336  
   337  	vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.to); return 1 })
   338  	vm.PutPropString(obj, "getTo")
   339  
   340  	vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, f.input); return 1 })
   341  	vm.PutPropString(obj, "getInput")
   342  
   343  	vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *f.gas); return 1 })
   344  	vm.PutPropString(obj, "getGas")
   345  
   346  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   347  		if f.value != nil {
   348  			pushValue(ctx, f.value)
   349  		} else {
   350  			ctx.PushUndefined()
   351  		}
   352  		return 1
   353  	})
   354  	vm.PutPropString(obj, "getValue")
   355  }
   356  
   357  type frameResult struct {
   358  	gasUsed    *uint
   359  	output     []byte
   360  	errorValue *string
   361  }
   362  
   363  func newFrameResult() *frameResult {
   364  	return &frameResult{
   365  		gasUsed: new(uint),
   366  	}
   367  }
   368  
   369  func (r *frameResult) pushObject(vm *duktape.Context) {
   370  	obj := vm.PushObject()
   371  
   372  	vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, *r.gasUsed); return 1 })
   373  	vm.PutPropString(obj, "getGasUsed")
   374  
   375  	vm.PushGoFunction(func(ctx *duktape.Context) int { pushValue(ctx, r.output); return 1 })
   376  	vm.PutPropString(obj, "getOutput")
   377  
   378  	vm.PushGoFunction(func(ctx *duktape.Context) int {
   379  		if r.errorValue != nil {
   380  			pushValue(ctx, *r.errorValue)
   381  		} else {
   382  			ctx.PushUndefined()
   383  		}
   384  		return 1
   385  	})
   386  	vm.PutPropString(obj, "getError")
   387  }
   388  
   389  // jsTracer provides an implementation of Tracer that evaluates a Javascript
   390  // function for each VM execution step.
   391  type jsTracer struct {
   392  	vm  *duktape.Context // Javascript VM instance
   393  	env *vm.EVM          // EVM instance executing the code being traced
   394  
   395  	tracerObject int // Stack index of the tracer JavaScript object
   396  	stateObject  int // Stack index of the global state to pull arguments from
   397  
   398  	opWrapper       *opWrapper       // Wrapper around the VM opcode
   399  	stackWrapper    *stackWrapper    // Wrapper around the VM stack
   400  	memoryWrapper   *memoryWrapper   // Wrapper around the VM memory
   401  	contractWrapper *contractWrapper // Wrapper around the contract object
   402  	dbWrapper       *dbWrapper       // Wrapper around the VM environment
   403  
   404  	pcValue     *uint   // Swappable pc value wrapped by a log accessor
   405  	gasValue    *uint   // Swappable gas value wrapped by a log accessor
   406  	costValue   *uint   // Swappable cost value wrapped by a log accessor
   407  	depthValue  *uint   // Swappable depth value wrapped by a log accessor
   408  	errorValue  *string // Swappable error value wrapped by a log accessor
   409  	refundValue *uint   // Swappable refund value wrapped by a log accessor
   410  
   411  	frame       *frame       // Represents entry into call frame. Fields are swappable
   412  	frameResult *frameResult // Represents exit from a call frame. Fields are swappable
   413  
   414  	ctx map[string]interface{} // Transaction context gathered throughout execution
   415  	err error                  // Error, if one has occurred
   416  
   417  	interrupt uint32 // Atomic flag to signal execution interruption
   418  	reason    error  // Textual reason for the interruption
   419  
   420  	activePrecompiles []common.Address // Updated on CaptureStart based on given rules
   421  	traceSteps        bool             // When true, will invoke step() on each opcode
   422  	traceCallFrames   bool             // When true, will invoke enter() and exit() js funcs
   423  }
   424  
   425  // New instantiates a new tracer instance. code specifies a Javascript snippet,
   426  // which must evaluate to an expression returning an object with 'step', 'fault'
   427  // and 'result' functions.
   428  func newJsTracer(code string, ctx *tracers2.Context) (tracers2.Tracer, error) {
   429  	if c, ok := assetTracers[code]; ok {
   430  		code = c
   431  	}
   432  	if ctx == nil {
   433  		ctx = new(tracers2.Context)
   434  	}
   435  	tracer := &jsTracer{
   436  		vm:              duktape.New(),
   437  		ctx:             make(map[string]interface{}),
   438  		opWrapper:       new(opWrapper),
   439  		stackWrapper:    new(stackWrapper),
   440  		memoryWrapper:   new(memoryWrapper),
   441  		contractWrapper: new(contractWrapper),
   442  		dbWrapper:       new(dbWrapper),
   443  		pcValue:         new(uint),
   444  		gasValue:        new(uint),
   445  		costValue:       new(uint),
   446  		depthValue:      new(uint),
   447  		refundValue:     new(uint),
   448  		frame:           newFrame(),
   449  		frameResult:     newFrameResult(),
   450  	}
   451  	if ctx.BlockHash != (common.Hash{}) {
   452  		tracer.ctx["blockHash"] = ctx.BlockHash
   453  
   454  		if ctx.TxHash != (common.Hash{}) {
   455  			tracer.ctx["txIndex"] = ctx.TxIndex
   456  			tracer.ctx["txHash"] = ctx.TxHash
   457  		}
   458  	}
   459  	// Set up builtins for this environment
   460  	tracer.vm.PushGlobalGoFunction("toHex", func(ctx *duktape.Context) int {
   461  		ctx.PushString(hexutil.Encode(popSlice(ctx)))
   462  		return 1
   463  	})
   464  	tracer.vm.PushGlobalGoFunction("toWord", func(ctx *duktape.Context) int {
   465  		var word common.Hash
   466  		if ptr, size := ctx.GetBuffer(-1); ptr != nil {
   467  			word = common.BytesToHash(makeSlice(ptr, size))
   468  		} else {
   469  			word = common.HexToHash(ctx.GetString(-1))
   470  		}
   471  		ctx.Pop()
   472  		copy(makeSlice(ctx.PushFixedBuffer(32), 32), word[:])
   473  		return 1
   474  	})
   475  	tracer.vm.PushGlobalGoFunction("toAddress", func(ctx *duktape.Context) int {
   476  		var addr common.Address
   477  		if ptr, size := ctx.GetBuffer(-1); ptr != nil {
   478  			addr = common.BytesToAddress(makeSlice(ptr, size))
   479  		} else {
   480  			addr = common.HexToAddress(ctx.GetString(-1))
   481  		}
   482  		ctx.Pop()
   483  		copy(makeSlice(ctx.PushFixedBuffer(20), 20), addr[:])
   484  		return 1
   485  	})
   486  	tracer.vm.PushGlobalGoFunction("toContract", func(ctx *duktape.Context) int {
   487  		var from common.Address
   488  		if ptr, size := ctx.GetBuffer(-2); ptr != nil {
   489  			from = common.BytesToAddress(makeSlice(ptr, size))
   490  		} else {
   491  			from = common.HexToAddress(ctx.GetString(-2))
   492  		}
   493  		nonce := uint64(ctx.GetInt(-1))
   494  		ctx.Pop2()
   495  
   496  		contract := crypto.CreateAddress(from, nonce)
   497  		copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
   498  		return 1
   499  	})
   500  	tracer.vm.PushGlobalGoFunction("toContract2", func(ctx *duktape.Context) int {
   501  		var from common.Address
   502  		if ptr, size := ctx.GetBuffer(-3); ptr != nil {
   503  			from = common.BytesToAddress(makeSlice(ptr, size))
   504  		} else {
   505  			from = common.HexToAddress(ctx.GetString(-3))
   506  		}
   507  		// Retrieve salt hex string from js stack
   508  		salt := common.HexToHash(ctx.GetString(-2))
   509  		// Retrieve code slice from js stack
   510  		var code []byte
   511  		if ptr, size := ctx.GetBuffer(-1); ptr != nil {
   512  			code = common.CopyBytes(makeSlice(ptr, size))
   513  		} else {
   514  			code = common.FromHex(ctx.GetString(-1))
   515  		}
   516  		codeHash := crypto.Keccak256(code)
   517  		ctx.Pop3()
   518  		contract := crypto.CreateAddress2(from, salt, codeHash)
   519  		copy(makeSlice(ctx.PushFixedBuffer(20), 20), contract[:])
   520  		return 1
   521  	})
   522  	tracer.vm.PushGlobalGoFunction("isPrecompiled", func(ctx *duktape.Context) int {
   523  		addr := common.BytesToAddress(popSlice(ctx))
   524  		for _, p := range tracer.activePrecompiles {
   525  			if p == addr {
   526  				ctx.PushBoolean(true)
   527  				return 1
   528  			}
   529  		}
   530  		ctx.PushBoolean(false)
   531  		return 1
   532  	})
   533  	tracer.vm.PushGlobalGoFunction("slice", func(ctx *duktape.Context) int {
   534  		start, end := ctx.GetInt(-2), ctx.GetInt(-1)
   535  		ctx.Pop2()
   536  
   537  		blob := popSlice(ctx)
   538  		size := end - start
   539  
   540  		if start < 0 || start > end || end > len(blob) {
   541  			// TODO(karalabe): We can't js-throw from Go inside duktape inside Go. The Go
   542  			// runtime goes belly up https://github.com/golang/go/issues/15639.
   543  			log.Warn("Tracer accessed out of bound memory", "available", len(blob), "offset", start, "size", size)
   544  			ctx.PushFixedBuffer(0)
   545  			return 1
   546  		}
   547  		copy(makeSlice(ctx.PushFixedBuffer(size), uint(size)), blob[start:end])
   548  		return 1
   549  	})
   550  	// Push the JavaScript tracer as object #0 onto the JSVM stack and validate it
   551  	if err := tracer.vm.PevalString("(" + code + ")"); err != nil {
   552  		log.Warn("Failed to compile tracer", "err", err)
   553  		return nil, err
   554  	}
   555  	tracer.tracerObject = 0 // yeah, nice, eval can't return the index itself
   556  
   557  	hasStep := tracer.vm.GetPropString(tracer.tracerObject, "step")
   558  	tracer.vm.Pop()
   559  
   560  	if !tracer.vm.GetPropString(tracer.tracerObject, "fault") {
   561  		return nil, fmt.Errorf("trace object must expose a function fault()")
   562  	}
   563  	tracer.vm.Pop()
   564  
   565  	if !tracer.vm.GetPropString(tracer.tracerObject, "result") {
   566  		return nil, fmt.Errorf("trace object must expose a function result()")
   567  	}
   568  	tracer.vm.Pop()
   569  
   570  	hasEnter := tracer.vm.GetPropString(tracer.tracerObject, "enter")
   571  	tracer.vm.Pop()
   572  	hasExit := tracer.vm.GetPropString(tracer.tracerObject, "exit")
   573  	tracer.vm.Pop()
   574  	if hasEnter != hasExit {
   575  		return nil, fmt.Errorf("trace object must expose either both or none of enter() and exit()")
   576  	}
   577  	tracer.traceCallFrames = hasEnter && hasExit
   578  	tracer.traceSteps = hasStep
   579  
   580  	// Tracer is valid, inject the big int library to access large numbers
   581  	tracer.vm.EvalString(bigIntegerJS)
   582  	tracer.vm.PutGlobalString("bigInt")
   583  
   584  	// Push the global environment state as object #1 into the JSVM stack
   585  	tracer.stateObject = tracer.vm.PushObject()
   586  
   587  	logObject := tracer.vm.PushObject()
   588  
   589  	tracer.opWrapper.pushObject(tracer.vm)
   590  	tracer.vm.PutPropString(logObject, "op")
   591  
   592  	tracer.stackWrapper.pushObject(tracer.vm)
   593  	tracer.vm.PutPropString(logObject, "stack")
   594  
   595  	tracer.memoryWrapper.pushObject(tracer.vm)
   596  	tracer.vm.PutPropString(logObject, "memory")
   597  
   598  	tracer.contractWrapper.pushObject(tracer.vm)
   599  	tracer.vm.PutPropString(logObject, "contract")
   600  
   601  	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.pcValue); return 1 })
   602  	tracer.vm.PutPropString(logObject, "getPC")
   603  
   604  	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.gasValue); return 1 })
   605  	tracer.vm.PutPropString(logObject, "getGas")
   606  
   607  	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.costValue); return 1 })
   608  	tracer.vm.PutPropString(logObject, "getCost")
   609  
   610  	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.depthValue); return 1 })
   611  	tracer.vm.PutPropString(logObject, "getDepth")
   612  
   613  	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int { ctx.PushUint(*tracer.refundValue); return 1 })
   614  	tracer.vm.PutPropString(logObject, "getRefund")
   615  
   616  	tracer.vm.PushGoFunction(func(ctx *duktape.Context) int {
   617  		if tracer.errorValue != nil {
   618  			ctx.PushString(*tracer.errorValue)
   619  		} else {
   620  			ctx.PushUndefined()
   621  		}
   622  		return 1
   623  	})
   624  	tracer.vm.PutPropString(logObject, "getError")
   625  
   626  	tracer.vm.PutPropString(tracer.stateObject, "log")
   627  
   628  	tracer.frame.pushObject(tracer.vm)
   629  	tracer.vm.PutPropString(tracer.stateObject, "frame")
   630  
   631  	tracer.frameResult.pushObject(tracer.vm)
   632  	tracer.vm.PutPropString(tracer.stateObject, "frameResult")
   633  
   634  	tracer.dbWrapper.pushObject(tracer.vm)
   635  	tracer.vm.PutPropString(tracer.stateObject, "db")
   636  
   637  	return tracer, nil
   638  }
   639  
   640  // Stop terminates execution of the tracer at the first opportune moment.
   641  func (jst *jsTracer) Stop(err error) {
   642  	jst.reason = err
   643  	atomic.StoreUint32(&jst.interrupt, 1)
   644  }
   645  
   646  // call executes a method on a JS object, catching any errors, formatting and
   647  // returning them as error objects.
   648  func (jst *jsTracer) call(noret bool, method string, args ...string) (json.RawMessage, error) {
   649  	// Execute the JavaScript call and return any error
   650  	jst.vm.PushString(method)
   651  	for _, arg := range args {
   652  		jst.vm.GetPropString(jst.stateObject, arg)
   653  	}
   654  	code := jst.vm.PcallProp(jst.tracerObject, len(args))
   655  	defer jst.vm.Pop()
   656  
   657  	if code != 0 {
   658  		err := jst.vm.SafeToString(-1)
   659  		return nil, errors.New(err)
   660  	}
   661  	// No error occurred, extract return value and return
   662  	if noret {
   663  		return nil, nil
   664  	}
   665  	// Push a JSON marshaller onto the stack. We can't marshal from the out-
   666  	// side because duktape can crash on large nestings and we can't catch
   667  	// C++ exceptions ourselves from Go. TODO(karalabe): Yuck, why wrap?!
   668  	jst.vm.PushString("(JSON.stringify)")
   669  	jst.vm.Eval()
   670  
   671  	jst.vm.Swap(-1, -2)
   672  	if code = jst.vm.Pcall(1); code != 0 {
   673  		err := jst.vm.SafeToString(-1)
   674  		return nil, errors.New(err)
   675  	}
   676  	return json.RawMessage(jst.vm.SafeToString(-1)), nil
   677  }
   678  
   679  func wrapError(context string, err error) error {
   680  	return fmt.Errorf("%v    in server-side tracer function '%v'", err, context)
   681  }
   682  
   683  // CaptureStart implements the Tracer interface to initialize the tracing operation.
   684  func (jst *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
   685  	jst.env = env
   686  	jst.ctx["type"] = "CALL"
   687  	if create {
   688  		jst.ctx["type"] = "CREATE"
   689  	}
   690  	jst.ctx["from"] = from
   691  	jst.ctx["to"] = to
   692  	jst.ctx["input"] = input
   693  	jst.ctx["gas"] = gas
   694  	jst.ctx["gasPrice"] = env.TxContext.GasPrice
   695  	jst.ctx["value"] = value
   696  
   697  	// Initialize the context
   698  	jst.ctx["block"] = env.Context.BlockNumber.Uint64()
   699  	jst.dbWrapper.db = env.StateDB
   700  	// Update list of precompiles based on current block
   701  	rules := env.ChainConfig().Rules(env.Context.BlockNumber)
   702  	jst.activePrecompiles = vm.ActivePrecompiles(rules)
   703  
   704  	// Compute intrinsic gas
   705  	isHomestead := env.ChainConfig().IsHomestead(env.Context.BlockNumber)
   706  	isIstanbul := env.ChainConfig().IsIstanbul(env.Context.BlockNumber)
   707  	isShanghai := env.ChainConfig().IsShanghai(env.Context.BlockNumber)
   708  	intrinsicGas, err := core.IntrinsicGas(input, nil, jst.ctx["type"] == "CREATE", isHomestead, isIstanbul, isShanghai)
   709  	if err != nil {
   710  		return
   711  	}
   712  	jst.ctx["intrinsicGas"] = intrinsicGas
   713  }
   714  
   715  // CaptureState implements the Tracer interface to trace a single step of VM execution.
   716  func (jst *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
   717  	if !jst.traceSteps {
   718  		return
   719  	}
   720  	if jst.err != nil {
   721  		return
   722  	}
   723  	// If tracing was interrupted, set the error and stop
   724  	if atomic.LoadUint32(&jst.interrupt) > 0 {
   725  		jst.err = jst.reason
   726  		jst.env.Cancel()
   727  		return
   728  	}
   729  	jst.opWrapper.op = op
   730  	jst.stackWrapper.stack = scope.Stack
   731  	jst.memoryWrapper.memory = scope.Memory
   732  	jst.contractWrapper.contract = scope.Contract
   733  
   734  	*jst.pcValue = uint(pc)
   735  	*jst.gasValue = uint(gas)
   736  	*jst.costValue = uint(cost)
   737  	*jst.depthValue = uint(depth)
   738  	*jst.refundValue = uint(jst.env.StateDB.GetRefund())
   739  
   740  	jst.errorValue = nil
   741  	if err != nil {
   742  		jst.errorValue = new(string)
   743  		*jst.errorValue = err.Error()
   744  	}
   745  
   746  	if _, err := jst.call(true, "step", "log", "db"); err != nil {
   747  		jst.err = wrapError("step", err)
   748  	}
   749  }
   750  
   751  // CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
   752  func (jst *jsTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
   753  }
   754  
   755  // CaptureFault implements the Tracer interface to trace an execution fault
   756  func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
   757  	if jst.err != nil {
   758  		return
   759  	}
   760  	// Apart from the error, everything matches the previous invocation
   761  	jst.errorValue = new(string)
   762  	*jst.errorValue = err.Error()
   763  
   764  	if _, err := jst.call(true, "fault", "log", "db"); err != nil {
   765  		jst.err = wrapError("fault", err)
   766  	}
   767  }
   768  
   769  // CaptureEnd is called after the call finishes to finalize the tracing.
   770  func (jst *jsTracer) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
   771  	jst.ctx["output"] = output
   772  	jst.ctx["time"] = t.String()
   773  	jst.ctx["gasUsed"] = gasUsed
   774  
   775  	if err != nil {
   776  		jst.ctx["error"] = err.Error()
   777  	}
   778  }
   779  
   780  // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
   781  func (jst *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
   782  	if !jst.traceCallFrames {
   783  		return
   784  	}
   785  	if jst.err != nil {
   786  		return
   787  	}
   788  	// If tracing was interrupted, set the error and stop
   789  	if atomic.LoadUint32(&jst.interrupt) > 0 {
   790  		jst.err = jst.reason
   791  		return
   792  	}
   793  
   794  	*jst.frame.typ = typ.String()
   795  	*jst.frame.from = from
   796  	*jst.frame.to = to
   797  	jst.frame.input = common.CopyBytes(input)
   798  	*jst.frame.gas = uint(gas)
   799  	jst.frame.value = nil
   800  	if value != nil {
   801  		jst.frame.value = new(big.Int).SetBytes(value.Bytes())
   802  	}
   803  
   804  	if _, err := jst.call(true, "enter", "frame"); err != nil {
   805  		jst.err = wrapError("enter", err)
   806  	}
   807  }
   808  
   809  // CaptureExit is called when EVM exits a scope, even if the scope didn't
   810  // execute any code.
   811  func (jst *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
   812  	if !jst.traceCallFrames {
   813  		return
   814  	}
   815  	// If tracing was interrupted, set the error and stop
   816  	if atomic.LoadUint32(&jst.interrupt) > 0 {
   817  		jst.err = jst.reason
   818  		return
   819  	}
   820  
   821  	jst.frameResult.output = common.CopyBytes(output)
   822  	*jst.frameResult.gasUsed = uint(gasUsed)
   823  	jst.frameResult.errorValue = nil
   824  	if err != nil {
   825  		jst.frameResult.errorValue = new(string)
   826  		*jst.frameResult.errorValue = err.Error()
   827  	}
   828  
   829  	if _, err := jst.call(true, "exit", "frameResult"); err != nil {
   830  		jst.err = wrapError("exit", err)
   831  	}
   832  }
   833  
   834  // GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
   835  func (jst *jsTracer) GetResult() (json.RawMessage, error) {
   836  	// Transform the context into a JavaScript object and inject into the state
   837  	obj := jst.vm.PushObject()
   838  
   839  	for key, val := range jst.ctx {
   840  		jst.addToObj(obj, key, val)
   841  	}
   842  	jst.vm.PutPropString(jst.stateObject, "ctx")
   843  
   844  	// Finalize the trace and return the results
   845  	result, err := jst.call(false, "result", "ctx", "db")
   846  	if err != nil {
   847  		jst.err = wrapError("result", err)
   848  	}
   849  	// Clean up the JavaScript environment
   850  	jst.vm.DestroyHeap()
   851  	jst.vm.Destroy()
   852  
   853  	return result, jst.err
   854  }
   855  
   856  // addToObj pushes a field to a JS object.
   857  func (jst *jsTracer) addToObj(obj int, key string, val interface{}) {
   858  	pushValue(jst.vm, val)
   859  	jst.vm.PutPropString(obj, key)
   860  }
   861  
   862  func pushValue(ctx *duktape.Context, val interface{}) {
   863  	switch val := val.(type) {
   864  	case uint64:
   865  		ctx.PushUint(uint(val))
   866  	case string:
   867  		ctx.PushString(val)
   868  	case []byte:
   869  		ptr := ctx.PushFixedBuffer(len(val))
   870  		copy(makeSlice(ptr, uint(len(val))), val)
   871  	case common.Address:
   872  		ptr := ctx.PushFixedBuffer(20)
   873  		copy(makeSlice(ptr, 20), val[:])
   874  	case *big.Int:
   875  		pushBigInt(val, ctx)
   876  	case int:
   877  		ctx.PushInt(val)
   878  	case uint:
   879  		ctx.PushUint(val)
   880  	case common.Hash:
   881  		ptr := ctx.PushFixedBuffer(32)
   882  		copy(makeSlice(ptr, 32), val[:])
   883  	default:
   884  		panic(fmt.Sprintf("unsupported type: %T", val))
   885  	}
   886  }