nimbus-eth1/nimbus/evm/tracer/legacy_tracer.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): 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