-
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 10 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 `-v` verbosity support for all tasks ([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 |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import type { | |
| TracingConfigWithBuffers, | ||
| AccountOverride, | ||
| GasReportConfig, | ||
| IncludeTraces, | ||
| } from "@nomicfoundation/edr"; | ||
|
|
||
| import { | ||
|
|
@@ -40,6 +41,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 { hexToBytes } from "ethereum-cryptography/utils"; | ||
| import { addr } from "micro-eth-signer"; | ||
|
|
@@ -73,6 +75,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"); | ||
|
|
||
|
|
@@ -139,13 +142,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; | ||
|
|
||
| /** | ||
| * Creates a new instance of `EdrProvider`. | ||
|
|
@@ -158,6 +163,7 @@ export class EdrProvider extends BaseProvider { | |
| jsonRpcRequestWrapper, | ||
| coverageConfig, | ||
| gasReportConfig, | ||
| includeCallTraces, | ||
| }: EdrProviderConfig): Promise<EdrProvider> { | ||
| const printLineFn = loggerConfig.printLineFn ?? printLine; | ||
| const replaceLastLineFn = loggerConfig.replaceLastLineFn ?? replaceLastLine; | ||
|
|
@@ -167,6 +173,7 @@ export class EdrProvider extends BaseProvider { | |
| coverageConfig, | ||
| gasReportConfig, | ||
| chainDescriptors, | ||
| includeCallTraces, | ||
| ); | ||
|
|
||
| let edrProvider: EdrProvider; | ||
|
|
@@ -205,7 +212,11 @@ export class EdrProvider extends BaseProvider { | |
| contractDecoder, | ||
| ); | ||
|
|
||
| edrProvider = new EdrProvider(provider, jsonRpcRequestWrapper); | ||
| edrProvider = new EdrProvider( | ||
| provider, | ||
| printLineFn, | ||
| jsonRpcRequestWrapper, | ||
| ); | ||
| } 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; | ||
| } | ||
|
|
||
|
|
@@ -310,6 +323,20 @@ export class EdrProvider extends BaseProvider { | |
| ); | ||
| } | ||
|
|
||
| #outputCallTraces(edrResponse: Response): void { | ||
|
ChristopherDedominici marked this conversation as resolved.
Outdated
|
||
| 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> { | ||
|
|
@@ -365,11 +392,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; | ||
| } | ||
|
|
||
|
|
@@ -430,6 +461,7 @@ export async function getProviderConfig( | |
| coverageConfig: CoverageConfig | undefined, | ||
| gasReportConfig: GasReportConfig | undefined, | ||
| chainDescriptors: ChainDescriptorsConfig, | ||
| includeCallTraces?: IncludeTraces, | ||
| ): Promise<ProviderConfig> { | ||
| const specId = hardhatHardforkToEdrSpecId( | ||
| networkConfig.hardfork, | ||
|
|
@@ -506,6 +538,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; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done here (I kept the links): 7a9a64c