nimbus-eth1/nimbus/evm/tracer/legacy_tracer.nim

206 lines
6.4 KiB
Nim

# Nimbus
# Copyright (c) 2023 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.
import
std/[json, sets, strutils, hashes],
eth/common/eth_types,
eth/rlp,
stew/byteutils,
chronicles,
".."/[types, memory, stack],
../interpreter/op_codes,
../../db/accounts_cache,
../../errors
type
LegacyTracer* = ref object of TracerRef
trace: JsonNode
accounts: HashSet[EthAddress]
storageKeys: seq[HashSet[UInt256]]
comp: Computation
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
template stripLeadingZeros(value: string): string =
var cidx = 0
# ignore the last character so we retain '0' on zero value
while cidx < value.len - 1 and value[cidx] == '0':
cidx.inc
value[cidx .. ^1]
proc encodeHexInt(x: SomeInteger): JsonNode =
%("0x" & x.toHex.stripLeadingZeros.toLowerAscii)
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, 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] = initHashSet[UInt256]()
ctx.storageKeys[depth] = initHashSet[UInt256]()
# Top call frame
method captureStart*(ctx: LegacyTracer, c: Computation,
sender: EthAddress, to: EthAddress,
create: bool, input: openArray[byte],
gas: GasInt, value: UInt256) {.gcsafe.} =
ctx.comp = c
method captureEnd*(ctx: LegacyTracer, output: openArray[byte],
gasUsed: GasInt, error: Option[string]) {.gcsafe.} =
discard
# Opcode level
method captureOpStart*(ctx: LegacyTracer, pc: int,
op: Op, gas: GasInt,
depth: int): int {.gcsafe.} =
try:
let
j = newJObject()
c = ctx.comp
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.values:
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.values.len > 2:
ctx.accounts.incl c.stack[^2, EthAddress]
of ExtCodeCopy, ExtCodeSize, Balance, SelfDestruct:
if c.stack.values.len > 1:
ctx.accounts.incl c.stack[^1, EthAddress]
else:
discard
if TracerFlags.DisableStorage notin ctx.flags:
if op == Sstore:
if c.stack.values.len > 1:
ctx.rememberStorageKey(c.msg.depth, c.stack[^1, UInt256])
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
except InsufficientStack as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
method captureOpEnd*(ctx: LegacyTracer, pc: int,
op: Op, gas: GasInt, refund: GasInt,
rData: openArray[byte],
depth: int, opIndex: int) {.gcsafe.} =
try:
let
j = ctx.trace["structLogs"].elems[opIndex]
c = ctx.comp
# 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, pc: int,
op: Op, gas: GasInt, refund: GasInt,
rData: openArray[byte],
depth: int, error: Option[string]) {.gcsafe.} =
try:
let c = ctx.comp
if ctx.trace["structLogs"].elems.len > 0:
let j = ctx.trace["structLogs"].elems[^1]
j["error"] = %(c.error.info)
j["gasCost"] = %(ctx.gas - gas)
ctx.trace["failed"] = %true
except KeyError as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
except InsufficientStack as ex:
error "LegacyTracer captureOpEnd", msg=ex.msg
proc getTracingResult*(ctx: LegacyTracer): JsonNode =
ctx.trace
iterator tracedAccounts*(ctx: LegacyTracer): EthAddress =
for acc in ctx.accounts:
yield acc
iterator tracedAccountsPairs*(ctx: LegacyTracer): (int, EthAddress) =
var idx = 0
for acc in ctx.accounts:
yield (idx, acc)
inc idx
proc removeTracedAccounts*(ctx: LegacyTracer, accounts: varargs[EthAddress]) =
for acc in accounts:
ctx.accounts.excl acc