2023-01-31 12:38:08 +00:00
|
|
|
# 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.
|
|
|
|
|
2021-04-08 14:52:10 +00:00
|
|
|
import
|
2023-01-31 12:38:08 +00:00
|
|
|
std/[json, strutils, sets, hashes],
|
2022-09-03 18:15:35 +00:00
|
|
|
chronicles, eth/common, stint,
|
2023-01-31 12:38:08 +00:00
|
|
|
nimcrypto/utils,
|
2021-04-22 16:05:58 +00:00
|
|
|
./types, ./memory, ./stack, ../db/accounts_cache,
|
2021-04-21 07:37:32 +00:00
|
|
|
./interpreter/op_codes
|
2021-04-08 14:52:10 +00:00
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "vm opcode"
|
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
proc hash*(x: UInt256): Hash =
|
2021-04-08 14:52:10 +00:00
|
|
|
result = hash(x.toByteArrayBE)
|
|
|
|
|
|
|
|
proc initTracer*(tracer: var TransactionTracer, flags: set[TracerFlags] = {}) =
|
|
|
|
tracer.trace = newJObject()
|
|
|
|
|
|
|
|
# make appear at the top of json object
|
|
|
|
tracer.trace["gas"] = %0
|
|
|
|
tracer.trace["failed"] = %false
|
2023-03-23 02:38:42 +00:00
|
|
|
|
|
|
|
if TracerFlags.GethCompatibility in tracer.flags:
|
|
|
|
tracer.trace["returnData"] = %""
|
|
|
|
else:
|
|
|
|
tracer.trace["returnValue"] = %""
|
|
|
|
|
2021-04-08 14:52:10 +00:00
|
|
|
tracer.trace["structLogs"] = newJArray()
|
|
|
|
tracer.flags = flags
|
|
|
|
tracer.accounts = initHashSet[EthAddress]()
|
|
|
|
tracer.storageKeys = @[]
|
|
|
|
|
|
|
|
proc prepare*(tracer: var TransactionTracer, compDepth: int) =
|
|
|
|
# this uncommon arragement is intentional
|
|
|
|
# compDepth will be varying up and down: 1,2,3,4,3,3,2,2,1
|
|
|
|
# see issue #245 and PR #247 discussion
|
|
|
|
if compDepth >= tracer.storageKeys.len:
|
|
|
|
let prevLen = tracer.storageKeys.len
|
|
|
|
tracer.storageKeys.setLen(compDepth + 1)
|
|
|
|
for i in prevLen ..< tracer.storageKeys.len - 1:
|
2022-04-08 04:54:11 +00:00
|
|
|
tracer.storageKeys[i] = initHashSet[UInt256]()
|
2021-04-08 14:52:10 +00:00
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
tracer.storageKeys[compDepth] = initHashSet[UInt256]()
|
2021-04-08 14:52:10 +00:00
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
proc rememberStorageKey(tracer: var TransactionTracer, compDepth: int, key: UInt256) =
|
2021-04-08 14:52:10 +00:00
|
|
|
tracer.storageKeys[compDepth].incl key
|
|
|
|
|
2022-04-08 04:54:11 +00:00
|
|
|
iterator storage(tracer: TransactionTracer, compDepth: int): UInt256 =
|
2021-04-08 14:52:10 +00:00
|
|
|
doAssert compDepth >= 0 and compDepth < tracer.storageKeys.len
|
|
|
|
for key in tracer.storageKeys[compDepth]:
|
|
|
|
yield key
|
|
|
|
|
2022-12-14 09:42:55 +00:00
|
|
|
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)
|
|
|
|
|
2021-04-08 14:52:10 +00:00
|
|
|
proc traceOpCodeStarted*(tracer: var TransactionTracer, c: Computation, op: Op): int =
|
|
|
|
if unlikely tracer.trace.isNil:
|
|
|
|
tracer.initTracer()
|
|
|
|
|
|
|
|
let j = newJObject()
|
|
|
|
tracer.trace["structLogs"].add(j)
|
|
|
|
|
2022-12-14 09:42:55 +00:00
|
|
|
if TracerFlags.GethCompatibility in tracer.flags:
|
|
|
|
j["pc"] = %(c.code.pc - 1)
|
|
|
|
j["op"] = %(op.int)
|
|
|
|
j["gas"] = encodeHexInt(c.gasMeter.gasRemaining)
|
|
|
|
j["gasCost"] = %("")
|
|
|
|
j["memSize"] = %c.memory.len
|
|
|
|
j["opName"] = %(($op).toUpperAscii)
|
|
|
|
j["depth"] = %(c.msg.depth + 1)
|
|
|
|
|
|
|
|
# log stack
|
|
|
|
if TracerFlags.DisableStack notin tracer.flags:
|
|
|
|
let st = newJArray()
|
|
|
|
for v in c.stack.values:
|
|
|
|
st.add(%("0x" & v.dumpHex.stripLeadingZeros))
|
|
|
|
j["stack"] = st
|
|
|
|
|
|
|
|
else:
|
|
|
|
j["op"] = %(($op).toUpperAscii)
|
|
|
|
j["pc"] = %(c.code.pc - 1)
|
|
|
|
j["depth"] = %(c.msg.depth + 1)
|
|
|
|
j["gas"] = %c.gasMeter.gasRemaining
|
|
|
|
|
|
|
|
# log stack
|
|
|
|
if TracerFlags.DisableStack notin tracer.flags:
|
|
|
|
let st = newJArray()
|
|
|
|
for v in c.stack.values:
|
|
|
|
st.add(%v.dumpHex())
|
|
|
|
j["stack"] = st
|
2021-04-08 14:52:10 +00:00
|
|
|
|
|
|
|
# log memory
|
|
|
|
if TracerFlags.DisableMemory notin tracer.flags:
|
|
|
|
let mem = newJArray()
|
|
|
|
const chunkLen = 32
|
|
|
|
let numChunks = c.memory.len div chunkLen
|
|
|
|
for i in 0 ..< numChunks:
|
2023-04-24 20:59:38 +00:00
|
|
|
let memHex = c.memory.bytes.toOpenArray(i * chunkLen, (i + 1) * chunkLen - 1).toHex()
|
2023-03-23 02:38:42 +00:00
|
|
|
if TracerFlags.GethCompatibility in tracer.flags:
|
|
|
|
mem.add(%("0x" & memHex.toLowerAscii))
|
|
|
|
else:
|
|
|
|
mem.add(%memHex)
|
2021-04-08 14:52:10 +00:00
|
|
|
j["memory"] = mem
|
|
|
|
|
|
|
|
if TracerFlags.EnableAccount in tracer.flags:
|
|
|
|
case op
|
|
|
|
of Call, CallCode, DelegateCall, StaticCall:
|
|
|
|
if c.stack.values.len > 2:
|
|
|
|
tracer.accounts.incl c.stack[^2, EthAddress]
|
|
|
|
of ExtCodeCopy, ExtCodeSize, Balance, SelfDestruct:
|
|
|
|
if c.stack.values.len > 1:
|
|
|
|
tracer.accounts.incl c.stack[^1, EthAddress]
|
|
|
|
else:
|
|
|
|
discard
|
|
|
|
|
|
|
|
if TracerFlags.DisableStorage notin tracer.flags:
|
|
|
|
if op == Sstore:
|
|
|
|
if c.stack.values.len > 1:
|
2022-04-08 04:54:11 +00:00
|
|
|
tracer.rememberStorageKey(c.msg.depth, c.stack[^1, UInt256])
|
2021-04-08 14:52:10 +00:00
|
|
|
|
|
|
|
result = tracer.trace["structLogs"].len - 1
|
|
|
|
|
|
|
|
proc traceOpCodeEnded*(tracer: var TransactionTracer, c: Computation, op: Op, lastIndex: int) =
|
|
|
|
let j = tracer.trace["structLogs"].elems[lastIndex]
|
|
|
|
|
|
|
|
# TODO: figure out how to get storage
|
|
|
|
# when contract execution interrupted by exception
|
|
|
|
if TracerFlags.DisableStorage notin tracer.flags:
|
|
|
|
var storage = newJObject()
|
|
|
|
if c.msg.depth < tracer.storageKeys.len:
|
2021-10-28 09:42:39 +00:00
|
|
|
var stateDB = c.vmState.stateDB
|
2021-04-08 14:52:10 +00:00
|
|
|
for key in tracer.storage(c.msg.depth):
|
2023-04-24 20:59:38 +00:00
|
|
|
let value = stateDB.getStorage(c.msg.contractAddress, key)
|
2022-12-14 09:42:55 +00:00
|
|
|
if TracerFlags.GethCompatibility in tracer.flags:
|
|
|
|
storage["0x" & key.dumpHex.stripLeadingZeros] =
|
|
|
|
%("0x" & value.dumpHex.stripLeadingZeros)
|
|
|
|
else:
|
|
|
|
storage[key.dumpHex] = %(value.dumpHex)
|
2021-04-08 14:52:10 +00:00
|
|
|
j["storage"] = storage
|
|
|
|
|
2022-12-14 09:42:55 +00:00
|
|
|
if TracerFlags.GethCompatibility in tracer.flags:
|
|
|
|
let gas = fromHex[GasInt](j["gas"].getStr)
|
|
|
|
j["gasCost"] = encodeHexInt(gas - c.gasMeter.gasRemaining)
|
|
|
|
else:
|
|
|
|
let gas = j["gas"].getBiggestInt()
|
|
|
|
j["gasCost"] = %(gas - c.gasMeter.gasRemaining)
|
2021-04-08 14:52:10 +00:00
|
|
|
|
2022-10-15 15:58:23 +00:00
|
|
|
if op in {Return, Revert} and TracerFlags.DisableReturnData notin tracer.flags:
|
2021-04-08 14:52:10 +00:00
|
|
|
let returnValue = %("0x" & toHex(c.output, true))
|
2023-03-23 02:38:42 +00:00
|
|
|
if TracerFlags.GethCompatibility in tracer.flags:
|
|
|
|
j["returnData"] = returnValue
|
|
|
|
tracer.trace["returnData"] = returnValue
|
|
|
|
else:
|
|
|
|
j["returnValue"] = returnValue
|
|
|
|
tracer.trace["returnValue"] = returnValue
|
2021-04-08 14:52:10 +00:00
|
|
|
|
|
|
|
trace "Op", json = j.pretty()
|
|
|
|
|
|
|
|
proc traceError*(tracer: var TransactionTracer, c: Computation) =
|
|
|
|
if tracer.trace["structLogs"].elems.len > 0:
|
|
|
|
let j = tracer.trace["structLogs"].elems[^1]
|
|
|
|
j["error"] = %(c.error.info)
|
|
|
|
trace "Error", json = j.pretty()
|
|
|
|
|
|
|
|
# even though the gasCost is incorrect,
|
|
|
|
# we have something to display,
|
|
|
|
# it is an error anyway
|
2022-12-14 09:42:55 +00:00
|
|
|
if TracerFlags.GethCompatibility in tracer.flags:
|
|
|
|
let gas = fromHex[GasInt](j["gas"].getStr)
|
|
|
|
j["gasCost"] = encodeHexInt(gas - c.gasMeter.gasRemaining)
|
|
|
|
else:
|
|
|
|
let gas = j["gas"].getBiggestInt()
|
|
|
|
j["gasCost"] = %(gas - c.gasMeter.gasRemaining)
|
2021-04-08 14:52:10 +00:00
|
|
|
|
|
|
|
tracer.trace["failed"] = %true
|