@nomiclabs/buidler
Version:
Buidler is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.
177 lines (143 loc) • 4.75 kB
text/typescript
import fsExtra from "fs-extra";
import path from "path";
import { BuidlerError } from "../../core/errors";
import { ERRORS } from "../../core/errors-list";
export interface CompilerBuild {
path: string;
version: string;
build: string;
longVersion: string;
keccak256: string;
urls: string[];
}
export interface CompilersList {
builds: CompilerBuild[];
releases: {
[version: string]: string;
};
latestRelease: string;
}
const COMPILER_FILES_DIR_URL =
"https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/";
const COMPILERS_LIST_URL = `${COMPILER_FILES_DIR_URL}list.json`;
async function downloadFile(
url: string,
destinationFile: string
): Promise<void> {
// This library indirectly validates the TLS certs, if it didn't this
// would be MITM-able.
const { default: download } = await import("download");
await download(url, path.dirname(destinationFile), {
filename: path.basename(destinationFile),
});
}
export class CompilerDownloader {
private readonly _compilersDir: string;
private readonly _localSolcVersion: string;
private readonly _download: (
url: string,
destinationFile: string
) => Promise<void>;
constructor(
readonly compilersDir: string,
readonly localSolcVersion: string,
readonly download = downloadFile
) {
this._compilersDir = compilersDir;
this._localSolcVersion = localSolcVersion;
this._download = download;
}
public async getDownloadedCompilerPath(version: string): Promise<string> {
const compilerBuild = await this.getCompilerBuild(version);
const downloadedFilePath = path.join(
this._compilersDir,
compilerBuild.path
);
if (!(await this._fileExists(downloadedFilePath))) {
await this.downloadCompiler(compilerBuild, downloadedFilePath);
}
await this.verifyCompiler(compilerBuild, downloadedFilePath);
return downloadedFilePath;
}
public async getCompilerBuild(version: string): Promise<CompilerBuild> {
const compilersListExisted = await this.compilersListExists();
let list = await this.getCompilersList();
let compilerBuildPath = list.releases[version];
// We may need to re-download the compilers list.
if (compilerBuildPath === undefined && compilersListExisted) {
await fsExtra.unlink(this.getCompilersListPath());
list = await this.getCompilersList();
compilerBuildPath = list.releases[version];
}
const compilerBuild = list.builds.find((b) => b.path === compilerBuildPath);
if (compilerBuild === undefined) {
throw new BuidlerError(ERRORS.SOLC.INVALID_VERSION, { version });
}
return compilerBuild;
}
public async getCompilersList(): Promise<CompilersList> {
if (!(await this.compilersListExists())) {
await this.downloadCompilersList();
}
return fsExtra.readJson(this.getCompilersListPath());
}
public getCompilersListPath() {
return path.join(this._compilersDir, "list.json");
}
public async compilersListExists() {
return fsExtra.pathExists(this.getCompilersListPath());
}
public async downloadCompilersList() {
try {
await this._download(COMPILERS_LIST_URL, this.getCompilersListPath());
} catch (error) {
throw new BuidlerError(
ERRORS.SOLC.VERSION_LIST_DOWNLOAD_FAILED,
{
localVersion: this._localSolcVersion,
},
error
);
}
}
public async downloadCompiler(
compilerBuild: CompilerBuild,
downloadedFilePath: string
) {
console.debug(`Downloading compiler version ${compilerBuild.version}`);
const compilerUrl = COMPILER_FILES_DIR_URL + compilerBuild.path;
try {
await this._download(compilerUrl, downloadedFilePath);
} catch (error) {
throw new BuidlerError(
ERRORS.SOLC.DOWNLOAD_FAILED,
{
remoteVersion: compilerBuild.version,
localVersion: this._localSolcVersion,
},
error
);
}
}
public async verifyCompiler(
compilerBuild: CompilerBuild,
downloadedFilePath: string
) {
const ethereumjsUtil = await import("ethereumjs-util");
const expectedKeccak256 = compilerBuild.keccak256;
const compiler = await fsExtra.readFile(downloadedFilePath);
const compilerKeccak256 = ethereumjsUtil.bufferToHex(
ethereumjsUtil.keccak(compiler)
);
if (expectedKeccak256 !== compilerKeccak256) {
await fsExtra.unlink(downloadedFilePath);
throw new BuidlerError(ERRORS.SOLC.INVALID_DOWNLOAD, {
remoteVersion: compilerBuild.version,
localVersion: this._localSolcVersion,
});
}
}
private async _fileExists(filePath: string) {
return fsExtra.pathExists(filePath);
}
}