Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sixty-rabbits-open.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hardhat": patch
---

Improved HHE909 error message to show incompatible files
176 changes: 176 additions & 0 deletions v-next/example-project/hardhat.config.ts.save
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { HardhatPluginError } from "hardhat/plugins";

import util from "node:util";
import { task, emptyTask, globalOption, configVariable } from "hardhat/config";
import HardhatNodeTestRunner from "@nomicfoundation/hardhat-node-test-runner";
import HardhatMochaTestRunner from "@nomicfoundation/hardhat-mocha";
import HardhatKeystore from "@nomicfoundation/hardhat-keystore";
import HardhatViem from "@nomicfoundation/hardhat-viem";
import HardhatViemAssertions import hardhatNetworkHelpersPlugin from "@nomicfoundation/hardhat-network-helpers";
import hardhatEthersPlugin from "@nomicfoundation/hardhat-ethers";
import hardhatChaiMatchersPlugin from "@nomicfoundation/hardhat-ethers-chai-matchers";
import hardhatTypechain from "@nomicfoundation/hardhat-typechain";
import hardhatIgnitionViem from "@nomicfoundation/hardhat-ignition-viem";
import hardhatVerify from "@nomicfoundation/hardhat-verify";
import hardhatLedger from "@nomicfoundation/hardhat-ledger";
import { ArgumentType } from "hardhat/types/arguments";

util.inspect.defaultOptions.depth = null;

const exampleEmptyTask = emptyTask("empty", "An example empty task").build();

const exampleEmptySubtask = task(["empty", "task"])
.setDescription("An example empty subtask task")
.setAction(async () => ({
default: async (_, _hre) => {
console.log("empty task");
},
}))
.build();

const exampleTaskOverride = task("example2")
.setAction(async () => ({
default: async (_, _hre) => {
console.log("from an override");
},
}))
.setDescription("An example task")
.addVariadicArgument({
name: "testFiles",
description: "An optional list of files to test",
defaultValue: [],
})
.addFlag({
name: "noCompile",
description: "Don't compile before running this task",
})
.addFlag({
name: "parallel",
description: "Run tests in parallel",
})
.addFlag({
name: "bail",
description: "Stop running tests after the first test failure",
})
.addOption({
name: "grep",
description: "Only run tests matching the given string or regexp",
type: ArgumentType.STRING_WITHOUT_DEFAULT,
defaultValue: undefined,
})
.build();

const greeting = task("hello", "Print a greeting")
.addOption({
name: "greeting",
description: "The greeting to print",
defaultValue: "Hello, World!",
})
.addOption({
name: "programmatic",
description: "An example to show a hidden option",
type: ArgumentType.BOOLEAN,
defaultValue: false,
hidden: true,
})
.setAction(async () => ({
default: async ({ greeting }, _) => {
console.log(greeting);
},
}))
.build();

const printConfig = task("config", "Print the config")
.setAction(async () => ({
default: async ({}, hre) => {
console.log(util.inspect(hre.config, { colors: true, depth: null }));
},
}))
.build();

const printAccounts = task("accounts", "Print the accounts")
.setAction(async () => ({
default: async ({}, hre) => {
const { provider } = await hre.network.connect();
console.log(await provider.request({ method: "eth_accounts" }));
},
}))
.build();

const pluginExample = {
id: "community-plugin",
tasks: [
task("plugin-hello", "Print a greeting from community-plugin")
.addOption({
name: "greeting",
description: "The greeting to print",
defaultValue: "Hello, World from community-plugin!",
})
.setAction(async () => ({
default: async ({ greeting }, _) => {
console.log(greeting);

if (greeting === "") {
throw new HardhatPluginError(
"community-plugin",
"Greeting cannot be empty",
);
}
},
}))
.build(),
],
globalOptions: [
globalOption({
name: "myGlobalOption",
description: "A global option",
defaultValue: "default",
}),
],
};

export default defineConfig({
networks: {
op: {
type: "http",
chainType: "op",
url: "https://mainnet.optimism.io/",
accounts: [configVariable("OP_SENDER")],
},
edrOp: {
type: "edr-simulated",
chainType: "op",
chainId: 10,
forking: {
url: "https://mainnet.optimism.io",
},
ledgerAccounts: [
// Set your ledger address here
// "0x070Da0697e6B82F0ab3f5D0FD9210EAdF2Ba1516",
],
},
opSepolia: {
type: "http",
chainType: "op",
url: "https://sepolia.optimism.io",
accounts: [configVariable("OP_SEPOLIA_SENDER")],
},
edrOpSepolia: {
type: "edr-simulated",
chainType: "op",
forking: {
url: "https://sepolia.optimism.io",
},
},
},
tasks: [
exampleTaskOverride,
exampleEmptyTask,
exampleEmptySubtask,
greeting,
printConfig,
printAccounts,
],
plugins: [
pluginExample,

Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const solidityTestUserConfigType = z.object({
forking: z
.object({
url: z.optional(sensitiveUrlSchema),
blockNumber: z.bigint().optional(),
blockNumber: z.number().or(z.bigint()).optional(),
rpcEndpoints: z.record(sensitiveStringSchema).optional(),
})
.optional(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,66 @@ export class SolcConfigSelector {
}
}

// Collect incompatible files information
const incompatibleFiles: Array<{ path: string; pragma: string }> = [];

// check root file
if (maxSatisfying(compilerVersions, rootVersionRange) === null) {
incompatibleFiles.push({
path: shortenPath(root.fsPath),
pragma: root.content.versionPragmas.join(" "),
});
}

// Check all dependencies
for (const transitiveDependency of this.#getTransitiveDependencies(
root,
dependencyGraph,
)) {
const depVersionRange = transitiveDependency.versionPragmasPath
.map((pragmas) => pragmas.join(" "))
.join(" ");
if (maxSatisfying(compilerVersions, depVersionRange) === null) {
incompatibleFiles.push({
path: shortenPath(transitiveDependency.dependency.fsPath),
pragma:
transitiveDependency.dependency.content.versionPragmas.join(" "),
});
}
}

// Bulid detailed error message
let detailedMessage = `No solc version enabled in this profile is compatible with this file and all of its dependencies.`;

if (incompatibleFiles.length > 0) {
detailedMessage += `

The following file(s) have incompatible version requirements:`;

const maxFilesToShow = 5;
const filesToShow = incompatibleFiles.slice(0, maxFilesToShow);

for (const file of filesToShow) {
detailedMessage += `
- ${file.path}: requires ${file.pragma}`;
}

if (incompatibleFiles.length > maxFilesToShow) {
detailedMessage += `
... and ${incompatibleFiles.length - maxFilesToShow} more file(s)`;
}

detailedMessage += `

Available compiler versions: ${compilerVersions.join(", ")}`;
}

return {
reason:
CompilationJobCreationErrorReason.NO_COMPATIBLE_SOLC_VERSION_FOUND,
rootFilePath: root.fsPath,
buildProfile: this.#buildProfileName,
formattedReason: `No solc version enabled in this profile is compatible with this file and all of its dependencies.`,
formattedReason: detailedMessage,
};
}

Expand Down