aboutsummaryrefslogtreecommitdiffstats
path: root/packages/deployer/src
diff options
context:
space:
mode:
authorBrandon Millman <brandon.millman@gmail.com>2018-03-30 01:02:46 +0800
committerBrandon Millman <brandon.millman@gmail.com>2018-03-30 01:02:46 +0800
commit665011174bab7cfc6ec53e0044d60e1463222aee (patch)
tree44bc55bd390044d6cbfe8e0f7dddb621a8be7249 /packages/deployer/src
parentd106079d9b69191d9cdc6d9323dbae3e4b45daf2 (diff)
parentc4dd9658e791a9f821ea3b6eb4326bcba53b081a (diff)
downloaddexon-sol-tools-665011174bab7cfc6ec53e0044d60e1463222aee.tar
dexon-sol-tools-665011174bab7cfc6ec53e0044d60e1463222aee.tar.gz
dexon-sol-tools-665011174bab7cfc6ec53e0044d60e1463222aee.tar.bz2
dexon-sol-tools-665011174bab7cfc6ec53e0044d60e1463222aee.tar.lz
dexon-sol-tools-665011174bab7cfc6ec53e0044d60e1463222aee.tar.xz
dexon-sol-tools-665011174bab7cfc6ec53e0044d60e1463222aee.tar.zst
dexon-sol-tools-665011174bab7cfc6ec53e0044d60e1463222aee.zip
Merge branch 'development' into feature/website/wallet-wrap
* development: (35 commits) Fix CHANGELOG Update Yarn.lock Standardize changelog dates and format Fix stubbing of a non-existent property Remove redundant cast Move common types out of web3 types Add monorepo_scripts to npmignore Add typeRoots Add clean-state tests Remove nested .gitignore files since `yarn publish` gets confused by them and ignores their contents on the top-level scope Remove WETH hack now that updated WETH address is in TokenRegistry Revert TokenRegistry address on Kovan Improve rounding error message Portal fill with mixed decimals Add error popover if TokenRegistry on network user is browsing on don't include the requisite default tokens for 0x Portal to function Set timeout for compiler tests Remove redundant types Add missing param comments Fix a comment Add a comment ...
Diffstat (limited to 'packages/deployer/src')
-rw-r--r--packages/deployer/src/cli.ts22
-rw-r--r--packages/deployer/src/commands.ts2
-rw-r--r--packages/deployer/src/compiler.ts239
-rw-r--r--packages/deployer/src/deployer.ts6
-rw-r--r--packages/deployer/src/globals.d.ts3
-rw-r--r--packages/deployer/src/utils/compiler.ts123
-rw-r--r--packages/deployer/src/utils/contract.ts17
-rw-r--r--packages/deployer/src/utils/encoder.ts8
-rw-r--r--packages/deployer/src/utils/error_reporter.ts18
-rw-r--r--packages/deployer/src/utils/fs_wrapper.ts1
-rw-r--r--packages/deployer/src/utils/types.ts30
11 files changed, 261 insertions, 208 deletions
diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts
index 7913c6344..d1bd645b3 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;
@@ -24,11 +25,11 @@ 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<void> {
+async function onCompileCommandAsync(argv: CliOptions): Promise<void> {
const opts: CompilerOptions = {
contractsDir: argv.contractsDir,
networkId: argv.networkId,
- optimizerEnabled: argv.shouldOptimize ? 1 : 0,
+ optimizerEnabled: argv.shouldOptimize,
artifactsDir: argv.artifactsDir,
specifiedContracts: getContractsSetFromList(argv.contracts),
};
@@ -38,7 +39,7 @@ async function onCompileCommand(argv: CliOptions): Promise<void> {
* Deploys a single contract with provided name and args.
* @param argv Instance of process.argv provided by yargs.
*/
-async function onDeployCommand(argv: CliOptions): Promise<void> {
+async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
const url = argv.jsonrpcUrl;
const web3Provider = new Web3.providers.HttpProvider(url);
const web3Wrapper = new Web3Wrapper(web3Provider);
@@ -46,7 +47,7 @@ async function onDeployCommand(argv: CliOptions): Promise<void> {
const compilerOpts: CompilerOptions = {
contractsDir: argv.contractsDir,
networkId,
- optimizerEnabled: argv.shouldOptimize ? 1 : 0,
+ optimizerEnabled: argv.shouldOptimize,
artifactsDir: argv.artifactsDir,
specifiedContracts: getContractsSetFromList(argv.contracts),
};
@@ -62,9 +63,9 @@ async function onDeployCommand(argv: CliOptions): Promise<void> {
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.
@@ -142,7 +143,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, consoleReporter(onCompileCommandAsync))
+ .command(
+ 'deploy',
+ 'deploy a single contract with provided arguments',
+ deployCommandBuilder,
+ consoleReporter(onDeployCommandAsync),
+ )
.help().argv;
})();
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<void> {
const compiler = new Compiler(opts);
- await compiler.compileAllAsync();
+ await compiler.compileAsync();
},
async deployAsync(contractName: string, args: any[], opts: DeployerOptions): Promise<void> {
const deployer = new Deployer(opts);
diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts
index e840ed572..4741a9086 100644
--- a/packages/deployer/src/compiler.ts
+++ b/packages/deployer/src/compiler.ts
@@ -1,3 +1,4 @@
+import { ContractAbi } from '@0xproject/types';
import { logUtils, promisify } from '@0xproject/utils';
import * as ethUtil from 'ethereumjs-util';
import * as fs from 'fs';
@@ -5,10 +6,18 @@ 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';
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 {
@@ -23,10 +32,6 @@ import {
import { utils } from './utils/utils';
const ALL_CONTRACTS_IDENTIFIER = '*';
-const SOLIDITY_VERSION_REGEX = /(?:solidity\s\^?)(\d+\.\d+\.\d+)/;
-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
/**
* The Compiler facilitates compiling Solidity smart contracts and saves the results
@@ -35,9 +40,10 @@ 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;
+ // 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<string> = new Set();
private _specifiedContracts: Set<string> = new Set();
private _contractSourceData: ContractSourceData = {};
@@ -82,64 +88,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 solcVersion = Compiler._parseSolidityVersion(source);
- const contractSpecificSourceData: ContractSpecificSourceData = {
- dependencies,
- solcVersion,
- 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.
- * @param source Source code of contract.
- * @return Solc compiler version.
- */
- 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');
- }
- const solcVersion = solcVersionMatch[1];
- return solcVersion;
- }
- /**
- * 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.
* @return An instance of the Compiler class.
@@ -152,21 +100,18 @@ export class Compiler {
this._specifiedContracts = opts.specifiedContracts;
}
/**
- * Compiles all Solidity files found in `contractsDir` and writes JSON artifacts to `artifactsDir`.
+ * Compiles selected Solidity files found in `contractsDir` and writes JSON artifacts to `artifactsDir`.
*/
- public async compileAllAsync(): Promise<void> {
- await this._createArtifactsDirIfDoesNotExistAsync();
+ public async compileAsync(): Promise<void> {
+ 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);
- });
- 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);
});
@@ -180,19 +125,28 @@ 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;
}
-
- 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);
@@ -210,7 +164,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,
@@ -218,21 +172,24 @@ export class Compiler {
const sourcesToCompile = {
sources: input,
};
- const compiled = solcInstance.compile(
- sourcesToCompile,
- 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);
});
}
const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION);
const contractIdentifier = `${fileName}:${contractName}`;
- const abi: Web3.ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface);
+ if (_.isUndefined(compiled.contracts[contractIdentifier])) {
+ throw new Error(
+ `Contract ${contractName} not found in ${fileName}. Please make sure your contract has the same name as it's file name`,
+ );
+ }
+ const abi: ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface);
const bytecode = `0x${compiled.contracts[contractIdentifier].bytecode}`;
const runtimeBytecode = `0x${compiled.contracts[contractIdentifier].runtimeBytecode}`;
const sourceMap = compiled.contracts[contractIdentifier].srcmap;
@@ -240,7 +197,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,
@@ -255,10 +212,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,
},
};
@@ -277,79 +235,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<void> {
- 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<ContractArtifact | void> {
- 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/deployer.ts b/packages/deployer/src/deployer.ts
index 68518a931..7ee45fed5 100644
--- a/packages/deployer/src/deployer.ts
+++ b/packages/deployer/src/deployer.ts
@@ -1,4 +1,4 @@
-import { AbiType, TxData } from '@0xproject/types';
+import { AbiType, ConstructorAbi, ContractAbi, TxData } from '@0xproject/types';
import { logUtils } from '@0xproject/utils';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
@@ -71,7 +71,7 @@ export class Deployer {
gas,
};
const abi = contractNetworkDataIfExists.abi;
- const constructorAbi = _.find(abi, { type: AbiType.Constructor }) as Web3.ConstructorAbi;
+ const constructorAbi = _.find(abi, { type: AbiType.Constructor }) as ConstructorAbi;
const constructorArgs = _.isUndefined(constructorAbi) ? [] : constructorAbi.inputs;
if (constructorArgs.length !== args.length) {
const constructorSignature = `constructor(${_.map(constructorArgs, arg => `${arg.type} ${arg.name}`).join(
@@ -107,7 +107,7 @@ export class Deployer {
* @param txData Tx options used for deployment.
* @return Promise that resolves to a web3 contract instance.
*/
- private async _deployFromAbiAsync(abi: Web3.ContractAbi, args: any[], txData: Web3.TxData): Promise<any> {
+ private async _deployFromAbiAsync(abi: ContractAbi, args: any[], txData: TxData): Promise<any> {
const contract: Web3.Contract<Web3.ContractInstance> = this.web3Wrapper.getContractFromAbi(abi);
const deployPromise = new Promise((resolve, reject) => {
/**
diff --git a/packages/deployer/src/globals.d.ts b/packages/deployer/src/globals.d.ts
index 3cff8a909..5b0d495d5 100644
--- a/packages/deployer/src/globals.d.ts
+++ b/packages/deployer/src/globals.d.ts
@@ -1,6 +1,7 @@
+declare module 'dirty-chai';
+
// tslint:disable:completed-docs
declare module 'solc' {
- import * as Web3 from 'web3';
export interface ContractCompilationResult {
srcmap: string;
srcmapRuntime: string;
diff --git a/packages/deployer/src/utils/compiler.ts b/packages/deployer/src/utils/compiler.ts
new file mode 100644
index 000000000..9c8fef26d
--- /dev/null
+++ b/packages/deployer/src/utils/compiler.ts
@@ -0,0 +1,123 @@
+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 artifactsDir Path to the artifacts directory.
+ * @param fileName Name of contract file.
+ * @return Contract data on network or undefined.
+ */
+export async function getContractArtifactIfExistsAsync(
+ artifactsDir: string,
+ fileName: string,
+): Promise<ContractArtifact | void> {
+ 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.
+ * @param artifactsDir Path to the artifacts directory.
+ */
+export async function createArtifactsDirIfDoesNotExistAsync(artifactsDir: string): Promise<void> {
+ 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\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');
+ }
+ 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;
+}
+
+/**
+ * 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)/;
+ 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 contractSources Source codes of contracts.
+ * @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);
+ 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/contract.ts b/packages/deployer/src/utils/contract.ts
index 9c57751ff..9b7baac11 100644
--- a/packages/deployer/src/utils/contract.ts
+++ b/packages/deployer/src/utils/contract.ts
@@ -1,4 +1,5 @@
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
+import { ContractAbi, EventAbi, FunctionAbi, MethodAbi, TxData } from '@0xproject/types';
import { promisify } from '@0xproject/utils';
import * as _ from 'lodash';
import * as Web3 from 'web3';
@@ -7,14 +8,14 @@ import { AbiType } from './types';
export class Contract implements Web3.ContractInstance {
public address: string;
- public abi: Web3.ContractAbi;
+ public abi: ContractAbi;
private _contract: Web3.ContractInstance;
- private _defaults: Partial<Web3.TxData>;
+ private _defaults: Partial<TxData>;
private _validator: SchemaValidator;
// This class instance is going to be populated with functions and events depending on the ABI
// and we don't know their types in advance
[name: string]: any;
- constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<Web3.TxData>) {
+ constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<TxData>) {
this._contract = web3ContractInstance;
this.address = web3ContractInstance.address;
this.abi = web3ContractInstance.abi;
@@ -24,8 +25,8 @@ export class Contract implements Web3.ContractInstance {
this._validator = new SchemaValidator();
}
private _populateFunctions(): void {
- const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function) as Web3.FunctionAbi[];
- _.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => {
+ const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function) as FunctionAbi[];
+ _.forEach(functionsAbi, (functionAbi: MethodAbi) => {
if (functionAbi.constant) {
const cbStyleCallFunction = this._contract[functionAbi.name].call;
this[functionAbi.name] = promisify(cbStyleCallFunction, this._contract);
@@ -42,8 +43,8 @@ export class Contract implements Web3.ContractInstance {
});
}
private _populateEvents(): void {
- const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event) as Web3.EventAbi[];
- _.forEach(eventsAbi, (eventAbi: Web3.EventAbi) => {
+ const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event) as EventAbi[];
+ _.forEach(eventsAbi, (eventAbi: EventAbi) => {
this[eventAbi.name] = this._contract[eventAbi.name];
});
}
@@ -51,7 +52,7 @@ export class Contract implements Web3.ContractInstance {
const promisifiedWithDefaultParams = async (...args: any[]) => {
const promise = new Promise((resolve, reject) => {
const lastArg = args[args.length - 1];
- let txData: Partial<Web3.TxData> = {};
+ let txData: Partial<TxData> = {};
if (this._isTxData(lastArg)) {
txData = args.pop();
}
diff --git a/packages/deployer/src/utils/encoder.ts b/packages/deployer/src/utils/encoder.ts
index e3acde252..4f62662e1 100644
--- a/packages/deployer/src/utils/encoder.ts
+++ b/packages/deployer/src/utils/encoder.ts
@@ -1,15 +1,15 @@
+import { AbiDefinition, ContractAbi, DataItem } from '@0xproject/types';
import * as _ from 'lodash';
-import * as Web3 from 'web3';
import * as web3Abi from 'web3-eth-abi';
import { AbiType } from './types';
export const encoder = {
- encodeConstructorArgsFromAbi(args: any[], abi: Web3.ContractAbi): string {
+ encodeConstructorArgsFromAbi(args: any[], abi: ContractAbi): string {
const constructorTypes: string[] = [];
- _.each(abi, (element: Web3.AbiDefinition) => {
+ _.each(abi, (element: AbiDefinition) => {
if (element.type === AbiType.Constructor) {
- _.each(element.inputs, (input: Web3.DataItem) => {
+ _.each(element.inputs, (input: DataItem) => {
constructorTypes.push(input.type);
});
}
diff --git a/packages/deployer/src/utils/error_reporter.ts b/packages/deployer/src/utils/error_reporter.ts
new file mode 100644
index 000000000..4e73307f0
--- /dev/null
+++ b/packages/deployer/src/utils/error_reporter.ts
@@ -0,0 +1,18 @@
+import { logUtils } from '@0xproject/utils';
+
+/**
+ * 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<T>(asyncFn: (arg: T) => Promise<void>): (arg: T) => Promise<void> {
+ const noThrowFnAsync = async (arg: T) => {
+ try {
+ const result = await asyncFn(arg);
+ return result;
+ } catch (err) {
+ logUtils.log(`${err}`);
+ }
+ };
+ return noThrowFnAsync;
+}
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<undefined>(fs.writeFile),
mkdirAsync: promisify<undefined>(fs.mkdir),
doesPathExistSync: fs.existsSync,
+ rmdirSync: fs.rmdirSync,
removeFileAsync: promisify<undefined>(fs.unlink),
};
diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts
index 0068faf6a..7cb3958cb 100644
--- a/packages/deployer/src/utils/types.ts
+++ b/packages/deployer/src/utils/types.ts
@@ -1,4 +1,4 @@
-import { TxData } from '@0xproject/types';
+import { ContractAbi, TxData } from '@0xproject/types';
import * as Web3 from 'web3';
import * as yargs from 'yargs';
@@ -20,10 +20,10 @@ export interface ContractNetworks {
export interface ContractNetworkData {
solc_version: string;
- optimizer_enabled: number;
+ optimizer_enabled: boolean;
keccak256: string;
source_tree_hash: string;
- abi: Web3.ContractAbi;
+ abi: ContractAbi;
bytecode: string;
runtime_bytecode: string;
address?: string;
@@ -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<string>;
}
@@ -84,27 +84,9 @@ export interface ContractSourceData {
export interface ContractSpecificSourceData {
dependencies: string[];
- solcVersion: string;
+ solcVersionRange: string;
sourceHash: Buffer;
- sourceTreeHashIfExists?: 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',
+ sourceTreeHash: Buffer;
}
export interface Token {