From 8fd705d2afe6ddcb53a16973256a0674e180be67 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 15 Mar 2018 16:30:18 +0100 Subject: Throw an error if contract file doesn't contain the contract with the same name --- packages/deployer/src/compiler.ts | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'packages/deployer/src/compiler.ts') diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index 83977709b..206f333f6 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -228,6 +228,11 @@ export class Compiler { } const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION); const contractIdentifier = `${fileName}:${contractName}`; + if (_.isUndefined(compiled.contracts[contractIdentifier])) { + throw new Error( + `Contract ${contractName} not found in ${fileName}. Please make sure your contract has the same name as a file`, + ); + } const abi: Web3.ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface); const bytecode = `0x${compiled.contracts[contractIdentifier].bytecode}`; const runtimeBytecode = `0x${compiled.contracts[contractIdentifier].runtimeBytecode}`; -- cgit v1.2.3 From c4b4bb9e8e50af5a21091f4e8f54176ffb337739 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 15 Mar 2018 16:42:36 +0100 Subject: Compile contracts sequentially --- packages/deployer/src/compiler.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'packages/deployer/src/compiler.ts') diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index 206f333f6..4563752f6 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -162,7 +162,9 @@ export class Compiler { _.forEach(fileNames, fileName => { this._setSourceTreeHash(fileName); }); - await Promise.all(_.map(fileNames, async fileName => this._compileContractAsync(fileName))); + for (const fileName of fileNames) { + await this._compileContractAsync(fileName); + } this._solcErrors.forEach(errMsg => { logUtils.log(errMsg); }); -- cgit v1.2.3 From f45191d0e81e3ad3873e78c3891fdd5f9e9fd55c Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 15 Mar 2018 17:08:48 +0100 Subject: Support proper semver version ranges --- packages/deployer/src/compiler.ts | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) (limited to 'packages/deployer/src/compiler.ts') diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index 4563752f6..942777458 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -5,6 +5,7 @@ import 'isomorphic-fetch'; import * as _ from 'lodash'; import * as path from 'path'; import * as requireFromString from 'require-from-string'; +import * as semver from 'semver'; import solc = require('solc'); import * as Web3 from 'web3'; @@ -23,7 +24,7 @@ import { import { utils } from './utils/utils'; const ALL_CONTRACTS_IDENTIFIER = '*'; -const SOLIDITY_VERSION_REGEX = /(?:solidity\s\^?)(\d+\.\d+\.\d+)/; +const SOLIDITY_VERSION_RANGE_REGEX = /pragma solidity (.*);/; const SOLIDITY_FILE_EXTENSION_REGEX = /(.*\.sol)/; const IMPORT_REGEX = /(import\s)/; const DEPENDENCY_PATH_REGEX = /"([^"]+)"/; // Source: https://github.com/BlockChainCompany/soljitsu/blob/master/lib/shared.js @@ -85,10 +86,10 @@ export class Compiler { private static _getContractSpecificSourceData(source: string): ContractSpecificSourceData { const dependencies: string[] = []; const sourceHash = ethUtil.sha3(source); - const solcVersion = Compiler._parseSolidityVersion(source); + const solcVersionRange = Compiler._parseSolidityVersionRange(source); const contractSpecificSourceData: ContractSpecificSourceData = { dependencies, - solcVersion, + solcVersionRange, sourceHash, }; const lines = source.split('\n'); @@ -105,17 +106,17 @@ export class Compiler { return contractSpecificSourceData; } /** - * Searches Solidity source code for compiler version. + * Searches Solidity source code for compiler version range. * @param source Source code of contract. - * @return Solc compiler version. + * @return Solc compiler version range. */ - private static _parseSolidityVersion(source: string): string { - const solcVersionMatch = source.match(SOLIDITY_VERSION_REGEX); - if (_.isNull(solcVersionMatch)) { - throw new Error('Could not find Solidity version in source'); + private static _parseSolidityVersionRange(source: string): string { + const solcVersionRangeMatch = source.match(SOLIDITY_VERSION_RANGE_REGEX); + if (_.isNull(solcVersionRangeMatch)) { + throw new Error('Could not find Solidity version range in source'); } - const solcVersion = solcVersionMatch[1]; - return solcVersion; + const solcVersionRange = solcVersionRangeMatch[1]; + return solcVersionRange; } /** * Normalizes the path found in the error message. @@ -189,8 +190,12 @@ export class Compiler { if (!shouldCompile) { return; } - - const fullSolcVersion = binPaths[contractSpecificSourceData.solcVersion]; + const availableCompilerVersions = _.keys(binPaths); + const solcVersion = semver.maxSatisfying( + availableCompilerVersions, + contractSpecificSourceData.solcVersionRange, + ); + const fullSolcVersion = binPaths[solcVersion]; const compilerBinFilename = path.join(__dirname, '../../solc_bin', fullSolcVersion); let solcjs: string; const isCompilerAvailableLocally = fs.existsSync(compilerBinFilename); @@ -208,7 +213,7 @@ export class Compiler { } const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename)); - logUtils.log(`Compiling ${fileName}...`); + logUtils.log(`Compiling ${fileName} with Solidity v${solcVersion}...`); const source = this._contractSources[fileName]; const input = { [fileName]: source, @@ -243,7 +248,7 @@ export class Compiler { const sources = _.keys(compiled.sources); const updated_at = Date.now(); const contractNetworkData: ContractNetworkData = { - solc_version: contractSpecificSourceData.solcVersion, + solc_version: solcVersion, keccak256: sourceHash, source_tree_hash: sourceTreeHash, optimizer_enabled: this._optimizerEnabled, -- cgit v1.2.3 From 439e8640859cf790bac51c1beeec4cf65aa33c57 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 16 Mar 2018 11:09:12 +0100 Subject: Change the type of optimizerEnabled to boolean and convert it to number only before passing to a compiler --- packages/deployer/src/compiler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'packages/deployer/src/compiler.ts') diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index 942777458..28232a661 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -32,7 +32,7 @@ const DEPENDENCY_PATH_REGEX = /"([^"]+)"/; // Source: https://github.com/BlockCh export class Compiler { private _contractsDir: string; private _networkId: number; - private _optimizerEnabled: number; + private _optimizerEnabled: boolean; private _artifactsDir: string; private _contractSources?: ContractSources; private _solcErrors: Set = new Set(); @@ -223,7 +223,7 @@ export class Compiler { }; const compiled = solcInstance.compile( sourcesToCompile, - this._optimizerEnabled, + Number(this._optimizerEnabled), this._findImportsIfSourcesExist.bind(this), ); -- cgit v1.2.3 From 18b9fe525622d9e18491f76136d7697806996a09 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 16 Mar 2018 15:09:11 +0100 Subject: Enable strictNullChecks --- packages/deployer/src/compiler.ts | 210 +++++++++++--------------------------- 1 file changed, 59 insertions(+), 151 deletions(-) (limited to 'packages/deployer/src/compiler.ts') diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index 28232a661..d9571488e 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -10,6 +10,14 @@ import solc = require('solc'); import * as Web3 from 'web3'; import { binPaths } from './solc/bin_paths'; +import { + createArtifactsDirIfDoesNotExistAsync, + findImportIfExist, + getContractArtifactIfExistsAsync, + getNormalizedErrMsg, + parseDependencies, + parseSolidityVersionRange, +} from './utils/compiler'; import { constants } from './utils/constants'; import { fsWrapper } from './utils/fs_wrapper'; import { @@ -24,17 +32,13 @@ import { import { utils } from './utils/utils'; const ALL_CONTRACTS_IDENTIFIER = '*'; -const SOLIDITY_VERSION_RANGE_REGEX = /pragma solidity (.*);/; -const SOLIDITY_FILE_EXTENSION_REGEX = /(.*\.sol)/; -const IMPORT_REGEX = /(import\s)/; -const DEPENDENCY_PATH_REGEX = /"([^"]+)"/; // Source: https://github.com/BlockChainCompany/soljitsu/blob/master/lib/shared.js export class Compiler { private _contractsDir: string; private _networkId: number; private _optimizerEnabled: boolean; private _artifactsDir: string; - private _contractSources?: ContractSources; + private _contractSources!: ContractSources; private _solcErrors: Set = new Set(); private _specifiedContracts: Set = new Set(); private _contractSourceData: ContractSourceData = {}; @@ -78,64 +82,6 @@ export class Compiler { } return sources; } - /** - * Gets contract dependendencies and keccak256 hash from source. - * @param source Source code of contract. - * @return Object with contract dependencies and keccak256 hash of source. - */ - private static _getContractSpecificSourceData(source: string): ContractSpecificSourceData { - const dependencies: string[] = []; - const sourceHash = ethUtil.sha3(source); - const solcVersionRange = Compiler._parseSolidityVersionRange(source); - const contractSpecificSourceData: ContractSpecificSourceData = { - dependencies, - solcVersionRange, - sourceHash, - }; - const lines = source.split('\n'); - _.forEach(lines, line => { - if (!_.isNull(line.match(IMPORT_REGEX))) { - const dependencyMatch = line.match(DEPENDENCY_PATH_REGEX); - if (!_.isNull(dependencyMatch)) { - const dependencyPath = dependencyMatch[1]; - const fileName = path.basename(dependencyPath); - contractSpecificSourceData.dependencies.push(fileName); - } - } - }); - return contractSpecificSourceData; - } - /** - * Searches Solidity source code for compiler version range. - * @param source Source code of contract. - * @return Solc compiler version range. - */ - private static _parseSolidityVersionRange(source: string): string { - const solcVersionRangeMatch = source.match(SOLIDITY_VERSION_RANGE_REGEX); - if (_.isNull(solcVersionRangeMatch)) { - throw new Error('Could not find Solidity version range in source'); - } - const solcVersionRange = solcVersionRangeMatch[1]; - return solcVersionRange; - } - /** - * Normalizes the path found in the error message. - * Example: converts 'base/Token.sol:6:46: Warning: Unused local variable' - * to 'Token.sol:6:46: Warning: Unused local variable' - * This is used to prevent logging the same error multiple times. - * @param errMsg An error message from the compiled output. - * @return The error message with directories truncated from the contract path. - */ - private static _getNormalizedErrMsg(errMsg: string): string { - const errPathMatch = errMsg.match(SOLIDITY_FILE_EXTENSION_REGEX); - if (_.isNull(errPathMatch)) { - throw new Error('Could not find a path in error message'); - } - const errPath = errPathMatch[0]; - const baseContract = path.basename(errPath); - const normalizedErrMsg = errMsg.replace(errPath, baseContract); - return normalizedErrMsg; - } /** * Instantiates a new instance of the Compiler class. * @param opts Options specifying directories, network, and optimization settings. @@ -149,20 +95,15 @@ export class Compiler { this._specifiedContracts = opts.specifiedContracts; } /** - * Compiles all Solidity files found in contractsDir and writes JSON artifacts to artifactsDir. + * Compiles selected Solidity files and writes JSON artifacts to artifactsDir. */ - public async compileAllAsync(): Promise { - await this._createArtifactsDirIfDoesNotExistAsync(); + public async compileAsync(): Promise { + await createArtifactsDirIfDoesNotExistAsync(this._artifactsDir); this._contractSources = await Compiler._getContractSourcesAsync(this._contractsDir); - _.forIn(this._contractSources, (source, fileName) => { - this._contractSourceData[fileName] = Compiler._getContractSpecificSourceData(source); - }); + _.forIn(this._contractSources, this._setContractSpecificSourceData.bind(this)); const fileNames = this._specifiedContracts.has(ALL_CONTRACTS_IDENTIFIER) ? _.keys(this._contractSources) : Array.from(this._specifiedContracts.values()); - _.forEach(fileNames, fileName => { - this._setSourceTreeHash(fileName); - }); for (const fileName of fileNames) { await this._compileContractAsync(fileName); } @@ -179,14 +120,19 @@ export class Compiler { throw new Error('Contract sources not yet initialized'); } const contractSpecificSourceData = this._contractSourceData[fileName]; - const currentArtifactIfExists = (await this._getContractArtifactIfExistsAsync(fileName)) as ContractArtifact; + const currentArtifactIfExists = await getContractArtifactIfExistsAsync(this._artifactsDir, fileName); const sourceHash = `0x${contractSpecificSourceData.sourceHash.toString('hex')}`; - const sourceTreeHash = `0x${contractSpecificSourceData.sourceTreeHashIfExists.toString('hex')}`; + const sourceTreeHash = `0x${contractSpecificSourceData.sourceTreeHash.toString('hex')}`; - const shouldCompile = - _.isUndefined(currentArtifactIfExists) || - currentArtifactIfExists.networks[this._networkId].optimizer_enabled !== this._optimizerEnabled || - currentArtifactIfExists.networks[this._networkId].source_tree_hash !== sourceTreeHash; + let shouldCompile = false; + if (_.isUndefined(currentArtifactIfExists)) { + shouldCompile = true; + } else { + const currentArtifact = currentArtifactIfExists as ContractArtifact; + shouldCompile = + currentArtifact.networks[this._networkId].optimizer_enabled !== this._optimizerEnabled || + currentArtifact.networks[this._networkId].source_tree_hash !== sourceTreeHash; + } if (!shouldCompile) { return; } @@ -221,15 +167,13 @@ export class Compiler { const sourcesToCompile = { sources: input, }; - const compiled = solcInstance.compile( - sourcesToCompile, - Number(this._optimizerEnabled), - this._findImportsIfSourcesExist.bind(this), + const compiled = solcInstance.compile(sourcesToCompile, Number(this._optimizerEnabled), importPath => + findImportIfExist(this._contractSources, importPath), ); if (!_.isUndefined(compiled.errors)) { _.forEach(compiled.errors, errMsg => { - const normalizedErrMsg = Compiler._getNormalizedErrMsg(errMsg); + const normalizedErrMsg = getNormalizedErrMsg(errMsg); this._solcErrors.add(normalizedErrMsg); }); } @@ -263,10 +207,11 @@ export class Compiler { let newArtifact: ContractArtifact; if (!_.isUndefined(currentArtifactIfExists)) { + const currentArtifact = currentArtifactIfExists as ContractArtifact; newArtifact = { - ...currentArtifactIfExists, + ...currentArtifact, networks: { - ...currentArtifactIfExists.networks, + ...currentArtifact.networks, [this._networkId]: contractNetworkData, }, }; @@ -285,79 +230,42 @@ export class Compiler { logUtils.log(`${fileName} artifact saved!`); } /** - * Sets the source tree hash for a file and its dependencies. - * @param fileName Name of contract file. - */ - private _setSourceTreeHash(fileName: string): void { - const contractSpecificSourceData = this._contractSourceData[fileName]; - if (_.isUndefined(contractSpecificSourceData)) { - throw new Error(`Contract data for ${fileName} not yet set`); - } - if (_.isUndefined(contractSpecificSourceData.sourceTreeHashIfExists)) { - const dependencies = contractSpecificSourceData.dependencies; - if (dependencies.length === 0) { - contractSpecificSourceData.sourceTreeHashIfExists = contractSpecificSourceData.sourceHash; - } else { - _.forEach(dependencies, dependency => { - this._setSourceTreeHash(dependency); - }); - const dependencySourceTreeHashes = _.map( - dependencies, - dependency => this._contractSourceData[dependency].sourceTreeHashIfExists, - ); - const sourceTreeHashesBuffer = Buffer.concat([ - contractSpecificSourceData.sourceHash, - ...dependencySourceTreeHashes, - ]); - contractSpecificSourceData.sourceTreeHashIfExists = ethUtil.sha3(sourceTreeHashesBuffer); - } - } - } - /** - * Callback to resolve dependencies with `solc.compile`. - * Throws error if contractSources not yet initialized. - * @param importPath Path to an imported dependency. - * @return Import contents object containing source code of dependency. + * Gets contract dependendencies and keccak256 hash from source. + * @param source Source code of contract. + * @return Object with contract dependencies and keccak256 hash of source. */ - private _findImportsIfSourcesExist(importPath: string): solc.ImportContents { - const fileName = path.basename(importPath); - const source = this._contractSources[fileName]; - if (_.isUndefined(source)) { - throw new Error(`Contract source not found for ${fileName}`); + private _setContractSpecificSourceData(source: string, fileName: string): void { + if (!_.isUndefined(this._contractSourceData[fileName])) { + return; } - const importContents: solc.ImportContents = { - contents: source, + const sourceHash = ethUtil.sha3(source); + const solcVersionRange = parseSolidityVersionRange(source); + const dependencies = parseDependencies(source); + const sourceTreeHash = this._getSourceTreeHash(fileName, sourceHash, dependencies); + this._contractSourceData[fileName] = { + dependencies, + solcVersionRange, + sourceHash, + sourceTreeHash, }; - return importContents; - } - /** - * Creates the artifacts directory if it does not already exist. - */ - private async _createArtifactsDirIfDoesNotExistAsync(): Promise { - if (!fsWrapper.doesPathExistSync(this._artifactsDir)) { - logUtils.log('Creating artifacts directory...'); - await fsWrapper.mkdirAsync(this._artifactsDir); - } } /** - * Gets contract data on network or returns if an artifact does not exist. + * Gets the source tree hash for a file and its dependencies. * @param fileName Name of contract file. - * @return Contract data on network or undefined. */ - private async _getContractArtifactIfExistsAsync(fileName: string): Promise { - let contractArtifact; - const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION); - const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`; - try { - const opts = { - encoding: 'utf8', - }; - const contractArtifactString = await fsWrapper.readFileAsync(currentArtifactPath, opts); - contractArtifact = JSON.parse(contractArtifactString); - return contractArtifact; - } catch (err) { - logUtils.log(`Artifact for ${fileName} does not exist`); - return undefined; + private _getSourceTreeHash(fileName: string, sourceHash: Buffer, dependencies: string[]): Buffer { + if (dependencies.length === 0) { + return sourceHash; + } else { + const dependencySourceTreeHashes = _.map(dependencies, dependency => { + const source = this._contractSources[dependency]; + this._setContractSpecificSourceData(source, dependency); + const sourceData = this._contractSourceData[dependency]; + return this._getSourceTreeHash(dependency, sourceData.sourceHash, sourceData.dependencies); + }); + const sourceTreeHashesBuffer = Buffer.concat([sourceHash, ...dependencySourceTreeHashes]); + const sourceTreeHash = ethUtil.sha3(sourceTreeHashesBuffer); + return sourceTreeHash; } } } -- cgit v1.2.3 From 32b85625c10279e53d75488fe81e3723afce0a1b Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 21 Mar 2018 17:23:07 +0100 Subject: Add a comment --- packages/deployer/src/compiler.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/deployer/src/compiler.ts') diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index d9571488e..fcff9e22f 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -38,6 +38,7 @@ export class Compiler { private _networkId: number; private _optimizerEnabled: boolean; private _artifactsDir: string; + // This get's set in the beggining of `compileAsync` function. It's not called from a constructor, but it's the only public method of that class and could as well be. private _contractSources!: ContractSources; private _solcErrors: Set = new Set(); private _specifiedContracts: Set = new Set(); -- cgit v1.2.3 From 73f8ae9a47b151d518d759d0d7cf79f6bea794d3 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 21 Mar 2018 18:01:26 +0100 Subject: Fix a comment --- packages/deployer/src/compiler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'packages/deployer/src/compiler.ts') diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts index fcff9e22f..712169c89 100644 --- a/packages/deployer/src/compiler.ts +++ b/packages/deployer/src/compiler.ts @@ -182,7 +182,7 @@ export class Compiler { const contractIdentifier = `${fileName}:${contractName}`; if (_.isUndefined(compiled.contracts[contractIdentifier])) { throw new Error( - `Contract ${contractName} not found in ${fileName}. Please make sure your contract has the same name as a file`, + `Contract ${contractName} not found in ${fileName}. Please make sure your contract has the same name as it's file name`, ); } const abi: Web3.ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface); -- cgit v1.2.3