From 02ede26893d5801c827f68d4ffb24c216b873a09 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 15 Mar 2018 16:22:42 +0100 Subject: Rename onDeployCommand to onDeployCommandAsync and onCompileCommand to onCompileCommandAsync --- packages/deployer/src/cli.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts index 7913c6344..b131a96d8 100644 --- a/packages/deployer/src/cli.ts +++ b/packages/deployer/src/cli.ts @@ -24,7 +24,7 @@ const DEFAULT_CONTRACTS_LIST = '*'; * Compiles all contracts with options passed in through CLI. * @param argv Instance of process.argv provided by yargs. */ -async function onCompileCommand(argv: CliOptions): Promise { +async function onCompileCommandAsync(argv: CliOptions): Promise { const opts: CompilerOptions = { contractsDir: argv.contractsDir, networkId: argv.networkId, @@ -38,7 +38,7 @@ async function onCompileCommand(argv: CliOptions): Promise { * Deploys a single contract with provided name and args. * @param argv Instance of process.argv provided by yargs. */ -async function onDeployCommand(argv: CliOptions): Promise { +async function onDeployCommandAsync(argv: CliOptions): Promise { const url = argv.jsonrpcUrl; const web3Provider = new Web3.providers.HttpProvider(url); const web3Wrapper = new Web3Wrapper(web3Provider); @@ -142,7 +142,12 @@ function deployCommandBuilder(yargsInstance: any) { default: DEFAULT_CONTRACTS_LIST, description: 'comma separated list of contracts to compile', }) - .command('compile', 'compile contracts', identityCommandBuilder, onCompileCommand) - .command('deploy', 'deploy a single contract with provided arguments', deployCommandBuilder, onDeployCommand) + .command('compile', 'compile contracts', identityCommandBuilder, onCompileCommandAsync) + .command( + 'deploy', + 'deploy a single contract with provided arguments', + deployCommandBuilder, + onDeployCommandAsync, + ) .help().argv; })(); -- cgit v1.2.3 From 3c36135d6c9c4b4deb1985bd71ba6f4002d16d0e Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 15 Mar 2018 16:29:55 +0100 Subject: Stop printing help on error --- packages/deployer/src/cli.ts | 5 +++-- packages/deployer/src/utils/error_reporter.ts | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 packages/deployer/src/utils/error_reporter.ts (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts index b131a96d8..176eaf973 100644 --- a/packages/deployer/src/cli.ts +++ b/packages/deployer/src/cli.ts @@ -10,6 +10,7 @@ import * as yargs from 'yargs'; import { commands } from './commands'; import { constants } from './utils/constants'; +import { consoleReporter } from './utils/error_reporter'; import { CliOptions, CompilerOptions, DeployerOptions } from './utils/types'; const DEFAULT_OPTIMIZER_ENABLED = false; @@ -142,12 +143,12 @@ function deployCommandBuilder(yargsInstance: any) { default: DEFAULT_CONTRACTS_LIST, description: 'comma separated list of contracts to compile', }) - .command('compile', 'compile contracts', identityCommandBuilder, onCompileCommandAsync) + .command('compile', 'compile contracts', identityCommandBuilder, consoleReporter(onCompileCommandAsync)) .command( 'deploy', 'deploy a single contract with provided arguments', deployCommandBuilder, - onDeployCommandAsync, + consoleReporter(onDeployCommandAsync), ) .help().argv; })(); diff --git a/packages/deployer/src/utils/error_reporter.ts b/packages/deployer/src/utils/error_reporter.ts new file mode 100644 index 000000000..b28e4fbbe --- /dev/null +++ b/packages/deployer/src/utils/error_reporter.ts @@ -0,0 +1,13 @@ +import { logUtils } from '@0xproject/utils'; + +export function consoleReporter(asyncFn: (arg: T) => Promise) { + const noThrowFnAsync = async (arg: T) => { + try { + const result = await asyncFn(arg); + return result; + } catch (err) { + logUtils.log(`${err}`); + } + }; + return noThrowFnAsync; +} -- cgit v1.2.3 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') 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') 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 ++++++++++++++++++++--------------- packages/deployer/src/utils/types.ts | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) (limited to 'packages/deployer/src') 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, diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts index 0068faf6a..f33f8f66a 100644 --- a/packages/deployer/src/utils/types.ts +++ b/packages/deployer/src/utils/types.ts @@ -84,7 +84,7 @@ export interface ContractSourceData { export interface ContractSpecificSourceData { dependencies: string[]; - solcVersion: string; + solcVersionRange: string; sourceHash: Buffer; sourceTreeHashIfExists?: Buffer; } -- 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/cli.ts | 4 ++-- packages/deployer/src/compiler.ts | 4 ++-- packages/deployer/src/utils/types.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts index 176eaf973..efbf56bb0 100644 --- a/packages/deployer/src/cli.ts +++ b/packages/deployer/src/cli.ts @@ -29,7 +29,7 @@ async function onCompileCommandAsync(argv: CliOptions): Promise { const opts: CompilerOptions = { contractsDir: argv.contractsDir, networkId: argv.networkId, - optimizerEnabled: argv.shouldOptimize ? 1 : 0, + optimizerEnabled: argv.shouldOptimize, artifactsDir: argv.artifactsDir, specifiedContracts: getContractsSetFromList(argv.contracts), }; @@ -47,7 +47,7 @@ async function onDeployCommandAsync(argv: CliOptions): Promise { const compilerOpts: CompilerOptions = { contractsDir: argv.contractsDir, networkId, - optimizerEnabled: argv.shouldOptimize ? 1 : 0, + optimizerEnabled: argv.shouldOptimize, artifactsDir: argv.artifactsDir, specifiedContracts: getContractsSetFromList(argv.contracts), }; 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), ); diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts index f33f8f66a..13b24b9b7 100644 --- a/packages/deployer/src/utils/types.ts +++ b/packages/deployer/src/utils/types.ts @@ -20,7 +20,7 @@ export interface ContractNetworks { export interface ContractNetworkData { solc_version: string; - optimizer_enabled: number; + optimizer_enabled: boolean; keccak256: string; source_tree_hash: string; abi: Web3.ContractAbi; @@ -53,7 +53,7 @@ export interface CliOptions extends yargs.Arguments { export interface CompilerOptions { contractsDir: string; networkId: number; - optimizerEnabled: number; + optimizerEnabled: boolean; artifactsDir: string; specifiedContracts: Set; } -- 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/cli.ts | 4 +- packages/deployer/src/commands.ts | 2 +- packages/deployer/src/compiler.ts | 210 +++++++++----------------------- packages/deployer/src/utils/compiler.ts | 118 ++++++++++++++++++ packages/deployer/src/utils/types.ts | 2 +- 5 files changed, 181 insertions(+), 155 deletions(-) create mode 100644 packages/deployer/src/utils/compiler.ts (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts index efbf56bb0..d1bd645b3 100644 --- a/packages/deployer/src/cli.ts +++ b/packages/deployer/src/cli.ts @@ -63,9 +63,9 @@ async function onDeployCommandAsync(argv: CliOptions): Promise { networkId, defaults, }; - const deployerArgsString = argv.args; + const deployerArgsString = argv.args as string; const deployerArgs = deployerArgsString.split(','); - await commands.deployAsync(argv.contract, deployerArgs, deployerOpts); + await commands.deployAsync(argv.contract as string, deployerArgs, deployerOpts); } /** * Creates a set of contracts to compile. diff --git a/packages/deployer/src/commands.ts b/packages/deployer/src/commands.ts index 32af7fc3f..8e544a60b 100644 --- a/packages/deployer/src/commands.ts +++ b/packages/deployer/src/commands.ts @@ -5,7 +5,7 @@ import { CompilerOptions, DeployerOptions } from './utils/types'; export const commands = { async compileAsync(opts: CompilerOptions): Promise { const compiler = new Compiler(opts); - await compiler.compileAllAsync(); + await compiler.compileAsync(); }, async deployAsync(contractName: string, args: any[], opts: DeployerOptions): Promise { const deployer = new Deployer(opts); 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; } } } diff --git a/packages/deployer/src/utils/compiler.ts b/packages/deployer/src/utils/compiler.ts new file mode 100644 index 000000000..3fb35e9ed --- /dev/null +++ b/packages/deployer/src/utils/compiler.ts @@ -0,0 +1,118 @@ +import { logUtils } from '@0xproject/utils'; +import * as _ from 'lodash'; +import * as path from 'path'; +import * as solc from 'solc'; + +import { constants } from './constants'; +import { fsWrapper } from './fs_wrapper'; +import { ContractArtifact, ContractSources } from './types'; + +/** + * Gets contract data on network or returns if an artifact does not exist. + * @param fileName Name of contract file. + * @return Contract data on network or undefined. + */ +export async function getContractArtifactIfExistsAsync( + artifactsDir: string, + fileName: string, +): Promise { + let contractArtifact; + const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION); + const currentArtifactPath = `${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; + } +} + +/** + * Creates the artifacts directory if it does not already exist. + */ +export async function createArtifactsDirIfDoesNotExistAsync(artifactsDir: string): Promise { + if (!fsWrapper.doesPathExistSync(artifactsDir)) { + logUtils.log('Creating artifacts directory...'); + await fsWrapper.mkdirAsync(artifactsDir); + } +} + +/** + * Searches Solidity source code for compiler version range. + * @param source Source code of contract. + * @return Solc compiler version range. + */ +export function parseSolidityVersionRange(source: string): string { + const SOLIDITY_VERSION_RANGE_REGEX = /pragma solidity (.*);/; + 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. + */ +export function getNormalizedErrMsg(errMsg: string): string { + const SOLIDITY_FILE_EXTENSION_REGEX = /(.*\.sol)/; + 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; +} + +export function parseDependencies(source: string): string[] { + // TODO: Use a proper parser + const IMPORT_REGEX = /(import\s)/; + const DEPENDENCY_PATH_REGEX = /"([^"]+)"/; // Source: https://github.com/BlockChainCompany/soljitsu/blob/master/lib/shared.js + const dependencies: string[] = []; + 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 basenName = path.basename(dependencyPath); + dependencies.push(basenName); + } + } + }); + return dependencies; +} + +/** + * 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. + */ +export function findImportIfExist(contractSources: ContractSources, importPath: string): solc.ImportContents { + const fileName = path.basename(importPath); + if (_.isUndefined(contractSources)) { + throw new Error('Contract sources not yet initialized'); + } + const source = contractSources[fileName]; + if (_.isUndefined(source)) { + throw new Error(`Contract source not found for ${fileName}`); + } + const importContents: solc.ImportContents = { + contents: source, + }; + return importContents; +} diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts index 13b24b9b7..72ee0df2f 100644 --- a/packages/deployer/src/utils/types.ts +++ b/packages/deployer/src/utils/types.ts @@ -86,7 +86,7 @@ export interface ContractSpecificSourceData { dependencies: string[]; solcVersionRange: string; sourceHash: Buffer; - sourceTreeHashIfExists?: Buffer; + sourceTreeHash: Buffer; } // TODO: Consolidate with 0x.js definitions once types are moved into a separate package. -- cgit v1.2.3 From 8b52793f2fa21bcb23a3c38b6d3773cfc20186fd Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 16 Mar 2018 16:34:02 +0100 Subject: Add tests for compiler utils --- packages/deployer/src/globals.d.ts | 2 ++ packages/deployer/src/utils/compiler.ts | 5 +---- packages/deployer/src/utils/fs_wrapper.ts | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/globals.d.ts b/packages/deployer/src/globals.d.ts index 3cff8a909..83db346f9 100644 --- a/packages/deployer/src/globals.d.ts +++ b/packages/deployer/src/globals.d.ts @@ -1,3 +1,5 @@ +declare module 'dirty-chai'; + // tslint:disable:completed-docs declare module 'solc' { import * as Web3 from 'web3'; diff --git a/packages/deployer/src/utils/compiler.ts b/packages/deployer/src/utils/compiler.ts index 3fb35e9ed..1d21d7d31 100644 --- a/packages/deployer/src/utils/compiler.ts +++ b/packages/deployer/src/utils/compiler.ts @@ -48,7 +48,7 @@ export async function createArtifactsDirIfDoesNotExistAsync(artifactsDir: string * @return Solc compiler version range. */ export function parseSolidityVersionRange(source: string): string { - const SOLIDITY_VERSION_RANGE_REGEX = /pragma solidity (.*);/; + const SOLIDITY_VERSION_RANGE_REGEX = /pragma\s+solidity\s+(.*);/; const solcVersionRangeMatch = source.match(SOLIDITY_VERSION_RANGE_REGEX); if (_.isNull(solcVersionRangeMatch)) { throw new Error('Could not find Solidity version range in source'); @@ -104,9 +104,6 @@ export function parseDependencies(source: string): string[] { */ export function findImportIfExist(contractSources: ContractSources, importPath: string): solc.ImportContents { const fileName = path.basename(importPath); - if (_.isUndefined(contractSources)) { - throw new Error('Contract sources not yet initialized'); - } const source = contractSources[fileName]; if (_.isUndefined(source)) { throw new Error(`Contract source not found for ${fileName}`); diff --git a/packages/deployer/src/utils/fs_wrapper.ts b/packages/deployer/src/utils/fs_wrapper.ts index 34c7caa0e..e02c83f27 100644 --- a/packages/deployer/src/utils/fs_wrapper.ts +++ b/packages/deployer/src/utils/fs_wrapper.ts @@ -7,5 +7,6 @@ export const fsWrapper = { writeFileAsync: promisify(fs.writeFile), mkdirAsync: promisify(fs.mkdir), doesPathExistSync: fs.existsSync, + rmdirSync: fs.rmdirSync, removeFileAsync: promisify(fs.unlink), }; -- cgit v1.2.3 From 477daf416805633d02c6d387be26bee6ed747ebe Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Fri, 16 Mar 2018 16:54:48 +0100 Subject: Add function docs --- packages/deployer/src/utils/compiler.ts | 5 +++++ packages/deployer/src/utils/error_reporter.ts | 1 + 2 files changed, 6 insertions(+) (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/utils/compiler.ts b/packages/deployer/src/utils/compiler.ts index 1d21d7d31..d8b5f78c9 100644 --- a/packages/deployer/src/utils/compiler.ts +++ b/packages/deployer/src/utils/compiler.ts @@ -77,6 +77,11 @@ export function getNormalizedErrMsg(errMsg: string): string { return normalizedErrMsg; } +/** + * Parses the contract source code and extracts the dendencies + * @param source Contract source code + * @return List of dependendencies + */ export function parseDependencies(source: string): string[] { // TODO: Use a proper parser const IMPORT_REGEX = /(import\s)/; diff --git a/packages/deployer/src/utils/error_reporter.ts b/packages/deployer/src/utils/error_reporter.ts index b28e4fbbe..bf3f96a7c 100644 --- a/packages/deployer/src/utils/error_reporter.ts +++ b/packages/deployer/src/utils/error_reporter.ts @@ -1,5 +1,6 @@ import { logUtils } from '@0xproject/utils'; +// Makes an async function no-throw printing errors to the console export function consoleReporter(asyncFn: (arg: T) => Promise) { const noThrowFnAsync = async (arg: T) => { try { -- cgit v1.2.3 From 111f7e917ed3cb16de9930e093ea7dbcb9451d19 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 21 Mar 2018 15:24:41 +0100 Subject: Add function docs --- packages/deployer/src/utils/error_reporter.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/utils/error_reporter.ts b/packages/deployer/src/utils/error_reporter.ts index bf3f96a7c..4e73307f0 100644 --- a/packages/deployer/src/utils/error_reporter.ts +++ b/packages/deployer/src/utils/error_reporter.ts @@ -1,7 +1,11 @@ import { logUtils } from '@0xproject/utils'; -// Makes an async function no-throw printing errors to the console -export function consoleReporter(asyncFn: (arg: T) => Promise) { +/** + * Makes an async function no-throw printing errors to the console + * @param asyncFn async function to wrap + * @return Wrapped version of the passed function + */ +export function consoleReporter(asyncFn: (arg: T) => Promise): (arg: T) => Promise { const noThrowFnAsync = async (arg: T) => { try { const result = await asyncFn(arg); -- 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') 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') 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 From e31ba2e12e90cca10037d2f6bf8ae17fe42e1991 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Wed, 21 Mar 2018 18:06:50 +0100 Subject: Add missing param comments --- packages/deployer/src/utils/compiler.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/utils/compiler.ts b/packages/deployer/src/utils/compiler.ts index d8b5f78c9..9c8fef26d 100644 --- a/packages/deployer/src/utils/compiler.ts +++ b/packages/deployer/src/utils/compiler.ts @@ -9,6 +9,7 @@ import { ContractArtifact, ContractSources } from './types'; /** * Gets contract data on network or returns if an artifact does not exist. + * @param artifactsDir Path to the artifacts directory. * @param fileName Name of contract file. * @return Contract data on network or undefined. */ @@ -34,6 +35,7 @@ export async function getContractArtifactIfExistsAsync( /** * Creates the artifacts directory if it does not already exist. + * @param artifactsDir Path to the artifacts directory. */ export async function createArtifactsDirIfDoesNotExistAsync(artifactsDir: string): Promise { if (!fsWrapper.doesPathExistSync(artifactsDir)) { @@ -104,6 +106,7 @@ export function parseDependencies(source: string): string[] { /** * Callback to resolve dependencies with `solc.compile`. * Throws error if contractSources not yet initialized. + * @param contractSources Source codes of contracts. * @param importPath Path to an imported dependency. * @return Import contents object containing source code of dependency. */ -- cgit v1.2.3 From e476682922ac57e268cf3bb7b3f46f8fba47497d Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 22 Mar 2018 12:37:44 +0100 Subject: Remove redundant types --- packages/deployer/src/utils/types.ts | 18 ------------------ 1 file changed, 18 deletions(-) (limited to 'packages/deployer/src') diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts index 72ee0df2f..540b31aff 100644 --- a/packages/deployer/src/utils/types.ts +++ b/packages/deployer/src/utils/types.ts @@ -89,24 +89,6 @@ export interface ContractSpecificSourceData { sourceTreeHash: Buffer; } -// TODO: Consolidate with 0x.js definitions once types are moved into a separate package. -export enum ZeroExError { - ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST', - ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST', - UnhandledError = 'UNHANDLED_ERROR', - UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES', - InvalidSignature = 'INVALID_SIGNATURE', - ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK', - InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER', - InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER', - InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT', - InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL', - InvalidJump = 'INVALID_JUMP', - OutOfGas = 'OUT_OF_GAS', - NoNetworkId = 'NO_NETWORK_ID', - SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND', -} - export interface Token { address?: string; name: string; -- cgit v1.2.3