aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeonid Logvinov <logvinov.leon@gmail.com>2018-03-16 22:09:11 +0800
committerLeonid Logvinov <logvinov.leon@gmail.com>2018-03-21 22:11:41 +0800
commit18b9fe525622d9e18491f76136d7697806996a09 (patch)
tree5bed8ae3fecf81a414d83dba67f7437b28d40bbe
parent439e8640859cf790bac51c1beeec4cf65aa33c57 (diff)
downloaddexon-sol-tools-18b9fe525622d9e18491f76136d7697806996a09.tar.gz
dexon-sol-tools-18b9fe525622d9e18491f76136d7697806996a09.tar.zst
dexon-sol-tools-18b9fe525622d9e18491f76136d7697806996a09.zip
Enable strictNullChecks
-rw-r--r--packages/deployer/src/cli.ts4
-rw-r--r--packages/deployer/src/commands.ts2
-rw-r--r--packages/deployer/src/compiler.ts210
-rw-r--r--packages/deployer/src/utils/compiler.ts118
-rw-r--r--packages/deployer/src/utils/types.ts2
-rw-r--r--packages/deployer/test/deploy_test.ts2
6 files changed, 182 insertions, 156 deletions
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<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.
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 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<string> = new Set();
private _specifiedContracts: Set<string> = new Set();
private _contractSourceData: ContractSourceData = {};
@@ -79,64 +83,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.
* @return An instance of the Compiler class.
@@ -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<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);
- });
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<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/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<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.
+ */
+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 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.
diff --git a/packages/deployer/test/deploy_test.ts b/packages/deployer/test/deploy_test.ts
index 26ce337ef..f67cd8234 100644
--- a/packages/deployer/test/deploy_test.ts
+++ b/packages/deployer/test/deploy_test.ts
@@ -38,7 +38,7 @@ beforeEach(function(done: DoneCallback) {
if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) {
await fsWrapper.removeFileAsync(exchangeArtifactPath);
}
- await compiler.compileAllAsync();
+ await compiler.compileAsync();
done();
})().catch(done);
});