diff options
Diffstat (limited to 'packages/sol-compiler/src/compiler.ts')
-rw-r--r-- | packages/sol-compiler/src/compiler.ts | 389 |
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!`); - } -} |