-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add verbosity for all hh tasks #7983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
dce7149
8e194d0
05bd623
18eb2f9
722cb9b
71b8f0b
6d8c8a2
3f78af7
593a016
c917a3c
564d509
1141434
273318b
7a9a64c
96b785c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| "@nomicfoundation/hardhat-utils": patch | ||
| "hardhat": patch | ||
| --- | ||
|
|
||
| Added `--verbosity` (and `-v`, `-vv`, and the other shorthands) to all tasks, including TypeScript tests ([7983](https://github.com/NomicFoundation/hardhat/pull/7983)), ([7963](https://github.com/NomicFoundation/hardhat/issues/7963)). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,7 @@ import type { | |
| ProviderConfig, | ||
| TracingConfigWithBuffers, | ||
| GasReportConfig, | ||
| IncludeTraces, | ||
| } from "@nomicfoundation/edr"; | ||
|
|
||
| import { ContractDecoder } from "@nomicfoundation/edr"; | ||
|
|
@@ -33,6 +34,7 @@ import { toSeconds } from "@nomicfoundation/hardhat-utils/date"; | |
| import { ensureError } from "@nomicfoundation/hardhat-utils/error"; | ||
| import { numberToHexString } from "@nomicfoundation/hardhat-utils/hex"; | ||
| import { deepEqual } from "@nomicfoundation/hardhat-utils/lang"; | ||
| import chalk from "chalk"; | ||
| import debug from "debug"; | ||
|
|
||
| import { sendErrorTelemetry } from "../../../cli/telemetry/sentry/reporter.js"; | ||
|
|
@@ -61,6 +63,7 @@ import { | |
| hardhatForkingConfigToEdrForkConfig, | ||
| } from "./utils/convert-to-edr.js"; | ||
| import { printLine, replaceLastLine } from "./utils/logger.js"; | ||
| import { formatTraces } from "./utils/trace-formatters.js"; | ||
|
|
||
| const log = debug("hardhat:core:hardhat-network:provider"); | ||
|
|
||
|
|
@@ -127,13 +130,15 @@ interface EdrProviderConfig { | |
| jsonRpcRequestWrapper?: JsonRpcRequestWrapperFunction; | ||
| coverageConfig?: CoverageConfig; | ||
| gasReportConfig?: GasReportConfig; | ||
| includeCallTraces?: IncludeTraces; | ||
| } | ||
|
|
||
| export class EdrProvider extends BaseProvider { | ||
| readonly #jsonRpcRequestWrapper?: JsonRpcRequestWrapperFunction; | ||
|
|
||
| #provider: Provider | undefined; | ||
| #nextRequestId = 1; | ||
| readonly #printLineFn: (line: string) => void; | ||
|
|
||
| public static async createContractDecoder( | ||
| tracingConfig: TracingConfigWithBuffers, | ||
|
|
@@ -152,6 +157,7 @@ export class EdrProvider extends BaseProvider { | |
| jsonRpcRequestWrapper, | ||
| coverageConfig, | ||
| gasReportConfig, | ||
| includeCallTraces, | ||
| }: EdrProviderConfig): Promise<EdrProvider> { | ||
| const printLineFn = loggerConfig.printLineFn ?? printLine; | ||
| const replaceLastLineFn = loggerConfig.replaceLastLineFn ?? replaceLastLine; | ||
|
|
@@ -161,6 +167,7 @@ export class EdrProvider extends BaseProvider { | |
| coverageConfig, | ||
| gasReportConfig, | ||
| chainDescriptors, | ||
| includeCallTraces, | ||
| ); | ||
|
|
||
| let edrProvider: EdrProvider; | ||
|
|
@@ -204,7 +211,11 @@ export class EdrProvider extends BaseProvider { | |
| contractDecoder, | ||
| ); | ||
|
|
||
| edrProvider = new EdrProvider(provider, jsonRpcRequestWrapper); | ||
| edrProvider = new EdrProvider( | ||
| provider, | ||
| printLineFn, | ||
| jsonRpcRequestWrapper, | ||
| ); | ||
| edrProviderWeakRef = new WeakRef(edrProvider); | ||
| } catch (error) { | ||
| ensureError(error); | ||
|
|
@@ -225,11 +236,13 @@ export class EdrProvider extends BaseProvider { | |
| */ | ||
| private constructor( | ||
| provider: Provider, | ||
| printLineFn: (line: string) => void, | ||
| jsonRpcRequestWrapper?: JsonRpcRequestWrapperFunction, | ||
| ) { | ||
| super(); | ||
|
|
||
| this.#provider = provider; | ||
| this.#printLineFn = printLineFn; | ||
| this.#jsonRpcRequestWrapper = jsonRpcRequestWrapper; | ||
| } | ||
|
|
||
|
|
@@ -311,6 +324,20 @@ export class EdrProvider extends BaseProvider { | |
| ); | ||
| } | ||
|
|
||
| #outputCallTraces(edrResponse: Response): void { | ||
| try { | ||
| const callTraces = edrResponse.callTraces(); | ||
|
|
||
| if (callTraces.length > 0) { | ||
| const formatted = formatTraces(callTraces, " ", chalk); | ||
| this.#printLineFn(" Call Traces:"); | ||
| this.#printLineFn(formatted); | ||
| } | ||
| } catch (e) { | ||
| log("Failed to get call traces: %O", e); | ||
| } | ||
| } | ||
|
||
|
|
||
| async #handleEdrResponse( | ||
| edrResponse: Response, | ||
| ): Promise<SuccessfulJsonRpcResponse> { | ||
|
|
@@ -366,11 +393,15 @@ export class EdrProvider extends BaseProvider { | |
| error.data = responseError.data; | ||
| } | ||
|
|
||
| this.#outputCallTraces(edrResponse); | ||
|
|
||
| /* eslint-disable-next-line no-restricted-syntax -- we may throw | ||
| non-Hardhat errors inside of an EthereumProvider */ | ||
| throw error; | ||
| } | ||
|
|
||
| this.#outputCallTraces(edrResponse); | ||
|
|
||
| return jsonRpcResponse; | ||
| } | ||
|
|
||
|
|
@@ -431,6 +462,7 @@ export async function getProviderConfig( | |
| coverageConfig: CoverageConfig | undefined, | ||
| gasReportConfig: GasReportConfig | undefined, | ||
| chainDescriptors: ChainDescriptorsConfig, | ||
| includeCallTraces?: IncludeTraces, | ||
| ): Promise<ProviderConfig> { | ||
| const specId = hardhatHardforkToEdrSpecId( | ||
| networkConfig.hardfork, | ||
|
|
@@ -477,6 +509,7 @@ export async function getProviderConfig( | |
| observability: { | ||
| codeCoverage: coverageConfig, | ||
| gasReport: gasReportConfig, | ||
| includeCallTraces, | ||
| }, | ||
| ownedAccounts: ownedAccounts.map((account) => account.secretKey), | ||
| precompileOverrides: [], | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| import type { Colorizer } from "../../../../utils/colorizer.js"; | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These functions have been moved from |
||
| import type { | ||
| LogTrace, | ||
| CallTrace, | ||
| DecodedTraceParameters, | ||
| } from "@nomicfoundation/edr"; | ||
|
|
||
| import { LogKind, CallKind, IncludeTraces } from "@nomicfoundation/edr"; | ||
| import { assertHardhatInvariant } from "@nomicfoundation/hardhat-errors"; | ||
| import { bytesToHexString } from "@nomicfoundation/hardhat-utils/hex"; | ||
|
|
||
| type NestedArray<T> = Array<T | NestedArray<T>>; | ||
|
|
||
| export function verbosityToIncludeTraces(verbosity: number): IncludeTraces { | ||
| if (verbosity >= 4) { | ||
| return IncludeTraces.All; | ||
| } else if (verbosity >= 3) { | ||
| return IncludeTraces.Failing; | ||
| } | ||
|
|
||
| return IncludeTraces.None; | ||
| } | ||
|
|
||
| export function formatTraces( | ||
| traces: CallTrace[], | ||
| prefix: string, | ||
| colorizer: Colorizer, | ||
| ): string { | ||
| const lines = traces.map((trace) => formatTrace(trace, colorizer)); | ||
| const formattedTraces = formatNestedArray(lines, prefix); | ||
| // Remove the trailing newline | ||
| return formattedTraces.slice(0, -1); | ||
| } | ||
|
|
||
| function formatInputs( | ||
| inputs: DecodedTraceParameters | Uint8Array, | ||
| color?: (text: string) => string, | ||
| ): string | undefined { | ||
| if (inputs instanceof Uint8Array) { | ||
| return inputs.length > 0 ? bytesToHexString(inputs) : undefined; | ||
| } else { | ||
| const formattedName = | ||
| color !== undefined ? color(inputs.name) : inputs.name; | ||
| return `${formattedName}(${inputs.arguments.join(", ")})`; | ||
| } | ||
| } | ||
|
|
||
| function formatOutputs(outputs: string | Uint8Array): string | undefined { | ||
| if (outputs instanceof Uint8Array) { | ||
| return outputs.length > 0 ? bytesToHexString(outputs) : undefined; | ||
| } else { | ||
| return outputs; | ||
| } | ||
| } | ||
|
|
||
| function formatLog(log: LogTrace, colorizer: Colorizer): string[] { | ||
| const { parameters } = log; | ||
| const lines = []; | ||
| if (Array.isArray(parameters)) { | ||
| const topics = parameters.map((topic) => bytesToHexString(topic)); | ||
| if (topics.length > 0) { | ||
| lines.push(`emit topic 0: ${colorizer.cyan(topics[0])}`); | ||
| } | ||
| for (let i = 1; i < topics.length - 1; i++) { | ||
| lines.push(` topic ${i}: ${colorizer.cyan(topics[i])}`); | ||
| } | ||
| if (topics.length > 1) { | ||
| lines.push(` data: ${colorizer.cyan(topics[topics.length - 1])}`); | ||
| } | ||
| } else { | ||
| lines.push( | ||
| `emit ${parameters.name}(${colorizer.cyan(parameters.arguments.join(", "))})`, | ||
| ); | ||
| } | ||
| return lines; | ||
| } | ||
|
|
||
| function formatKind(kind: CallKind): string | undefined { | ||
| assertHardhatInvariant( | ||
| kind !== CallKind.Create, | ||
| "Unexpected call kind 'Create'", | ||
| ); | ||
|
|
||
| switch (kind) { | ||
| case CallKind.Call: | ||
| return undefined; | ||
| case CallKind.CallCode: | ||
| return "callcode"; | ||
| case CallKind.DelegateCall: | ||
| return "delegatecall"; | ||
| case CallKind.StaticCall: | ||
| return "staticcall"; | ||
| } | ||
| } | ||
|
|
||
| function formatTrace( | ||
| trace: CallTrace, | ||
| colorizer: Colorizer, | ||
| ): NestedArray<string> { | ||
| const { | ||
| success, | ||
| address, | ||
| contract, | ||
| inputs, | ||
| gasUsed, | ||
| value, | ||
| kind, | ||
| isCheatcode, | ||
| outputs, | ||
| } = trace; | ||
| let color; | ||
| if (isCheatcode) { | ||
| color = colorizer.blue; | ||
| } else if (success) { | ||
| color = colorizer.green; | ||
| } else { | ||
| color = colorizer.red; | ||
| } | ||
|
|
||
| const formattedInputs = formatInputs(inputs, color); | ||
| const formattedOutputs = formatOutputs(outputs); | ||
|
|
||
| let openingLine: string; | ||
| let closingLine: string | undefined; | ||
| if (kind === CallKind.Create) { | ||
| openingLine = `[${gasUsed}] ${colorizer.yellow("→ new")} ${contract ?? "<unknown>"}@${address}`; | ||
| // TODO: Uncomment this when the formattedInputs starts containing | ||
| // the address of where the contract was deployed instead of the code. | ||
| // if (formattedInputs !== undefined) { | ||
| // openingLine = `${openingLine}@${formattedInputs}`; | ||
| // } | ||
| } else { | ||
| const formattedKind = formatKind(kind); | ||
| openingLine = `[${gasUsed}] ${color(contract ?? address)}`; | ||
| if (formattedInputs !== undefined) { | ||
| openingLine = `${openingLine}::${formattedInputs}`; | ||
| } | ||
| if (value !== 0n) { | ||
| openingLine = `${openingLine} {value: ${value}}`; | ||
| } | ||
| if (formattedKind !== undefined) { | ||
| openingLine = `${openingLine} ${colorizer.yellow(`[${formattedKind}]`)}`; | ||
| } | ||
| } | ||
| if (formattedOutputs !== undefined) { | ||
| if ( | ||
| formattedOutputs === "EvmError: Revert" || | ||
| formattedOutputs.startsWith("revert:") | ||
| ) { | ||
| closingLine = `${color("←")} ${color("[Revert]")} ${formattedOutputs}`; | ||
| } else { | ||
| closingLine = `${color("←")} ${formattedOutputs}`; | ||
| } | ||
| } | ||
|
|
||
| const lines = []; | ||
| lines.push(openingLine); | ||
| for (const child of trace.children) { | ||
| if (child.kind === LogKind.Log) { | ||
| lines.push(formatLog(child, colorizer)); | ||
| } else { | ||
| lines.push(formatTrace(child, colorizer)); | ||
| } | ||
| } | ||
| if (closingLine !== undefined) { | ||
| lines.push([closingLine]); | ||
| } | ||
| return lines; | ||
| } | ||
|
|
||
| function formatNestedArray( | ||
| data: NestedArray<string>, | ||
| prefix = "", | ||
| isTopLevel = true, | ||
| ): string { | ||
| let output = ""; | ||
|
|
||
| for (let i = 0; i < data.length; i++) { | ||
| const item = data[i]; | ||
|
|
||
| if (Array.isArray(item) && typeof item[0] === "string") { | ||
| const [label, ...children] = item; | ||
|
|
||
| if (isTopLevel) { | ||
| output += `${prefix}${label}\n`; | ||
| output += formatNestedArray(children, prefix, false); | ||
| } else { | ||
| const isLast = i === data.length - 1; | ||
| const connector = isLast ? " └─ " : " ├─ "; | ||
| const childPrefix = isLast ? " " : " │ "; | ||
| output += `${prefix}${connector}${label}\n`; | ||
| output += formatNestedArray(children, prefix + childPrefix, false); | ||
| } | ||
| } else if (typeof item === "string") { | ||
| const isLast = i === data.length - 1; | ||
| const connector = isLast ? " └─ " : " ├─ "; | ||
| output += `${prefix}${connector}${item}\n`; | ||
| } | ||
| } | ||
|
|
||
| return output; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.