aboutsummaryrefslogtreecommitdiffstats
path: root/packages/sol-compiler/src/compiler.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/sol-compiler/src/compiler.ts')
-rw-r--r--packages/sol-compiler/src/compiler.ts389
1 files changed, 0 insertions, 389 deletions
diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts
deleted file mode 100644
index afa4cc5bb..000000000
--- a/packages/sol-compiler/src/compiler.ts
+++ /dev/null
@@ -1,389 +0,0 @@
-import { assert } from '@0x/assert';
-import {
- FallthroughResolver,
- FSResolver,
- NameResolver,
- NPMResolver,
- RelativeFSResolver,
- Resolver,
- SpyResolver,
- URLResolver,
-} from '@0x/sol-resolver';
-import { logUtils } from '@0x/utils';
-import { execSync } from 'child_process';
-import * as chokidar from 'chokidar';
-import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types';
-import * as fs from 'fs';
-import * as _ from 'lodash';
-import * as path from 'path';
-import * as pluralize from 'pluralize';
-import * as semver from 'semver';
-import solc = require('solc');
-
-import { compilerOptionsSchema } from './schemas/compiler_options_schema';
-import {
- addHexPrefixToContractBytecode,
- compileDockerAsync,
- compileSolcJSAsync,
- createDirIfDoesNotExistAsync,
- getContractArtifactIfExistsAsync,
- getDependencyNameToPackagePath,
- getSolcJSReleasesAsync,
- getSourcesWithDependencies,
- getSourceTreeHash,
- makeContractPathsRelative,
- parseSolidityVersionRange,
- printCompilationErrorsAndWarnings,
-} from './utils/compiler';
-import { constants } from './utils/constants';
-import { fsWrapper } from './utils/fs_wrapper';
-import { utils } from './utils/utils';
-
-type TYPE_ALL_FILES_IDENTIFIER = '*';
-const ALL_CONTRACTS_IDENTIFIER = '*';
-const ALL_FILES_IDENTIFIER = '*';
-const DEFAULT_CONTRACTS_DIR = path.resolve('contracts');
-const DEFAULT_ARTIFACTS_DIR = path.resolve('artifacts');
-const DEFAULT_USE_DOCKERISED_SOLC = false;
-// 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.
-const DEFAULT_COMPILER_SETTINGS: solc.CompilerSettings = {
- optimizer: {
- enabled: false,
- },
- outputSelection: {
- [ALL_FILES_IDENTIFIER]: {
- [ALL_CONTRACTS_IDENTIFIER]: ['abi', 'evm.bytecode.object'],
- },
- },
-};
-const CONFIG_FILE = 'compiler.json';
-
-interface VersionToInputs {
- [solcVersion: string]: {
- standardInput: solc.StandardInput;
- contractsToCompile: string[];
- };
-}
-
-interface ContractPathToData {
- [contractPath: string]: ContractData;
-}
-
-interface ContractData {
- currentArtifactIfExists: ContractArtifact | void;
- sourceTreeHashHex: string;
- contractName: string;
-}
-
-/**
- * The Compiler facilitates compiling Solidity smart contracts and saves the results
- * to artifact files.
- */
-export class Compiler {
- private readonly _resolver: Resolver;
- private readonly _nameResolver: NameResolver;
- private readonly _contractsDir: string;
- private readonly _compilerSettings: solc.CompilerSettings;
- private readonly _artifactsDir: string;
- private readonly _solcVersionIfExists: string | undefined;
- private readonly _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
- private readonly _useDockerisedSolc: boolean;
- /**
- * Instantiates a new instance of the Compiler class.
- * @param opts Optional compiler options
- * @return An instance of the Compiler class.
- */
- constructor(opts?: CompilerOptions) {
- const passedOpts = opts || {};
- assert.doesConformToSchema('opts', passedOpts, compilerOptionsSchema);
- // TODO: Look for config file in parent directories if not found in current directory
- const config: CompilerOptions = fs.existsSync(CONFIG_FILE)
- ? JSON.parse(fs.readFileSync(CONFIG_FILE).toString())
- : {};
- assert.doesConformToSchema('compiler.json', config, compilerOptionsSchema);
- this._contractsDir = path.resolve(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._useDockerisedSolc =
- passedOpts.useDockerisedSolc || config.useDockerisedSolc || DEFAULT_USE_DOCKERISED_SOLC;
- this._nameResolver = new NameResolver(this._contractsDir);
- const resolver = new FallthroughResolver();
- resolver.appendResolver(new URLResolver());
- resolver.appendResolver(new NPMResolver(this._contractsDir));
- resolver.appendResolver(new RelativeFSResolver(this._contractsDir));
- resolver.appendResolver(new FSResolver());
- resolver.appendResolver(this._nameResolver);
- this._resolver = resolver;
- }
- /**
- * Compiles selected Solidity files found in `contractsDir` and writes JSON artifacts to `artifactsDir`.
- */
- public async compileAsync(): Promise<void> {
- await createDirIfDoesNotExistAsync(this._artifactsDir);
- await createDirIfDoesNotExistAsync(constants.SOLC_BIN_DIR);
- await this._compileContractsAsync(this._getContractNamesToCompile(), true);
- }
- /**
- * 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.
- */
- public async getCompilerOutputsAsync(): Promise<StandardOutput[]> {
- const promisedOutputs = this._compileContractsAsync(this._getContractNamesToCompile(), false);
- return promisedOutputs;
- }
- public async watchAsync(): Promise<void> {
- console.clear(); // tslint:disable-line:no-console
- logUtils.logWithTime('Starting compilation in watch mode...');
- const MATCH_NOTHING_REGEX = '^$';
- const IGNORE_DOT_FILES_REGEX = /(^|[\/\\])\../;
- // Initially we watch nothing. We'll add the paths later.
- const watcher = chokidar.watch(MATCH_NOTHING_REGEX, { ignored: IGNORE_DOT_FILES_REGEX });
- const onFileChangedAsync = async () => {
- watcher.unwatch('*'); // Stop watching
- try {
- await this.compileAsync();
- logUtils.logWithTime('Found 0 errors. Watching for file changes.');
- } catch (err) {
- if (err.typeName === 'CompilationError') {
- logUtils.logWithTime(
- `Found ${err.errorsCount} ${pluralize('error', err.errorsCount)}. Watching for file changes.`,
- );
- } else {
- logUtils.logWithTime('Found errors. Watching for file changes.');
- }
- }
-
- const pathsToWatch = this._getPathsToWatch();
- watcher.add(pathsToWatch);
- };
- await onFileChangedAsync();
- watcher.on('change', (changedFilePath: string) => {
- console.clear(); // tslint:disable-line:no-console
- 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
- });
- }
- private _getPathsToWatch(): string[] {
- const contractNames = this._getContractNamesToCompile();
- const spyResolver = new SpyResolver(this._resolver);
- for (const contractName of contractNames) {
- const 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.
- getSourceTreeHash(spyResolver, contractSource.path);
- }
- const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath));
- return pathsToWatch;
- }
- private _getContractNamesToCompile(): string[] {
- let contractNamesToCompile;
- if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) {
- const allContracts = this._nameResolver.getAll();
- contractNamesToCompile = _.map(allContracts, contractSource =>
- path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION),
- );
- } else {
- return this._specifiedContracts;
- }
- 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.
- */
- private async _compileContractsAsync(contractNames: string[], shouldPersist: boolean): Promise<StandardOutput[]> {
- // batch input contracts together based on the version of the compiler that they require.
- const versionToInputs: VersionToInputs = {};
-
- // map contract paths to data about them for later verification and persistence
- const contractPathToData: ContractPathToData = {};
-
- const solcJSReleases = await getSolcJSReleasesAsync();
- const resolvedContractSources = [];
- for (const contractName of contractNames) {
- const spyResolver = new SpyResolver(this._resolver);
- const contractSource = spyResolver.resolve(contractName);
- const sourceTreeHashHex = getSourceTreeHash(spyResolver, contractSource.path).toString('hex');
- const contractData = {
- contractName: path.basename(contractName, constants.SOLIDITY_FILE_EXTENSION),
- currentArtifactIfExists: await getContractArtifactIfExistsAsync(this._artifactsDir, contractName),
- sourceTreeHashHex: `0x${sourceTreeHashHex}`,
- };
- if (!this._shouldCompile(contractData)) {
- continue;
- }
- contractPathToData[contractSource.path] = contractData;
- const solcVersion = _.isUndefined(this._solcVersionIfExists)
- ? semver.maxSatisfying(_.keys(solcJSReleases), parseSolidityVersionRange(contractSource.source))
- : this._solcVersionIfExists;
- const isFirstContractWithThisVersion = _.isUndefined(versionToInputs[solcVersion]);
- if (isFirstContractWithThisVersion) {
- versionToInputs[solcVersion] = {
- standardInput: {
- language: 'Solidity',
- sources: {},
- settings: this._compilerSettings,
- },
- contractsToCompile: [],
- };
- }
- // add input to the right version batch
- for (const resolvedContractSource of spyResolver.resolvedContractSources) {
- versionToInputs[solcVersion].standardInput.sources[resolvedContractSource.absolutePath] = {
- content: resolvedContractSource.source,
- };
- }
- resolvedContractSources.push(...spyResolver.resolvedContractSources);
- versionToInputs[solcVersion].contractsToCompile.push(contractSource.path);
- }
-
- const dependencyNameToPath = getDependencyNameToPackagePath(resolvedContractSources);
-
- const compilerOutputs: StandardOutput[] = [];
- for (const solcVersion of _.keys(versionToInputs)) {
- const input = versionToInputs[solcVersion];
- logUtils.warn(
- `Compiling ${input.contractsToCompile.length} contracts (${
- input.contractsToCompile
- }) with Solidity v${solcVersion}...`,
- );
- let compilerOutput;
- let fullSolcVersion;
- input.standardInput.settings.remappings = _.map(
- dependencyNameToPath,
- (dependencyPackagePath: string, dependencyName: string) => `${dependencyName}=${dependencyPackagePath}`,
- );
- if (this._useDockerisedSolc) {
- const dockerCommand = `docker run ethereum/solc:${solcVersion} --version`;
- const versionCommandOutput = execSync(dockerCommand).toString();
- const versionCommandOutputParts = versionCommandOutput.split(' ');
- fullSolcVersion = versionCommandOutputParts[versionCommandOutputParts.length - 1].trim();
- compilerOutput = await compileDockerAsync(solcVersion, input.standardInput);
- } else {
- fullSolcVersion = solcJSReleases[solcVersion];
- compilerOutput = await compileSolcJSAsync(solcVersion, input.standardInput);
- }
- if (!_.isUndefined(compilerOutput.errors)) {
- printCompilationErrorsAndWarnings(compilerOutput.errors);
- }
- compilerOutput.sources = makeContractPathsRelative(
- compilerOutput.sources,
- this._contractsDir,
- dependencyNameToPath,
- );
- compilerOutput.contracts = makeContractPathsRelative(
- compilerOutput.contracts,
- this._contractsDir,
- dependencyNameToPath,
- );
-
- for (const contractPath of input.contractsToCompile) {
- const contractName = contractPathToData[contractPath].contractName;
-
- const 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`,
- );
- }
-
- addHexPrefixToContractBytecode(compiledContract);
-
- if (shouldPersist) {
- await this._persistCompiledContractAsync(
- contractPath,
- contractPathToData[contractPath].currentArtifactIfExists,
- contractPathToData[contractPath].sourceTreeHashHex,
- contractName,
- fullSolcVersion,
- compilerOutput,
- );
- }
- }
-
- compilerOutputs.push(compilerOutput);
- }
-
- return compilerOutputs;
- }
- private _shouldCompile(contractData: ContractData): boolean {
- if (_.isUndefined(contractData.currentArtifactIfExists)) {
- return true;
- } else {
- const currentArtifact = contractData.currentArtifactIfExists as ContractArtifact;
- const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
- const didCompilerSettingsChange = !_.isEqual(
- _.omit(currentArtifact.compiler.settings, 'remappings'),
- _.omit(this._compilerSettings, 'remappings'),
- );
- const didSourceChange = currentArtifact.sourceTreeHashHex !== contractData.sourceTreeHashHex;
- return !isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange;
- }
- }
- private async _persistCompiledContractAsync(
- contractPath: string,
- currentArtifactIfExists: ContractArtifact | void,
- sourceTreeHashHex: string,
- contractName: string,
- fullSolcVersion: string,
- compilerOutput: solc.StandardOutput,
- ): Promise<void> {
- const compiledContract = compilerOutput.contracts[contractPath][contractName];
-
- // need to gather sourceCodes for this artifact, but compilerOutput.sources (the list of contract modules)
- // contains listings for every contract compiled during the compiler invocation that compiled the contract
- // to be persisted, which could include many that are irrelevant to the contract at hand. So, gather up only
- // the relevant sources:
- const { sourceCodes, sources } = getSourcesWithDependencies(
- this._resolver,
- contractPath,
- compilerOutput.sources,
- );
-
- const contractVersion: ContractVersionData = {
- compilerOutput: compiledContract,
- sources,
- sourceCodes,
- sourceTreeHashHex,
- compiler: {
- name: 'solc',
- version: fullSolcVersion,
- settings: this._compilerSettings,
- },
- };
-
- let newArtifact: ContractArtifact;
- if (!_.isUndefined(currentArtifactIfExists)) {
- const currentArtifact = currentArtifactIfExists as ContractArtifact;
- newArtifact = {
- ...currentArtifact,
- ...contractVersion,
- };
- } else {
- newArtifact = {
- schemaVersion: constants.LATEST_ARTIFACT_VERSION,
- contractName,
- ...contractVersion,
- networks: {},
- };
- }
-
- const artifactString = utils.stringifyWithFormatting(newArtifact);
- const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
- await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
- logUtils.warn(`${contractName} artifact saved!`);
- }
-}