@newos/cli
Version:
Command-line interface for the NewOS
176 lines • 8.13 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSolcBinEnv = exports.setSolcCachePath = exports.getCompilerBinary = exports.getCompiler = exports.fetchCompiler = exports.resolveCompilerVersion = void 0;
const fs_1 = __importDefault(require("fs"));
const fs_extra_1 = require("fs-extra");
const axios_1 = __importDefault(require("axios"));
const solc_wrapper_1 = __importDefault(require("solc-wrapper"));
const semver_1 = __importDefault(require("semver"));
const lodash_1 = require("lodash");
const upgrades_1 = require("@newos/upgrades");
const os_1 = require("os");
const path_1 = __importDefault(require("path"));
const child_1 = __importDefault(require("../../../utils/child"));
const try_1 = require("../../../utils/try");
const solidity_1 = require("../../../utils/solidity");
const ethereumjs_util_1 = require("ethereumjs-util");
// Downloaded compilers will be stored here.
// TODO: Check writeability and fall back to tmp if needed
let SOLC_CACHE_PATH = path_1.default.join(os_1.homedir(), '.solc');
// Modified ENV to use when running native solc
let SOLC_BIN_ENV = null;
// How frequently to renew the solc list
const SOLC_LIST_EXPIRES_IN_SECONDS = 1 * 60 * 60; // 1 hour
// and where to download it from
const SOLC_LIST_URL = 'https://solc-bin.ethereum.org/bin/list.json';
class SolcjsCompiler {
constructor(compilerBinary) {
this.compiler = solc_wrapper_1.default(compilerBinary);
}
version() {
return this.compiler.version();
}
async compile(input) {
return JSON.parse(this.compiler.compile(JSON.stringify(input), undefined));
}
}
class SolcBinCompiler {
constructor(version) {
this._version = version;
}
version() {
return this._version;
}
async compile(input) {
const output = child_1.default.execSync('solc --standard-json', {
input: JSON.stringify(input),
env: SOLC_BIN_ENV,
});
return JSON.parse(output.toString());
}
}
async function resolveCompilerVersion(requiredSemver) {
// Create an array with all unique semver restrictions (dropping initial 'v' if set manually by the user)
const requiredSemvers = lodash_1.uniq(lodash_1.compact(lodash_1.castArray(requiredSemver))).map(str => str.startsWith('v') ? str.slice(1) : str);
// TODO: Pin the compiler release, so a new release does not cause all contracts to be recompiled and thus redeployed
const solcList = await getAvailableCompilerVersions();
const build = await getCompilerVersion(requiredSemvers, solcList);
return build;
}
exports.resolveCompilerVersion = resolveCompilerVersion;
async function fetchCompiler(build) {
// Try local compiler and see if version matches
const localVersion = await localCompilerVersion();
if (localVersion && solidity_1.compilerVersionsMatch(localVersion, build.longVersion)) {
upgrades_1.Loggy.onVerbose(__filename, 'fetchCompiler', 'download-compiler', `Using local solc compiler found`);
return new SolcBinCompiler(localVersion);
}
// Go with emscriptem version if not
const localFile = path_1.default.join(SOLC_CACHE_PATH, build.path);
if (!fs_1.default.existsSync(localFile))
await downloadCompiler(build, localFile);
const compilerBinary = getCompilerBinary(localFile);
// Wrap emscriptem with solc-wrapper
return new SolcjsCompiler(compilerBinary);
}
exports.fetchCompiler = fetchCompiler;
async function getCompiler(requiredSemver) {
const version = await resolveCompilerVersion(requiredSemver);
return fetchCompiler(version);
}
exports.getCompiler = getCompiler;
async function localCompilerVersion() {
const output = await try_1.tryAwait(() => child_1.default.exec('solc --version', { env: SOLC_BIN_ENV }));
if (!output)
return null;
const match = output.stdout.match(/^Version: ([^\s]+)/m);
if (!match)
return null;
return match[1];
}
async function getAvailableCompilerVersions() {
const localPath = path_1.default.join(SOLC_CACHE_PATH, 'list.json');
await fs_extra_1.mkdirp(SOLC_CACHE_PATH);
if (fs_1.default.existsSync(localPath) && Date.now() - +fs_1.default.statSync(localPath).mtime < SOLC_LIST_EXPIRES_IN_SECONDS * 1000) {
return fs_extra_1.readJson(localPath);
}
try {
const response = await axios_1.default.get(SOLC_LIST_URL);
const list = response.data;
await fs_extra_1.writeJson(localPath, list);
return list;
}
catch (err) {
if (fs_1.default.existsSync(localPath)) {
upgrades_1.Loggy.noSpin.warn(__filename, 'getAvailableCompilerVersions', 'get-compiler-versions', `Error downloading solc releases list, using cached version`);
return fs_extra_1.readJson(localPath);
}
else {
err.message = `Could not retrieve solc releases list: ${err.message}`;
throw err;
}
}
}
async function getCompilerVersion(requiredSemvers, solcList) {
// Returns build from list given a version
const getBuild = (version) => solcList.builds.find(build => build.path === solcList.releases[version]);
// Return latest release if there are no restrictions
if (requiredSemvers.length === 0)
return getBuild(solcList.latestRelease);
// Look for the most recent release that matches semver restriction
const comparatorSet = requiredSemvers.join(' ');
const releases = Object.keys(solcList.releases);
const maxRelease = semver_1.default.maxSatisfying(releases, comparatorSet);
if (maxRelease)
return getBuild(maxRelease);
// If user asked for a specific version, look for it
if (requiredSemvers.length === 1) {
const build = lodash_1.reverse(solcList.builds).find(b => b.path.startsWith(`soljson-v${requiredSemvers[0]}`));
if (build)
return build;
}
throw new Error(`Could not find a compiler that matches required versions (${comparatorSet})`);
}
async function downloadCompiler(build, localFile) {
const { version, keccak256: expectedHash, path: versionPath } = build;
upgrades_1.Loggy.onVerbose(__filename, 'downloadCompiler', 'download-compiler', `Downloading compiler version ${version}`);
const url = `https://solc-bin.ethereum.org/bin/${versionPath}`;
const { data: compilerSource } = await axios_1.default.get(url);
// Verify checksum
const hash = '0x' + ethereumjs_util_1.keccak256(compilerSource).toString('hex');
if (hash !== expectedHash) {
throw new Error(`Checksum of compiler downloaded from ${url} does not match (expected ${expectedHash} but got ${hash})`);
}
// Cache downloaded source
await fs_extra_1.mkdirp(SOLC_CACHE_PATH);
fs_1.default.writeFileSync(localFile, compilerSource);
upgrades_1.Loggy.succeed('download-compiler');
}
function getCompilerBinary(compilerPath) {
// This magic is necessary as ts-node gets stuck when requiring the compiler large asm file
// Copied from https://github.com/nomiclabs/buidler/blob/0a551dca18522ac2ac0cf2143d71580bf29f5dce/packages/buidler-core/src/internal/solidity/compiler/index.ts#L72-L89
const Module = module.constructor;
const previousHook = Module._extensions['.js'];
Module._extensions['.js'] = function (module, filename) {
const content = fs_1.default.readFileSync(filename, 'utf8');
Object.getPrototypeOf(module)._compile.call(module, content, filename);
};
// eslint-disable-next-line @typescript-eslint/no-var-requires
const loadedSolc = require(compilerPath);
Module._extensions['.js'] = previousHook;
return loadedSolc;
}
exports.getCompilerBinary = getCompilerBinary;
// Used for tests
function setSolcCachePath(value) {
SOLC_CACHE_PATH = value;
}
exports.setSolcCachePath = setSolcCachePath;
function setSolcBinEnv(value) {
SOLC_BIN_ENV = value;
}
exports.setSolcBinEnv = setSolcBinEnv;
//# sourceMappingURL=CompilerProvider.js.map