UNPKG

@truffle/compile-solidity

Version:

Compiler helper and artifact manager for Solidity files

229 lines (198 loc) 7.54 kB
const debug = require("debug")("compile:compilerSupplier"); const requireFromString = require("require-from-string"); const fs = require("fs"); const originalRequire = require("original-require"); const axios = require("axios").default; const semver = require("semver"); const solcWrap = require("solc/wrapper"); const LoadingStrategy = require("./LoadingStrategy"); class VersionRange extends LoadingStrategy { compilerFromString(code) { const markedListeners = this.markListeners(); try { const soljson = requireFromString(code); return solcWrap(soljson); } finally { this.removeListener(markedListeners); } } findNewestValidVersion(version, allVersions) { if (!semver.validRange(version)) return null; const satisfyingVersions = Object.keys(allVersions.releases) .map(solcVersion => { if (semver.satisfies(solcVersion, version)) return solcVersion; }) .filter(solcVersion => solcVersion); if (satisfyingVersions.length > 0) { return satisfyingVersions.reduce((newestVersion, version) => { return semver.gtr(version, newestVersion) ? version : newestVersion; }, "0.0.0"); } else { return null; } } getCachedSolcByFileName(fileName) { const markedListeners = this.markListeners(); try { const filePath = this.resolveCache(fileName); const soljson = originalRequire(filePath); debug("soljson %o", soljson); return solcWrap(soljson); } finally { this.removeListener(markedListeners); } } // Range can also be a single version specification like "0.5.0" getCachedSolcByVersionRange(version) { const cachedCompilerFileNames = fs.readdirSync(this.compilerCachePath); const validVersions = cachedCompilerFileNames.filter(fileName => { const match = fileName.match(/v\d+\.\d+\.\d+.*/); if (match) return semver.satisfies(match[0], version); }); const multipleValidVersions = validVersions.length > 1; const compilerFileName = multipleValidVersions ? this.getMostRecentVersionOfCompiler(validVersions) : validVersions[0]; return this.getCachedSolcByFileName(compilerFileName); } getCachedSolcFileName(commit) { const cachedCompilerFileNames = fs.readdirSync(this.compilerCachePath); return cachedCompilerFileNames.find(fileName => { return fileName.includes(commit); }); } getMostRecentVersionOfCompiler(versions) { return versions.reduce((mostRecentVersionFileName, fileName) => { const match = fileName.match(/v\d+\.\d+\.\d+.*/); const mostRecentVersionMatch = mostRecentVersionFileName.match( /v\d+\.\d+\.\d+.*/ ); return semver.gtr(match[0], mostRecentVersionMatch[0]) ? fileName : mostRecentVersionFileName; }, "-v0.0.0+commit"); } getSatisfyingVersionFromCache(versionRange) { if (this.versionIsCached(versionRange)) { return this.getCachedSolcByVersionRange(versionRange); } throw this.errors("noVersion", versionRange); } async getSolcByCommit(commit) { const solcFileName = this.getCachedSolcFileName(commit); if (solcFileName) return this.getCachedSolcByFileName(solcFileName); const allVersions = await this.getSolcVersions(); const fileName = this.getSolcVersionFileName(commit, allVersions); if (!fileName) throw new Error("No matching version found"); return this.getSolcByUrlAndCache(fileName); } async getSolcByUrlAndCache(fileName, index = 0) { const url = `${this.config.compilerRoots[index].replace( /\/+$/, "" )}/${fileName}`; const { events } = this.config; events.emit("downloadCompiler:start", { attemptNumber: index + 1 }); try { const response = await axios.get(url, { maxRedirects: 50 }); events.emit("downloadCompiler:succeed"); this.addFileToCache(response.data, fileName); return this.compilerFromString(response.data); } catch (error) { events.emit("downloadCompiler:fail"); if (index >= this.config.compilerRoots.length - 1) { throw this.errors("noRequest", "compiler URLs", error); } return this.getSolcByUrlAndCache(fileName, index + 1); } } async getSolcFromCacheOrUrl(versionConstraint) { let allVersions, versionToUse; try { allVersions = await this.getSolcVersions(); } catch (error) { throw this.errors("noRequest", versionConstraint, error); } const isVersionRange = !semver.valid(versionConstraint); versionToUse = isVersionRange ? this.findNewestValidVersion(versionConstraint, allVersions) : versionConstraint; const fileName = this.getSolcVersionFileName(versionToUse, allVersions); if (!fileName) throw this.errors("noVersion", versionToUse); if (this.fileIsCached(fileName)) return this.getCachedSolcByFileName(fileName); return this.getSolcByUrlAndCache(fileName); } getSolcVersions(index = 0) { const { events } = this.config; events.emit("fetchSolcList:start", { attemptNumber: index + 1 }); if (!this.config.compilerRoots || this.config.compilerRoots.length < 1) { events.emit("fetchSolcList:fail"); throw this.errors("noUrl"); } const { compilerRoots } = this.config; // trim trailing slashes from compilerRoot const url = `${compilerRoots[index].replace(/\/+$/, "")}/list.json`; return axios .get(url, { maxRedirects: 50 }) .then(response => { events.emit("fetchSolcList:succeed"); return response.data; }) .catch(error => { events.emit("fetchSolcList:fail"); if (index >= this.config.compilerRoots.length - 1) { throw this.errors("noRequest", "version URLs", error); } return this.getSolcVersions(index + 1); }); } getSolcVersionFileName(version, allVersions) { if (allVersions.releases[version]) return allVersions.releases[version]; const isPrerelease = version.includes("nightly") || version.includes("commit"); if (isPrerelease) { for (let build of allVersions.builds) { const exists = build["prerelease"] === version || build["build"] === version || build["longVersion"] === version; if (exists) return build["path"]; } } const versionToUse = this.findNewestValidVersion(version, allVersions); if (versionToUse) return allVersions.releases[versionToUse]; return null; } async load(versionRange) { const rangeIsSingleVersion = semver.valid(versionRange); if (rangeIsSingleVersion && this.versionIsCached(versionRange)) { return this.getCachedSolcByVersionRange(versionRange); } try { return await this.getSolcFromCacheOrUrl(versionRange); } catch (error) { if (error.message.includes("Failed to complete request")) { return this.getSatisfyingVersionFromCache(versionRange); } throw new Error(error); } } normalizeSolcVersion(input) { const version = String(input); return version.split(":")[1].trim(); } versionIsCached(version) { const cachedCompilerFileNames = fs.readdirSync(this.compilerCachePath); const cachedVersions = cachedCompilerFileNames.map(fileName => { const match = fileName.match(/v\d+\.\d+\.\d+.*/); if (match) return match[0]; }); return cachedVersions.find(cachedVersion => semver.satisfies(cachedVersion, version) ); } } module.exports = VersionRange;