diff --git a/nimbus/evm/state.nim b/nimbus/evm/state.nim index 5bb8fd621..f580476e8 100644 --- a/nimbus/evm/state.nim +++ b/nimbus/evm/state.nim @@ -365,6 +365,12 @@ proc removeTracedAccounts*(vmState: BaseVMState, accounts: varargs[EthAddress]) for acc in accounts: vmState.tracer.accounts.excl acc +proc tracerGasUsed*(vmState: BaseVMState, gasUsed: GasInt) = + vmState.tracer.gasUsed = gasUsed + +proc tracerGasUsed*(vmState: BaseVMState): GasInt = + vmState.tracer.gasUsed + proc status*(vmState: BaseVMState): bool = ExecutionOK in vmState.flags diff --git a/nimbus/evm/transaction_tracer.nim b/nimbus/evm/transaction_tracer.nim index 9d4b5bcdc..7ec99865f 100644 --- a/nimbus/evm/transaction_tracer.nim +++ b/nimbus/evm/transaction_tracer.nim @@ -18,7 +18,6 @@ proc initTracer*(tracer: var TransactionTracer, flags: set[TracerFlags] = {}) = tracer.trace["gas"] = %0 tracer.trace["failed"] = %false tracer.trace["returnValue"] = %"" - tracer.trace["structLogs"] = newJArray() tracer.flags = flags tracer.accounts = initHashSet[EthAddress]() @@ -44,6 +43,16 @@ iterator storage(tracer: TransactionTracer, compDepth: int): UInt256 = for key in tracer.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 traceOpCodeStarted*(tracer: var TransactionTracer, c: Computation, op: Op): int = if unlikely tracer.trace.isNil: tracer.initTracer() @@ -51,17 +60,34 @@ proc traceOpCodeStarted*(tracer: var TransactionTracer, c: Computation, op: Op): let j = newJObject() tracer.trace["structLogs"].add(j) - j["op"] = %(($op).toUpperAscii) - j["pc"] = %(c.code.pc - 1) - j["depth"] = %(c.msg.depth + 1) - j["gas"] = %c.gasMeter.gasRemaining + 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(%v.dumpHex()) - j["stack"] = st + # 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 # log memory if TracerFlags.DisableMemory notin tracer.flags: @@ -101,11 +127,19 @@ proc traceOpCodeEnded*(tracer: var TransactionTracer, c: Computation, op: Op, la var stateDB = c.vmState.stateDB for key in tracer.storage(c.msg.depth): let value = stateDB.getStorage(c.msg.contractAddress, key) - storage[key.dumpHex] = %(value.dumpHex) + if TracerFlags.GethCompatibility in tracer.flags: + storage["0x" & key.dumpHex.stripLeadingZeros] = + %("0x" & value.dumpHex.stripLeadingZeros) + else: + storage[key.dumpHex] = %(value.dumpHex) j["storage"] = storage - let gasRemaining = j["gas"].getBiggestInt() - j["gasCost"] = %(gasRemaining - c.gasMeter.gasRemaining) + 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) if op in {Return, Revert} and TracerFlags.DisableReturnData notin tracer.flags: let returnValue = %("0x" & toHex(c.output, true)) @@ -123,7 +157,11 @@ proc traceError*(tracer: var TransactionTracer, c: Computation) = # even though the gasCost is incorrect, # we have something to display, # it is an error anyway - let gasRemaining = j["gas"].getBiggestInt() - j["gasCost"] = %(gasRemaining - c.gasMeter.gasRemaining) + 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) tracer.trace["failed"] = %true diff --git a/nimbus/evm/types.nim b/nimbus/evm/types.nim index 78d80df79..fd8dc1c24 100644 --- a/nimbus/evm/types.nim +++ b/nimbus/evm/types.nim @@ -65,12 +65,14 @@ type DisableStateDiff EnableAccount DisableReturnData + GethCompatibility TransactionTracer* = object trace*: JsonNode flags*: set[TracerFlags] accounts*: HashSet[EthAddress] storageKeys*: seq[HashSet[UInt256]] + gasUsed*: GasInt Computation* = ref object # The execution computation diff --git a/nimbus/transaction/call_common.nim b/nimbus/transaction/call_common.nim index 675bc1111..90eeaab18 100644 --- a/nimbus/transaction/call_common.nim +++ b/nimbus/transaction/call_common.nim @@ -242,6 +242,8 @@ proc finishRunningComputation(host: TransactionHost, call: CallParams): CallResu let c = host.computation let gasRemaining = calculateAndPossiblyRefundGas(host, call) + # evm gas used without intrinsic gas + host.vmState.tracerGasUsed(host.msg.gas - gasRemaining) result.isError = c.isError result.gasUsed = call.gasLimit - gasRemaining diff --git a/nimbus/vm_state.nim b/nimbus/vm_state.nim index eba0cdf71..bd243222c 100644 --- a/nimbus/vm_state.nim +++ b/nimbus/vm_state.nim @@ -38,6 +38,7 @@ export vms.status, vms.`status=`, vms.tracedAccounts, - vms.tracedAccountsPairs + vms.tracedAccountsPairs, + vms.tracerGasUsed # End diff --git a/tools/evmstate/evmstate.nim b/tools/evmstate/evmstate.nim index 48ab94e54..50b9fda32 100644 --- a/tools/evmstate/evmstate.nim +++ b/tools/evmstate/evmstate.nim @@ -50,6 +50,7 @@ type StateResult = object name : string pass : bool + root : Hash256 fork : string error: string state: StateDump @@ -87,14 +88,14 @@ proc verifyResult(ctx: var StateContext, vmState: BaseVMState) = let obtainedHash = vmState.readOnlyStateDB.rootHash if obtainedHash != ctx.expectedHash: ctx.error = "post state root mismatch: got $1, want $2" % - [$obtainedHash, $ctx.expectedHash] + [($obtainedHash).toLowerAscii, $ctx.expectedHash] return let logEntries = vmState.getAndClearLogEntries() let actualLogsHash = rlpHash(logEntries) if actualLogsHash != ctx.expectedLogs: ctx.error = "post state log hash mismatch: got $1, want $2" % - [$actualLogsHash, $ctx.expectedLogs] + [($actualLogsHash).toLowerAscii, $ctx.expectedLogs] return proc `%`(x: UInt256): JsonNode = @@ -143,6 +144,7 @@ proc writeResultToStdout(stateRes: seq[StateResult]) = let z = %{ "name" : %(res.name), "pass" : %(res.pass), + "stateRoot" : %(res.root), "fork" : %(res.fork), "error": %(res.error) } @@ -173,16 +175,37 @@ proc dumpState(vmState: BaseVMState): StateDump = accounts: dumpAccounts(vmState.stateDB) ) +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 writeTraceToStderr(vmState: BaseVMState, pretty: bool) = let trace = vmState.getTracingResult() + trace["gasUsed"] = encodeHexInt(vmState.tracerGasUsed) + trace.delete("gas") + let stateRoot = %{ + "stateRoot": %(vmState.readOnlyStateDB.rootHash) + } if pretty: stderr.writeLine(trace.pretty) else: + var gasUsed = 0 let logs = trace["structLogs"] trace.delete("structLogs") for x in logs: + if "error" in x: + trace["error"] = x["error"] + x.delete("error") stderr.writeLine($x) + stderr.writeLine($trace) + stderr.writeLine($stateRoot) proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateResult = let @@ -213,6 +236,7 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR result = StateResult( name : ctx.name, pass : ctx.error.len == 0, + root : vmState.stateDB.rootHash, fork : toString(ctx.fork), error: ctx.error ) @@ -233,12 +257,13 @@ proc runExecution(ctx: var StateContext, conf: StateConf, pre: JsonNode): StateR if ctx.fork >= FkSpurious: if db.isEmptyAccount(miner): db.deleteAccount(miner) - db.persist() + db.persist(clearCache = false) proc toTracerFlags(conf: Stateconf): set[TracerFlags] = result = { TracerFlags.DisableStateDiff, - TracerFlags.EnableTracing + TracerFlags.EnableTracing, + TracerFlags.GethCompatibility } if conf.disableMemory : result.incl TracerFlags.DisableMemory