UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

290 lines 13.6 kB
import { execFile } from "node:child_process"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import { HardhatError, assertHardhatInvariant, } from "@nomicfoundation/hardhat-errors"; import { bytesToHexString } from "@nomicfoundation/hardhat-utils/bytes"; import { sha256 } from "@nomicfoundation/hardhat-utils/crypto"; import { createDebug } from "@nomicfoundation/hardhat-utils/debug"; import { ensureError } from "@nomicfoundation/hardhat-utils/error"; import { chmod, createFile, ensureDir, exists, readBinaryFile, readJsonFile, remove, writeJsonFile, } from "@nomicfoundation/hardhat-utils/fs"; import { getPrefixedHexString } from "@nomicfoundation/hardhat-utils/hex"; import { download } from "@nomicfoundation/hardhat-utils/request"; import { MultiProcessMutex } from "@nomicfoundation/hardhat-utils/synchronization"; import AdmZip from "adm-zip"; import { NativeCompiler, SolcJsCompiler } from "./compiler.js"; const log = createDebug("hardhat:core:solidity:build-system:compiler:downloader"); const COMPILER_REPOSITORY_URL = "https://binaries.soliditylang.org"; const DEFAULT_COMPILER_DOWNLOAD_RETRY_COUNT = 3; const DEFAULT_COMPILER_DOWNLOAD_RETRY_DELAY_MS = 2000; // We use a mirror of nikitastupin/solc because downloading directly from // github has rate limiting issues const LINUX_ARM64_REPOSITORY_URL = "https://solc-linux-arm64-mirror.hardhat.org/linux/aarch64"; export var CompilerPlatform; (function (CompilerPlatform) { CompilerPlatform["LINUX"] = "linux-amd64"; CompilerPlatform["LINUX_ARM64"] = "linux-arm64"; CompilerPlatform["WINDOWS"] = "windows-amd64"; CompilerPlatform["MACOS"] = "macosx-amd64"; CompilerPlatform["WASM"] = "wasm"; })(CompilerPlatform || (CompilerPlatform = {})); /** * Default implementation of CompilerDownloader. */ export class CompilerDownloaderImplementation { static getCompilerPlatform() { // TODO: This check is seriously wrong. It doesn't take into account // the architecture nor the toolchain. This should check the triplet of // system instead (see: https://wiki.osdev.org/Target_Triplet). // // The only reason this downloader works is that it validates if the // binaries actually run. // // On top of that, AppleSilicon with Rosetta2 makes things even more // complicated, as it allows x86 binaries to run on ARM, not on MacOS but // on Linux Docker containers too! // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- Ignore other platforms switch (os.platform()) { case "win32": return CompilerPlatform.WINDOWS; case "linux": if (os.arch() === "arm64") { return CompilerPlatform.LINUX_ARM64; } else { return CompilerPlatform.LINUX; } case "darwin": return CompilerPlatform.MACOS; default: return CompilerPlatform.WASM; } } #platform; #compilersDir; #downloadFunction; #mutexCompilerList; constructor(platform, compilersDir, downloadFunction = download) { this.#platform = platform; this.#compilersDir = compilersDir; this.#mutexCompilerList = new MultiProcessMutex(path.join(compilersDir, "compiler-download-list")); this.#downloadFunction = downloadFunction; } async updateCompilerListIfNeeded(versions) { await this.#mutexCompilerList.use(async () => { if (await this.#shouldDownloadCompilerList(versions)) { try { log(`Downloading the list of solc builds for platform ${this.#platform}`); await this.#downloadCompilerList(); } catch (e) { ensureError(e); throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY.VERSION_LIST_DOWNLOAD_FAILED, e); } } }); } async isCompilerDownloaded(version) { const build = await this.#getCompilerBuild(version); const downloadPath = this.#getCompilerBinaryPathFromBuild(build); return await exists(downloadPath); } async downloadCompiler(version) { // A per-version mutex ensures that only one process at a time can download a given compiler version, // while still allowing different compiler versions to be downloaded in parallel. // Without the mutex, a concurrent process might check whether a version exists, incorrectly // find it missing (because another process is still downloading it), and start a redundant download. const mutex = new MultiProcessMutex(path.join(this.#compilersDir, `compiler-download-${version}`)); return await mutex.use(async () => { const isCompilerDownloaded = await this.isCompilerDownloaded(version); if (isCompilerDownloaded === true) { return true; } const build = await this.#getCompilerBuild(version); let downloadPath = ""; for (let i = 0; i <= DEFAULT_COMPILER_DOWNLOAD_RETRY_COUNT; i++) { try { downloadPath = await this.#downloadAndVerifyCompiler(build); break; } catch (e) { if (i === DEFAULT_COMPILER_DOWNLOAD_RETRY_COUNT) { ensureError(e); throw e; } else { const attempt = i + 1; log(`Download or verification failed for solc ${version}, retrying (attempt ${attempt} of ${DEFAULT_COMPILER_DOWNLOAD_RETRY_COUNT})`); await new Promise((resolve) => setTimeout(resolve, DEFAULT_COMPILER_DOWNLOAD_RETRY_DELAY_MS)); } } } return await this.#postProcessCompilerDownload(build, downloadPath); }); } async getCompiler(version) { const build = await this.#getCompilerBuild(version); const compilerPath = this.#getCompilerBinaryPathFromBuild(build); assertHardhatInvariant(await exists(compilerPath), `Trying to get a compiler ${version} before it was downloaded`); if (await exists(this.#getCompilerDoesNotWorkFile(build))) { return undefined; } if (this.#platform === CompilerPlatform.WASM) { return new SolcJsCompiler(version, build.longVersion, compilerPath); } return new NativeCompiler(version, build.longVersion, compilerPath); } async #getCompilerBuild(version) { const listPath = this.#getCompilerListPath(); assertHardhatInvariant(await exists(listPath), `Trying to get the compiler list for ${this.#platform} before it was downloaded`); const list = await this.#readCompilerList(listPath); const build = list.builds.find((b) => b.version === version && b.prerelease === undefined); if (build === undefined) { throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY.INVALID_SOLC_VERSION, { version, }); } return build; } #getCompilerListPath() { return path.join(this.#compilersDir, this.#platform, "list.json"); } async #readCompilerList(listPath) { return await readJsonFile(listPath); } #getCompilerDownloadPathFromBuild(build) { return path.join(this.#compilersDir, this.#platform, build.path); } #getCompilerBinaryPathFromBuild(build) { const downloadPath = this.#getCompilerDownloadPathFromBuild(build); if (this.#platform !== CompilerPlatform.WINDOWS || !downloadPath.endsWith(".zip")) { return downloadPath; } return path.join(this.#compilersDir, build.version, "solc.exe"); } #getCompilerDoesNotWorkFile(build) { return `${this.#getCompilerBinaryPathFromBuild(build)}.does.not.work`; } async #shouldDownloadCompilerList(versions) { const listPath = this.#getCompilerListPath(); log(`Checking if the compiler list for ${this.#platform} should be downloaded at ${listPath}`); if (!(await exists(listPath))) { return true; } const list = await this.#readCompilerList(listPath); const listVersions = new Set(list.builds.map((b) => b.version)); for (const version of versions) { if (!listVersions.has(version)) { // TODO: We should also check if it wasn't downloaded soon ago return true; } } // download the list in case the cached list contains older ARM64 Linux builds without URL return list.builds .map((b) => b.path.startsWith("solc-v") && b.url === undefined) .reduce((a, b) => a || b, false); } async #downloadCompilerList() { log(`Downloading compiler list for platform ${this.#platform}`); const downloadPath = this.#getCompilerListPath(); // download hte official solc compiler list (now that ARM64 Linus is supported) await this.#downloadFunction(`${COMPILER_REPOSITORY_URL}/${this.#platform}/list.json`, downloadPath); // for Linux ARM64, we need to merge the official list with our custom builds if (this.#platform === CompilerPlatform.LINUX_ARM64) { // cache the official list since the file will be overwritten below const officialCompilerList = await readJsonFile(downloadPath); await this.#downloadFunction(`${LINUX_ARM64_REPOSITORY_URL}/list.json`, downloadPath); // add missing information and an explicit URL for download const armLinuxcompilerList = await readJsonFile(downloadPath); for (const build of armLinuxcompilerList.builds) { build.path = `solc-v${build.version}`; build.url = LINUX_ARM64_REPOSITORY_URL; build.longVersion = build.version; } // merge the official and custom lists officialCompilerList.builds = officialCompilerList.builds.concat(armLinuxcompilerList.builds); officialCompilerList.releases = { ...officialCompilerList.releases, ...armLinuxcompilerList.releases, }; await writeJsonFile(downloadPath, officialCompilerList); } } async #downloadAndVerifyCompiler(build) { let downloadPath = ""; try { downloadPath = await this.#downloadCompiler(build); } catch (e) { ensureError(e); throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY.DOWNLOAD_FAILED, { remoteVersion: build.longVersion, }, e); } const verified = await this.#verifyCompilerDownload(build, downloadPath); if (!verified) { throw new HardhatError(HardhatError.ERRORS.CORE.SOLIDITY.INVALID_DOWNLOAD, { remoteVersion: build.longVersion, }); } return downloadPath; } async #downloadCompiler(build) { // use the explicit URL if available or the default solc download URL if not const defaultUrl = `${COMPILER_REPOSITORY_URL}/${this.#platform}`; const url = `${build.url ?? defaultUrl}/${build.path}`; log(`Downloading compiler ${build.version} from ${url}`); const downloadPath = this.#getCompilerDownloadPathFromBuild(build); await this.#downloadFunction(url, downloadPath); return downloadPath; } async #verifyCompilerDownload(build, downloadPath) { const expectedSha = getPrefixedHexString(build.sha256); const compiler = await readBinaryFile(downloadPath); const compilerSha = bytesToHexString(await sha256(compiler)); if (expectedSha !== compilerSha) { await remove(downloadPath); return false; } return true; } async #postProcessCompilerDownload(build, downloadPath) { if (this.#platform === CompilerPlatform.WASM) { return true; } if (this.#platform === CompilerPlatform.LINUX || this.#platform === CompilerPlatform.LINUX_ARM64 || this.#platform === CompilerPlatform.MACOS) { await chmod(downloadPath, 0o755); } else if (this.#platform === CompilerPlatform.WINDOWS && downloadPath.endsWith(".zip")) { // some window builds are zipped, some are not const solcFolder = path.join(this.#compilersDir, build.version); await ensureDir(solcFolder); const zip = new AdmZip(downloadPath); zip.extractAllTo(solcFolder); } log("Checking native solc binary"); const nativeSolcWorks = await this.#checkNativeSolc(build); if (nativeSolcWorks) { return true; } await createFile(this.#getCompilerDoesNotWorkFile(build)); return false; } async #checkNativeSolc(build) { const solcPath = this.#getCompilerBinaryPathFromBuild(build); const execFileP = promisify(execFile); try { await execFileP(solcPath, ["--version"]); return true; } catch { log(`solc binary at ${solcPath} is not working`); return false; } } } //# sourceMappingURL=downloader.js.map