185 lines
5.8 KiB
Nim
185 lines
5.8 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
# http://opensource.org/licenses/MIT)
|
|
# at your option. This file may not be copied, modified, or distributed except
|
|
# according to those terms.
|
|
|
|
## Populates the tracer API methods
|
|
## ================================
|
|
##
|
|
## The module name `legacy_tracer` is probably a misonmer as it also works
|
|
## with the new APIs for `CoreDb` and `Ledger`.
|
|
##
|
|
|
|
import
|
|
std/[json, sets, strutils, hashes],
|
|
eth/common/eth_types,
|
|
eth/rlp,
|
|
stew/byteutils,
|
|
chronicles,
|
|
".."/[types, memory, stack],
|
|
../interpreter/op_codes,
|
|
../../db/ledger,
|
|
../evm_errors
|
|
|
|
type
|
|
LegacyTracer* = ref object of TracerRef
|
|
trace: JsonNode
|
|
accounts: HashSet[EthAddress]
|
|
storageKeys: seq[HashSet[UInt256]]
|
|
gas: GasInt
|
|
|
|
proc hash*(x: UInt256): Hash =
|
|
result = hash(x.toByteArrayBE)
|
|
|
|
proc rememberStorageKey(ctx: LegacyTracer, compDepth: int, key: UInt256) =
|
|
ctx.storageKeys[compDepth].incl key
|
|
|
|
iterator storage(ctx: LegacyTracer, compDepth: int): UInt256 =
|
|
doAssert compDepth >= 0 and compDepth < ctx.storageKeys.len
|
|
for key in ctx.storageKeys[compDepth]:
|
|
yield key
|
|
|
|
proc newLegacyTracer*(flags: set[TracerFlags]): LegacyTracer =
|
|
let trace = newJObject()
|
|
|
|
# make appear at the top of json object
|
|
trace["gas"] = %0
|
|
trace["failed"] = %false
|
|
trace["returnValue"] = %""
|
|
trace["structLogs"] = newJArray()
|
|
|
|
LegacyTracer(
|
|
flags: flags,
|
|
trace: trace
|
|
)
|
|
|
|
method capturePrepare*(ctx: LegacyTracer, comp: Computation, depth: int) {.gcsafe.} =
|
|
if depth >= ctx.storageKeys.len:
|
|
let prevLen = ctx.storageKeys.len
|
|
ctx.storageKeys.setLen(depth + 1)
|
|
for i in prevLen ..< ctx.storageKeys.len - 1:
|
|
ctx.storageKeys[i] = HashSet[UInt256]()
|
|
|
|
ctx.storageKeys[depth] = HashSet[UInt256]()
|
|
|
|
# Opcode level
|
|
method captureOpStart*(ctx: LegacyTracer, c: Computation,
|
|
fixed: bool, pc: int, op: Op, gas: GasInt,
|
|
depth: int): int {.gcsafe.} =
|
|
try:
|
|
let
|
|
j = newJObject()
|
|
ctx.trace["structLogs"].add(j)
|
|
|
|
j["op"] = %(($op).toUpperAscii)
|
|
j["pc"] = %(c.code.pc - 1)
|
|
j["depth"] = %(c.msg.depth + 1)
|
|
j["gas"] = %(gas)
|
|
ctx.gas = gas
|
|
|
|
# log stack
|
|
if TracerFlags.DisableStack notin ctx.flags:
|
|
let stack = newJArray()
|
|
for v in c.stack:
|
|
stack.add(%v.dumpHex())
|
|
j["stack"] = stack
|
|
|
|
# log memory
|
|
if TracerFlags.DisableMemory notin ctx.flags:
|
|
let mem = newJArray()
|
|
const chunkLen = 32
|
|
let numChunks = c.memory.len div chunkLen
|
|
for i in 0 ..< numChunks:
|
|
let memHex = c.memory.bytes.toOpenArray(i * chunkLen, (i + 1) * chunkLen - 1).toHex()
|
|
mem.add(%(memHex.toUpperAscii))
|
|
j["memory"] = mem
|
|
|
|
if TracerFlags.EnableAccount in ctx.flags:
|
|
case op
|
|
of Call, CallCode, DelegateCall, StaticCall:
|
|
if c.stack.len > 2:
|
|
ctx.accounts.incl c.stack[^2, EthAddress].expect("stack constains more than 2 elements")
|
|
of ExtCodeCopy, ExtCodeSize, Balance, SelfDestruct:
|
|
if c.stack.len > 1:
|
|
ctx.accounts.incl c.stack[^1, EthAddress].expect("stack is not empty")
|
|
else:
|
|
discard
|
|
|
|
if TracerFlags.DisableStorage notin ctx.flags:
|
|
if op == Sstore:
|
|
if c.stack.len > 1:
|
|
ctx.rememberStorageKey(c.msg.depth,
|
|
c.stack[^1, UInt256].expect("stack is not empty"))
|
|
|
|
result = ctx.trace["structLogs"].len - 1
|
|
except KeyError as ex:
|
|
error "LegacyTracer captureOpStart", msg=ex.msg
|
|
except ValueError as ex:
|
|
error "LegacyTracer captureOpStart", msg=ex.msg
|
|
|
|
method captureOpEnd*(ctx: LegacyTracer, c: Computation,
|
|
fixed: bool, pc: int, op: Op, gas: GasInt, refund: int64,
|
|
rData: openArray[byte],
|
|
depth: int, opIndex: int) {.gcsafe.} =
|
|
try:
|
|
let
|
|
j = ctx.trace["structLogs"].elems[opIndex]
|
|
|
|
# TODO: figure out how to get storage
|
|
# when contract execution interrupted by exception
|
|
if TracerFlags.DisableStorage notin ctx.flags:
|
|
var storage = newJObject()
|
|
if c.msg.depth < ctx.storageKeys.len:
|
|
var stateDB = c.vmState.stateDB
|
|
for key in ctx.storage(c.msg.depth):
|
|
let value = stateDB.getStorage(c.msg.contractAddress, key)
|
|
storage[key.dumpHex] = %(value.dumpHex)
|
|
j["storage"] = storage
|
|
|
|
j["gasCost"] = %(ctx.gas - gas)
|
|
|
|
if op in {Return, Revert} and TracerFlags.DisableReturnData notin ctx.flags:
|
|
let returnValue = %("0x" & toHex(c.output))
|
|
j["returnValue"] = returnValue
|
|
ctx.trace["returnValue"] = returnValue
|
|
except KeyError as ex:
|
|
error "LegacyTracer captureOpEnd", msg=ex.msg
|
|
except RlpError as ex:
|
|
error "LegacyTracer captureOpEnd", msg=ex.msg
|
|
|
|
method captureFault*(ctx: LegacyTracer, comp: Computation,
|
|
fixed: bool, pc: int, op: Op, gas: GasInt, refund: int64,
|
|
rData: openArray[byte],
|
|
depth: int, error: Opt[string]) {.gcsafe.} =
|
|
try:
|
|
if ctx.trace["structLogs"].elems.len > 0:
|
|
let j = ctx.trace["structLogs"].elems[^1]
|
|
j["error"] = %(comp.error.info)
|
|
j["gasCost"] = %(ctx.gas - gas)
|
|
|
|
ctx.trace["failed"] = %true
|
|
except KeyError as ex:
|
|
error "LegacyTracer captureOpEnd", msg=ex.msg
|
|
|
|
proc getTracingResult*(ctx: LegacyTracer): JsonNode =
|
|
ctx.trace
|
|
|
|
iterator tracedAccounts*(ctx: LegacyTracer): Address =
|
|
for acc in ctx.accounts:
|
|
yield acc
|
|
|
|
iterator tracedAccountsPairs*(ctx: LegacyTracer): (int, Address) =
|
|
var idx = 0
|
|
for acc in ctx.accounts:
|
|
yield (idx, acc)
|
|
inc idx
|
|
|
|
proc removeTracedAccounts*(ctx: LegacyTracer, accounts: varargs[Address]) =
|
|
for acc in accounts:
|
|
ctx.accounts.excl acc
|