206 lines
6.4 KiB
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
|