UNPKG

hardhat

Version:

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

269 lines 12 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 { 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 debug from "debug"; import { NativeCompiler, SolcJsCompiler } from "./compiler.js"; const log = debug("hardhat:solidity:downloader"); const COMPILER_REPOSITORY_URL = "https://binaries.soliditylang.org"; // 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-aarch64"; 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; #mutexCompiler = new MultiProcessMutex("compiler-download"); #mutexCompilerList = new MultiProcessMutex("compiler-download-list"); /** * Use CompilerDownloader.getConcurrencySafeDownloader instead */ constructor(platform, compilersDir, downloadFunction = download) { this.#platform = platform; this.#compilersDir = compilersDir; 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 exists(downloadPath); } async downloadCompiler(version) { // Since only one process at a time can acquire the mutex, we avoid the risk of downloading the same compiler multiple times. // This is because the mutex blocks access until a compiler has been fully downloaded, preventing any new process // from checking whether that version of the compiler exists. Without mutex it might incorrectly // return false, indicating that the compiler isn't present, even though it is currently being downloaded. return this.#mutexCompiler.use(async () => { const isCompilerDownloaded = await this.isCompilerDownloaded(version); if (isCompilerDownloaded === true) { return true; } const build = await this.#getCompilerBuild(version); 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 this.#postProcessCompilerDownload(build, downloadPath); }); } async getCompiler(version) { 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(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 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; } } return false; } async #downloadCompilerList() { log(`Downloading compiler list for platform ${this.#platform}`); let url; if (this.#onLinuxArm()) { url = `${LINUX_ARM64_REPOSITORY_URL}/list.json`; } else { url = `${COMPILER_REPOSITORY_URL}/${this.#platform}/list.json`; } const downloadPath = this.#getCompilerListPath(); await this.#downloadFunction(url, downloadPath); // If using the arm64 binary mirror, the list.json file has different information than the solc official mirror, so we complete it if (this.#onLinuxArm()) { const compilerList = await readJsonFile(downloadPath); for (const build of compilerList.builds) { build.path = `solc-v${build.version}`; build.longVersion = build.version; } await writeJsonFile(downloadPath, compilerList); } } #onLinuxArm() { return this.#platform === CompilerPlatform.LINUX_ARM64; } async #downloadCompiler(build) { let url; if (this.#onLinuxArm()) { url = `${LINUX_ARM64_REPOSITORY_URL}/${build.path}`; } else { url = `${COMPILER_REPOSITORY_URL}/${this.#platform}/${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 { default: AdmZip } = await import("adm-zip"); 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