diff --git a/.changeset/thick-schools-play.md b/.changeset/thick-schools-play.md new file mode 100644 index 00000000000..b9312ce99a5 --- /dev/null +++ b/.changeset/thick-schools-play.md @@ -0,0 +1,5 @@ +--- +"hardhat": patch +--- + +Fixed compiler download for old versions in ARM64 ([8062](https://github.com/NomicFoundation/hardhat/pull/8062)). diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts index 137bffbde1e..6c6883ecfaf 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts @@ -246,11 +246,6 @@ export class CompilerDownloaderImplementation implements CompilerDownloader { public async getCompiler(version: string): Promise { const build = await this.#getCompilerBuild(version); - assertHardhatInvariant( - build !== undefined, - `Trying to get a compiler ${version} before it was downloaded`, - ); - const compilerPath = this.#getCompilerBinaryPathFromBuild(build); assertHardhatInvariant( diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/index.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/index.ts index 32365ca7911..dc0529141e1 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/index.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/compiler/index.ts @@ -13,6 +13,8 @@ import { exists, isBinaryFile } from "@nomicfoundation/hardhat-utils/fs"; import { getCacheDir } from "@nomicfoundation/hardhat-utils/global-dir"; import debug from "debug"; +import { hasArm64MirrorBuild, hasOfficialArm64Build } from "../solc-info.js"; + import { NativeCompiler, SolcJsCompiler } from "./compiler.js"; import { CompilerDownloaderImplementation, @@ -28,6 +30,25 @@ async function getGlobalCompilersCacheDir(): Promise { const log = debug("hardhat:core:solidity:build-system:compiler"); +/** + * Returns true if a platform-specific build exists for the given version + * on the given compiler platform. On non-ARM64 platforms (including WASM) + * every version is assumed to have a build; on ARM64 Linux only versions + * in the community mirror (>= 0.5.0) or with official builds (>= 0.8.31) do. + * + * Exported only for testing purposes. + */ +export function hasNativeBuildForPlatform( + version: string, + platform: CompilerPlatform, +): boolean { + if (platform !== CompilerPlatform.LINUX_ARM64) { + return true; + } + + return hasOfficialArm64Build(version) || hasArm64MirrorBuild(version); +} + export async function downloadSolcCompilers( versions: Set, quiet: boolean, @@ -40,9 +61,20 @@ export async function downloadSolcCompilers( await getGlobalCompilersCacheDir(), ); - await mainCompilerDownloader.updateCompilerListIfNeeded(versions); + // Only attempt native downloads for versions that have a native build + // on this platform. On ARM64 Linux, older versions (< 0.5.0) have no + // native binary anywhere and would cause the downloader to throw. + const nativeVersions = [...versions].filter((v) => + hasNativeBuildForPlatform(v, platform), + ); + + if (nativeVersions.length > 0) { + await mainCompilerDownloader.updateCompilerListIfNeeded( + new Set(nativeVersions), + ); + } - for (const version of versions) { + for (const version of nativeVersions) { if (!(await mainCompilerDownloader.isCompilerDownloaded(version))) { if (!quiet) { console.log(`Downloading solc ${version}`); @@ -155,15 +187,18 @@ async function getCompilerFromVersion( ) { if (!preferWasm) { const platform = CompilerDownloaderImplementation.getCompilerPlatform(); - const compilerDownloader = new CompilerDownloaderImplementation( - platform, - await getGlobalCompilersCacheDir(), - ); - const compiler = await compilerDownloader.getCompiler(version); + if (hasNativeBuildForPlatform(version, platform)) { + const compilerDownloader = new CompilerDownloaderImplementation( + platform, + await getGlobalCompilersCacheDir(), + ); + + const compiler = await compilerDownloader.getCompiler(version); - if (compiler !== undefined) { - return compiler; + if (compiler !== undefined) { + return compiler; + } } } diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/solc-info.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/solc-info.ts index 09cd69663ac..30194857c97 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/solc-info.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity/build-system/solc-info.ts @@ -5,6 +5,10 @@ import semver from "semver"; // The first solc version with official ARM64 Linux builds export const FIRST_OFFICIAL_ARM64_SOLC_VERSION = "0.8.31"; +// The lowest solc version available in the frozen ARM64 mirror repo +// (https://github.com/NomicFoundation/solc-linux-arm64-mirror/tree/main/public/linux/aarch64) +export const FIRST_ARM64_MIRROR_SOLC_VERSION = "0.5.0"; + /** * Determines if a solc version has an official ARM64 Linux build. */ @@ -12,6 +16,17 @@ export function hasOfficialArm64Build(version: string): boolean { return semver.gte(version, FIRST_OFFICIAL_ARM64_SOLC_VERSION); } +/** + * Determines if a solc version has a native ARM64 Linux build available + * in the community mirror (versions 0.5.0–0.8.30). + */ +export function hasArm64MirrorBuild(version: string): boolean { + return ( + semver.gte(version, FIRST_ARM64_MIRROR_SOLC_VERSION) === true && + semver.lt(version, FIRST_OFFICIAL_ARM64_SOLC_VERSION) === true + ); +} + /** * Returns true if running on a platform that doesn't have official native * solc builds for all versions (currently ARM64 Linux before 0.8.31). diff --git a/v-next/hardhat/src/internal/builtin-plugins/solidity/config.ts b/v-next/hardhat/src/internal/builtin-plugins/solidity/config.ts index d25ac246c19..7755712055c 100644 --- a/v-next/hardhat/src/internal/builtin-plugins/solidity/config.ts +++ b/v-next/hardhat/src/internal/builtin-plugins/solidity/config.ts @@ -22,6 +22,7 @@ import { z } from "zod"; import { DEFAULT_BUILD_PROFILES } from "./build-profiles.js"; import { + hasArm64MirrorBuild, hasOfficialArm64Build, missesSomeOfficialNativeBuilds, } from "./build-system/solc-info.js"; @@ -343,14 +344,20 @@ function resolveSolcConfig( // Resolve per-compiler preferWasm: // If explicitly set, use that value. - // Otherwise, for ARM64 Linux in production, default to true only for - // versions without official ARM64 builds. + // Otherwise, for ARM64 Linux: + // - Versions below the mirror threshold (< 0.5.0) always use WASM, + // since no native ARM64 build exists anywhere. + // - In production, versions without official ARM64 builds + // also default to WASM. let resolvedPreferWasm: boolean | undefined = solcConfig.preferWasm; if (resolvedPreferWasm === undefined && missesSomeOfficialNativeBuilds()) { - resolvedPreferWasm = - production && !hasOfficialArm64Build(solcConfig.version) - ? true - : undefined; + const version = solcConfig.version; + + if (!hasOfficialArm64Build(version) && !hasArm64MirrorBuild(version)) { + resolvedPreferWasm = true; + } else if (production && !hasOfficialArm64Build(version)) { + resolvedPreferWasm = true; + } } return { diff --git a/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts b/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts index 72b397e54cb..b5cb2f10b63 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/downloader.ts @@ -574,6 +574,38 @@ describe( assert.equal(compiler.version, "0.8.28"); }); }); + + describe("versions missing from the native build list", function () { + it("isCompilerDownloaded should throw for a version not in the native build list", async () => { + await downloader.updateCompilerListIfNeeded(new Set(["0.8.28"])); + + await assertRejectsWithHardhatError( + () => downloader.isCompilerDownloaded("0.4.24"), + HardhatError.ERRORS.CORE.SOLIDITY.INVALID_SOLC_VERSION, + { version: "0.4.24" }, + ); + }); + + it("downloadCompiler should throw for a version not in the native build list", async () => { + await downloader.updateCompilerListIfNeeded(new Set(["0.8.28"])); + + await assertRejectsWithHardhatError( + () => downloader.downloadCompiler("0.4.24"), + HardhatError.ERRORS.CORE.SOLIDITY.INVALID_SOLC_VERSION, + { version: "0.4.24" }, + ); + }); + + it("getCompiler should throw for a version not in the native build list", async () => { + await downloader.updateCompilerListIfNeeded(new Set(["0.8.28"])); + + await assertRejectsWithHardhatError( + () => downloader.getCompiler("0.4.24"), + HardhatError.ERRORS.CORE.SOLIDITY.INVALID_SOLC_VERSION, + { version: "0.4.24" }, + ); + }); + }); }); }, ); diff --git a/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/index.ts b/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/index.ts index fcdecffe9f5..4205446a9b9 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/index.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/compiler/index.ts @@ -1,9 +1,14 @@ import type { CompilerInput } from "../../../../../../src/types/solidity.js"; import assert from "node:assert/strict"; -import { before, beforeEach, describe, it } from "node:test"; +import { after, before, beforeEach, describe, it } from "node:test"; -import { useTmpDir } from "@nomicfoundation/hardhat-test-utils"; +import { getTmpDir, useTmpDir } from "@nomicfoundation/hardhat-test-utils"; +import { remove } from "@nomicfoundation/hardhat-utils/fs"; +import { + resetMockCacheDir, + setMockCacheDir, +} from "@nomicfoundation/hardhat-utils/global-dir"; import { NativeCompiler, @@ -13,6 +18,15 @@ import { CompilerDownloaderImplementation as CompilerDownloader, CompilerPlatform, } from "../../../../../../src/internal/builtin-plugins/solidity/build-system/compiler/downloader.js"; +import { + downloadSolcCompilers, + getCompiler, + hasNativeBuildForPlatform, +} from "../../../../../../src/internal/builtin-plugins/solidity/build-system/compiler/index.js"; +import { + hasArm64MirrorBuild, + hasOfficialArm64Build, +} from "../../../../../../src/internal/builtin-plugins/solidity/build-system/solc-info.js"; import { spawn } from "../../../../../../src/internal/cli/init/subprocess.js"; const solcVersion = "0.6.6"; @@ -268,5 +282,92 @@ contract A {} ); }); }); + + describe("hasNativeBuildForPlatform", function () { + it("returns true for all versions on non-ARM64 platforms", () => { + for (const platform of [ + CompilerPlatform.LINUX, + CompilerPlatform.MACOS, + CompilerPlatform.WINDOWS, + CompilerPlatform.WASM, + ]) { + assert.equal(hasNativeBuildForPlatform("0.4.0", platform), true); + assert.equal(hasNativeBuildForPlatform("0.4.24", platform), true); + assert.equal(hasNativeBuildForPlatform("0.5.0", platform), true); + assert.equal(hasNativeBuildForPlatform("0.8.28", platform), true); + assert.equal(hasNativeBuildForPlatform("0.8.31", platform), true); + } + }); + + it("returns false for versions below 0.5.0 on ARM64", () => { + assert.equal( + hasNativeBuildForPlatform("0.4.0", CompilerPlatform.LINUX_ARM64), + false, + ); + assert.equal( + hasNativeBuildForPlatform("0.4.24", CompilerPlatform.LINUX_ARM64), + false, + ); + }); + + it("returns true for mirror-range versions on ARM64", () => { + assert.equal( + hasNativeBuildForPlatform("0.5.0", CompilerPlatform.LINUX_ARM64), + true, + ); + assert.equal( + hasNativeBuildForPlatform("0.8.30", CompilerPlatform.LINUX_ARM64), + true, + ); + }); + + it("returns true for official ARM64 versions on ARM64", () => { + assert.equal( + hasNativeBuildForPlatform("0.8.31", CompilerPlatform.LINUX_ARM64), + true, + ); + assert.equal( + hasNativeBuildForPlatform("0.9.0", CompilerPlatform.LINUX_ARM64), + true, + ); + }); + }); + + describe( + "ARM64 WASM fallback for old versions", + { + skip: + CompilerDownloader.getCompilerPlatform() !== + CompilerPlatform.LINUX_ARM64, + }, + function () { + let testCacheDir: string; + + before(async function () { + testCacheDir = await getTmpDir("arm64-wasm-fallback"); + setMockCacheDir(testCacheDir); + }); + + after(async function () { + resetMockCacheDir(); + await remove(testCacheDir); + }); + + it("should fall back to WASM when no native ARM64 build exists for 0.4.x", async () => { + const version = "0.4.24"; + + // Verify our precondition: 0.4.24 has no native ARM64 build + assert.equal(hasOfficialArm64Build(version), false); + assert.equal(hasArm64MirrorBuild(version), false); + + // Use the high-level download + get functions that automatically + // skip native on ARM64 for old versions and fall back to WASM. + await downloadSolcCompilers(new Set([version]), true); + const compiler = await getCompiler(version, { preferWasm: false }); + + assert.ok(compiler.isSolcJs, "Should be a WASM compiler"); + }); + }, + ); }, ); diff --git a/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/solc-info.ts b/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/solc-info.ts index d33ae2cab10..c9c5a6eff29 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/solc-info.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/solidity/build-system/solc-info.ts @@ -3,6 +3,7 @@ import os from "node:os"; import { describe, it } from "node:test"; import { + hasArm64MirrorBuild, hasOfficialArm64Build, missesSomeOfficialNativeBuilds, } from "../../../../../src/internal/builtin-plugins/solidity/build-system/solc-info.js"; @@ -24,6 +25,26 @@ describe("solc-info", () => { }); }); + describe("hasArm64MirrorBuild", () => { + it("returns false for versions before 0.5.0 (no native ARM64 build anywhere)", () => { + assert.equal(hasArm64MirrorBuild("0.4.0"), false); + assert.equal(hasArm64MirrorBuild("0.4.26"), false); + }); + + it("returns true for versions in the mirror range (0.5.0 to 0.8.30)", () => { + assert.equal(hasArm64MirrorBuild("0.5.0"), true); + assert.equal(hasArm64MirrorBuild("0.6.12"), true); + assert.equal(hasArm64MirrorBuild("0.8.0"), true); + assert.equal(hasArm64MirrorBuild("0.8.30"), true); + }); + + it("returns false for versions with official ARM64 builds (>= 0.8.31)", () => { + assert.equal(hasArm64MirrorBuild("0.8.31"), false); + assert.equal(hasArm64MirrorBuild("0.9.0"), false); + assert.equal(hasArm64MirrorBuild("1.0.0"), false); + }); + }); + describe("missesSomeOfficialNativeBuilds", () => { it("returns a boolean based on the platform", () => { const result = missesSomeOfficialNativeBuilds(); diff --git a/v-next/hardhat/test/internal/builtin-plugins/solidity/config.ts b/v-next/hardhat/test/internal/builtin-plugins/solidity/config.ts index 6bfbb6a783f..cbfa10fe05c 100644 --- a/v-next/hardhat/test/internal/builtin-plugins/solidity/config.ts +++ b/v-next/hardhat/test/internal/builtin-plugins/solidity/config.ts @@ -654,6 +654,28 @@ describe("solidity plugin config resolution", () => { () => { const otherResolvedConfig = { paths: { root: process.cwd() } } as any; + it("should default preferWasm to true for versions below the ARM64 mirror threshold in all profiles", async () => { + const resolvedConfig = await resolveSolidityUserConfig( + { + solidity: { + compilers: [ + { version: "0.4.24" }, // No native ARM64 build anywhere + ], + }, + }, + otherResolvedConfig, + ); + + // Both default and production profiles should force WASM for < 0.5.0 + const defaultCompilers = + resolvedConfig.solidity.profiles.default.compilers; + assert.equal(defaultCompilers[0].preferWasm, true); + + const productionCompilers = + resolvedConfig.solidity.profiles.production.compilers; + assert.equal(productionCompilers[0].preferWasm, true); + }); + it("should default preferWasm to true in production profile for versions without official ARM64 builds", async () => { const resolvedConfig = await resolveSolidityUserConfig( { @@ -694,12 +716,12 @@ describe("solidity plugin config resolution", () => { assert.equal(productionCompilers[1].preferWasm, undefined); }); - it("should leave preferWasm undefined in default profile for all versions", async () => { + it("should leave preferWasm undefined in default profile for versions with native builds", async () => { const resolvedConfig = await resolveSolidityUserConfig( { solidity: { compilers: [ - { version: "0.8.28" }, // No official ARM64 build + { version: "0.8.28" }, // In mirror range — has native build { version: "0.8.31" }, // Has official ARM64 build ], }, @@ -707,7 +729,7 @@ describe("solidity plugin config resolution", () => { otherResolvedConfig, ); - // Default profile gets preferWasm: undefined for all versions + // Default profile gets preferWasm: undefined for versions that have native builds const defaultCompilers = resolvedConfig.solidity.profiles.default.compilers; assert.equal(defaultCompilers[0].preferWasm, undefined);