UNPKG

@0x/sol-compiler

Version:
452 lines 25.5 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __values = (this && this.__values) || function (o) { var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; if (m) return m.call(o); return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; }; Object.defineProperty(exports, "__esModule", { value: true }); var _a, _b; var assert_1 = require("@0x/assert"); var sol_resolver_1 = require("@0x/sol-resolver"); var utils_1 = require("@0x/utils"); var chokidar = require("chokidar"); var fs = require("fs"); var _ = require("lodash"); var path = require("path"); var pluralize = require("pluralize"); var semver = require("semver"); var compiler_options_schema_1 = require("./schemas/compiler_options_schema"); var bin_paths_1 = require("./solc/bin_paths"); var compiler_1 = require("./utils/compiler"); var constants_1 = require("./utils/constants"); var fs_wrapper_1 = require("./utils/fs_wrapper"); var utils_2 = require("./utils/utils"); var ALL_CONTRACTS_IDENTIFIER = '*'; var ALL_FILES_IDENTIFIER = '*'; var DEFAULT_CONTRACTS_DIR = path.resolve('contracts'); var DEFAULT_ARTIFACTS_DIR = path.resolve('artifacts'); // Solc compiler settings cannot be configured from the commandline. // If you need this configured, please create a `compiler.json` config file // with your desired configurations. var DEFAULT_COMPILER_SETTINGS = { optimizer: { enabled: false, }, outputSelection: (_a = {}, _a[ALL_FILES_IDENTIFIER] = (_b = {}, _b[ALL_CONTRACTS_IDENTIFIER] = ['abi', 'evm.bytecode.object'], _b), _a), }; var CONFIG_FILE = 'compiler.json'; /** * The Compiler facilitates compiling Solidity smart contracts and saves the results * to artifact files. */ var Compiler = /** @class */ (function () { /** * Instantiates a new instance of the Compiler class. * @param opts Optional compiler options * @return An instance of the Compiler class. */ function Compiler(opts) { assert_1.assert.doesConformToSchema('opts', opts, compiler_options_schema_1.compilerOptionsSchema); // TODO: Look for config file in parent directories if not found in current directory var config = fs.existsSync(CONFIG_FILE) ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString()) : {}; var passedOpts = opts || {}; assert_1.assert.doesConformToSchema('compiler.json', config, compiler_options_schema_1.compilerOptionsSchema); this._contractsDir = passedOpts.contractsDir || config.contractsDir || DEFAULT_CONTRACTS_DIR; this._solcVersionIfExists = passedOpts.solcVersion || config.solcVersion; this._compilerSettings = passedOpts.compilerSettings || config.compilerSettings || DEFAULT_COMPILER_SETTINGS; this._artifactsDir = passedOpts.artifactsDir || config.artifactsDir || DEFAULT_ARTIFACTS_DIR; this._specifiedContracts = passedOpts.contracts || config.contracts || ALL_CONTRACTS_IDENTIFIER; this._nameResolver = new sol_resolver_1.NameResolver(path.resolve(this._contractsDir)); var resolver = new sol_resolver_1.FallthroughResolver(); resolver.appendResolver(new sol_resolver_1.URLResolver()); var packagePath = path.resolve(''); resolver.appendResolver(new sol_resolver_1.NPMResolver(packagePath)); resolver.appendResolver(new sol_resolver_1.RelativeFSResolver(this._contractsDir)); resolver.appendResolver(new sol_resolver_1.FSResolver()); resolver.appendResolver(this._nameResolver); this._resolver = resolver; } /** * Compiles selected Solidity files found in `contractsDir` and writes JSON artifacts to `artifactsDir`. */ Compiler.prototype.compileAsync = function () { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, compiler_1.createDirIfDoesNotExistAsync(this._artifactsDir)]; case 1: _a.sent(); return [4 /*yield*/, compiler_1.createDirIfDoesNotExistAsync(constants_1.constants.SOLC_BIN_DIR)]; case 2: _a.sent(); return [4 /*yield*/, this._compileContractsAsync(this._getContractNamesToCompile(), true)]; case 3: _a.sent(); return [2 /*return*/]; } }); }); }; /** * Compiles Solidity files specified during instantiation, and returns the * compiler output given by solc. Return value is an array of outputs: * Solidity modules are batched together by version required, and each * element of the returned array corresponds to a compiler version, and * each element contains the output for all of the modules compiled with * that version. */ Compiler.prototype.getCompilerOutputsAsync = function () { return __awaiter(this, void 0, void 0, function () { var promisedOutputs; return __generator(this, function (_a) { promisedOutputs = this._compileContractsAsync(this._getContractNamesToCompile(), false); return [2 /*return*/, promisedOutputs]; }); }); }; Compiler.prototype.watchAsync = function () { return __awaiter(this, void 0, void 0, function () { var MATCH_NOTHING_REGEX, IGNORE_DOT_FILES_REGEX, watcher, onFileChangedAsync; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: console.clear(); // tslint:disable-line:no-console utils_1.logUtils.logWithTime('Starting compilation in watch mode...'); MATCH_NOTHING_REGEX = '^$'; IGNORE_DOT_FILES_REGEX = /(^|[\/\\])\../; watcher = chokidar.watch(MATCH_NOTHING_REGEX, { ignored: IGNORE_DOT_FILES_REGEX }); onFileChangedAsync = function () { return __awaiter(_this, void 0, void 0, function () { var err_1, pathsToWatch; return __generator(this, function (_a) { switch (_a.label) { case 0: watcher.unwatch('*'); // Stop watching _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.compileAsync()]; case 2: _a.sent(); utils_1.logUtils.logWithTime('Found 0 errors. Watching for file changes.'); return [3 /*break*/, 4]; case 3: err_1 = _a.sent(); if (err_1.typeName === 'CompilationError') { utils_1.logUtils.logWithTime("Found " + err_1.errorsCount + " " + pluralize('error', err_1.errorsCount) + ". Watching for file changes."); } else { utils_1.logUtils.logWithTime('Found errors. Watching for file changes.'); } return [3 /*break*/, 4]; case 4: pathsToWatch = this._getPathsToWatch(); watcher.add(pathsToWatch); return [2 /*return*/]; } }); }); }; return [4 /*yield*/, onFileChangedAsync()]; case 1: _a.sent(); watcher.on('change', function (changedFilePath) { console.clear(); // tslint:disable-line:no-console utils_1.logUtils.logWithTime('File change detected. Starting incremental compilation...'); // NOTE: We can't await it here because that's a callback. // Instead we stop watching inside of it and start it again when we're finished. onFileChangedAsync(); // tslint:disable-line no-floating-promises }); return [2 /*return*/]; } }); }); }; Compiler.prototype._getPathsToWatch = function () { var e_1, _a; var contractNames = this._getContractNamesToCompile(); var spyResolver = new sol_resolver_1.SpyResolver(this._resolver); try { for (var contractNames_1 = __values(contractNames), contractNames_1_1 = contractNames_1.next(); !contractNames_1_1.done; contractNames_1_1 = contractNames_1.next()) { var contractName = contractNames_1_1.value; var contractSource = spyResolver.resolve(contractName); // NOTE: We ignore the return value here. We don't want to compute the source tree hash. // We just want to call a SpyResolver on each contracts and it's dependencies and // this is a convenient way to reuse the existing code that does that. // We can then get all the relevant paths from the `spyResolver` below. compiler_1.getSourceTreeHash(spyResolver, contractSource.path); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (contractNames_1_1 && !contractNames_1_1.done && (_a = contractNames_1.return)) _a.call(contractNames_1); } finally { if (e_1) throw e_1.error; } } var pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(function (cs) { return cs.absolutePath; })); return pathsToWatch; }; Compiler.prototype._getContractNamesToCompile = function () { var contractNamesToCompile; if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) { var allContracts = this._nameResolver.getAll(); contractNamesToCompile = _.map(allContracts, function (contractSource) { return path.basename(contractSource.path, constants_1.constants.SOLIDITY_FILE_EXTENSION); }); } else { contractNamesToCompile = this._specifiedContracts.map(function (specifiedContract) { return path.basename(specifiedContract, constants_1.constants.SOLIDITY_FILE_EXTENSION); }); } return contractNamesToCompile; }; /** * Compiles contracts, and, if `shouldPersist` is true, saves artifacts to artifactsDir. * @param fileName Name of contract with '.sol' extension. * @return an array of compiler outputs, where each element corresponds to a different version of solc-js. */ Compiler.prototype._compileContractsAsync = function (contractNames, shouldPersist) { return __awaiter(this, void 0, void 0, function () { var e_2, _a, e_3, _b, e_4, _c, versionToInputs, contractPathToData, contractNames_2, contractNames_2_1, contractName, contractSource, sourceTreeHashHex, contractData, _d, solcVersion, isFirstContractWithThisVersion, e_2_1, compilerOutputs, solcVersions, solcVersions_1, solcVersions_1_1, solcVersion, input, _e, solcInstance, fullSolcVersion, compilerOutput, _f, _g, contractPath, contractName, compiledContract, e_4_1, e_3_1; return __generator(this, function (_h) { switch (_h.label) { case 0: versionToInputs = {}; contractPathToData = {}; _h.label = 1; case 1: _h.trys.push([1, 6, 7, 8]); contractNames_2 = __values(contractNames), contractNames_2_1 = contractNames_2.next(); _h.label = 2; case 2: if (!!contractNames_2_1.done) return [3 /*break*/, 5]; contractName = contractNames_2_1.value; contractSource = this._resolver.resolve(contractName); sourceTreeHashHex = compiler_1.getSourceTreeHash(this._resolver, path.join(this._contractsDir, contractSource.path)).toString('hex'); _d = { contractName: contractName }; return [4 /*yield*/, compiler_1.getContractArtifactIfExistsAsync(this._artifactsDir, contractName)]; case 3: contractData = (_d.currentArtifactIfExists = _h.sent(), _d.sourceTreeHashHex = "0x" + sourceTreeHashHex, _d); if (!this._shouldCompile(contractData)) { return [3 /*break*/, 4]; } contractPathToData[contractSource.path] = contractData; solcVersion = _.isUndefined(this._solcVersionIfExists) ? semver.maxSatisfying(_.keys(bin_paths_1.binPaths), compiler_1.parseSolidityVersionRange(contractSource.source)) : this._solcVersionIfExists; isFirstContractWithThisVersion = _.isUndefined(versionToInputs[solcVersion]); if (isFirstContractWithThisVersion) { versionToInputs[solcVersion] = { standardInput: { language: 'Solidity', sources: {}, settings: this._compilerSettings, }, contractsToCompile: [], }; } // add input to the right version batch versionToInputs[solcVersion].standardInput.sources[contractSource.path] = { content: contractSource.source, }; versionToInputs[solcVersion].contractsToCompile.push(contractSource.path); _h.label = 4; case 4: contractNames_2_1 = contractNames_2.next(); return [3 /*break*/, 2]; case 5: return [3 /*break*/, 8]; case 6: e_2_1 = _h.sent(); e_2 = { error: e_2_1 }; return [3 /*break*/, 8]; case 7: try { if (contractNames_2_1 && !contractNames_2_1.done && (_a = contractNames_2.return)) _a.call(contractNames_2); } finally { if (e_2) throw e_2.error; } return [7 /*endfinally*/]; case 8: compilerOutputs = []; solcVersions = _.keys(versionToInputs); _h.label = 9; case 9: _h.trys.push([9, 21, 22, 23]); solcVersions_1 = __values(solcVersions), solcVersions_1_1 = solcVersions_1.next(); _h.label = 10; case 10: if (!!solcVersions_1_1.done) return [3 /*break*/, 20]; solcVersion = solcVersions_1_1.value; input = versionToInputs[solcVersion]; utils_1.logUtils.warn("Compiling " + input.contractsToCompile.length + " contracts (" + input.contractsToCompile + ") with Solidity v" + solcVersion + "..."); return [4 /*yield*/, compiler_1.getSolcAsync(solcVersion)]; case 11: _e = _h.sent(), solcInstance = _e.solcInstance, fullSolcVersion = _e.fullSolcVersion; compilerOutput = compiler_1.compile(this._resolver, solcInstance, input.standardInput); compilerOutputs.push(compilerOutput); _h.label = 12; case 12: _h.trys.push([12, 17, 18, 19]); _f = __values(input.contractsToCompile), _g = _f.next(); _h.label = 13; case 13: if (!!_g.done) return [3 /*break*/, 16]; contractPath = _g.value; contractName = contractPathToData[contractPath].contractName; compiledContract = compilerOutput.contracts[contractPath][contractName]; if (_.isUndefined(compiledContract)) { throw new Error("Contract " + contractName + " not found in " + contractPath + ". Please make sure your contract has the same name as it's file name"); } compiler_1.addHexPrefixToContractBytecode(compiledContract); if (!shouldPersist) return [3 /*break*/, 15]; return [4 /*yield*/, this._persistCompiledContractAsync(contractPath, contractPathToData[contractPath].currentArtifactIfExists, contractPathToData[contractPath].sourceTreeHashHex, contractName, fullSolcVersion, compilerOutput)]; case 14: _h.sent(); _h.label = 15; case 15: _g = _f.next(); return [3 /*break*/, 13]; case 16: return [3 /*break*/, 19]; case 17: e_4_1 = _h.sent(); e_4 = { error: e_4_1 }; return [3 /*break*/, 19]; case 18: try { if (_g && !_g.done && (_c = _f.return)) _c.call(_f); } finally { if (e_4) throw e_4.error; } return [7 /*endfinally*/]; case 19: solcVersions_1_1 = solcVersions_1.next(); return [3 /*break*/, 10]; case 20: return [3 /*break*/, 23]; case 21: e_3_1 = _h.sent(); e_3 = { error: e_3_1 }; return [3 /*break*/, 23]; case 22: try { if (solcVersions_1_1 && !solcVersions_1_1.done && (_b = solcVersions_1.return)) _b.call(solcVersions_1); } finally { if (e_3) throw e_3.error; } return [7 /*endfinally*/]; case 23: return [2 /*return*/, compilerOutputs]; } }); }); }; Compiler.prototype._shouldCompile = function (contractData) { if (_.isUndefined(contractData.currentArtifactIfExists)) { return true; } else { var currentArtifact = contractData.currentArtifactIfExists; var isUserOnLatestVersion = currentArtifact.schemaVersion === constants_1.constants.LATEST_ARTIFACT_VERSION; var didCompilerSettingsChange = !_.isEqual(currentArtifact.compiler.settings, this._compilerSettings); var didSourceChange = currentArtifact.sourceTreeHashHex !== contractData.sourceTreeHashHex; return !isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange; } }; Compiler.prototype._persistCompiledContractAsync = function (contractPath, currentArtifactIfExists, sourceTreeHashHex, contractName, fullSolcVersion, compilerOutput) { return __awaiter(this, void 0, void 0, function () { var compiledContract, _a, sourceCodes, sources, contractVersion, newArtifact, currentArtifact, artifactString, currentArtifactPath; return __generator(this, function (_b) { switch (_b.label) { case 0: compiledContract = compilerOutput.contracts[contractPath][contractName]; _a = compiler_1.getSourcesWithDependencies(this._resolver, contractPath, compilerOutput.sources), sourceCodes = _a.sourceCodes, sources = _a.sources; contractVersion = { compilerOutput: compiledContract, sources: sources, sourceCodes: sourceCodes, sourceTreeHashHex: sourceTreeHashHex, compiler: { name: 'solc', version: fullSolcVersion, settings: this._compilerSettings, }, }; if (!_.isUndefined(currentArtifactIfExists)) { currentArtifact = currentArtifactIfExists; newArtifact = __assign({}, currentArtifact, contractVersion); } else { newArtifact = __assign({ schemaVersion: constants_1.constants.LATEST_ARTIFACT_VERSION, contractName: contractName }, contractVersion, { networks: {} }); } artifactString = utils_2.utils.stringifyWithFormatting(newArtifact); currentArtifactPath = this._artifactsDir + "/" + contractName + ".json"; return [4 /*yield*/, fs_wrapper_1.fsWrapper.writeFileAsync(currentArtifactPath, artifactString)]; case 1: _b.sent(); utils_1.logUtils.warn(contractName + " artifact saved!"); return [2 /*return*/]; } }); }); }; return Compiler; }()); exports.Compiler = Compiler; //# sourceMappingURL=compiler.js.map