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 }