Skip to content
Merged
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/thick-schools-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hardhat": patch
---

Fixed compiler download for old versions in ARM64 ([8062](https://github.com/NomicFoundation/hardhat/pull/8062)).
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,6 @@ export class CompilerDownloaderImplementation implements CompilerDownloader {
public async getCompiler(version: string): Promise<Compiler | undefined> {
const build = await this.#getCompilerBuild(version);

assertHardhatInvariant(
Copy link
Copy Markdown
Contributor Author

@ChristopherDedominici ChristopherDedominici Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertHardhatInvariant was redundant because #getCompilerBuild() (in this file) already guarantees build is never undefined, if the version isn't found, it throws a HardhatError

build !== undefined,
`Trying to get a compiler ${version} before it was downloaded`,
);

const compilerPath = this.#getCompilerBinaryPathFromBuild(build);

assertHardhatInvariant(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -28,6 +30,25 @@ async function getGlobalCompilersCacheDir(): Promise<string> {

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<string>,
quiet: boolean,
Expand All @@ -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}`);
Expand Down Expand Up @@ -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)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this double-protection here, in case preferWasm: false is wrong used. It also aligns well with "prefer", it's not a strict mandate.

const compilerDownloader = new CompilerDownloaderImplementation(
platform,
await getGlobalCompilersCacheDir(),
);

const compiler = await compilerDownloader.getCompiler(version);

if (compiler !== undefined) {
return compiler;
if (compiler !== undefined) {
return compiler;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,28 @@ 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.
*/
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).
Expand Down
19 changes: 13 additions & 6 deletions v-next/hardhat/src/internal/builtin-plugins/solidity/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"]));

Comment thread
ChristopherDedominici marked this conversation as resolved.
await assertRejectsWithHardhatError(
() => downloader.getCompiler("0.4.24"),
HardhatError.ERRORS.CORE.SOLIDITY.INVALID_SOLC_VERSION,
{ version: "0.4.24" },
);
});
});
});
},
);
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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";
Expand Down Expand Up @@ -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);
});
Comment thread
ChristopherDedominici marked this conversation as resolved.

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");
});
},
);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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();
Expand Down
Loading
Loading