aboutsummaryrefslogtreecommitdiffstats
path: root/packages/deployer/src
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-02-26 10:32:12 +0800
committerFabio Berger <me@fabioberger.com>2018-02-26 10:32:12 +0800
commitfffaafe4c996ac0c458dd6cb9e3598b1d93b7aa4 (patch)
tree21f9398ba2975bda4706fb8f54c0c717b88ecfd4 /packages/deployer/src
parent66b36f6d8f7c0f0487e53badb035ac50e1ec5669 (diff)
parent709fa9e02ec21cee9fc145b4a578742c8dd190aa (diff)
downloaddexon-sol-tools-fffaafe4c996ac0c458dd6cb9e3598b1d93b7aa4.tar
dexon-sol-tools-fffaafe4c996ac0c458dd6cb9e3598b1d93b7aa4.tar.gz
dexon-sol-tools-fffaafe4c996ac0c458dd6cb9e3598b1d93b7aa4.tar.bz2
dexon-sol-tools-fffaafe4c996ac0c458dd6cb9e3598b1d93b7aa4.tar.lz
dexon-sol-tools-fffaafe4c996ac0c458dd6cb9e3598b1d93b7aa4.tar.xz
dexon-sol-tools-fffaafe4c996ac0c458dd6cb9e3598b1d93b7aa4.tar.zst
dexon-sol-tools-fffaafe4c996ac0c458dd6cb9e3598b1d93b7aa4.zip
Merge branch 'development' into moveOutDocGenerator
* development: (36 commits) Fix english translations Fix footer on mobile re-add google analytics code Fix Russian translation Move all dependencies on @0xproject/types out of devDependencies Slight improvement to footer Fix a few Korean translations Address feedback Use source tree hash instead of compile flag Fix race condition Update CHANGELOG Delete artifacts directory Add generated contract artifacts to gitignore Check dependencies when determining if should be recompiled Update CHANGELOG Remove unused CHANGELOG entry Remove unused import Change assert.doesConformToShema interface Remove a type assertion Publish ...
Diffstat (limited to 'packages/deployer/src')
-rw-r--r--packages/deployer/src/cli.ts4
-rw-r--r--packages/deployer/src/compiler.ts215
-rw-r--r--packages/deployer/src/deployer.ts34
-rw-r--r--packages/deployer/src/utils/constants.ts1
-rw-r--r--packages/deployer/src/utils/encoder.ts2
-rw-r--r--packages/deployer/src/utils/types.ts16
6 files changed, 181 insertions, 91 deletions
diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts
index 3c6d042c0..ba156ac20 100644
--- a/packages/deployer/src/cli.ts
+++ b/packages/deployer/src/cli.ts
@@ -6,6 +6,7 @@ import * as Web3 from 'web3';
import * as yargs from 'yargs';
import { commands } from './commands';
+import { constants } from './utils/constants';
import { CliOptions, CompilerOptions, DeployerOptions } from './utils/types';
const DEFAULT_OPTIMIZER_ENABLED = false;
@@ -101,7 +102,8 @@ function getContractsSetFromList(contracts: string): Set<string> {
const specifiedContracts = new Set();
const contractsArray = contracts.split(',');
_.forEach(contractsArray, contractName => {
- specifiedContracts.add(contractName);
+ const fileName = `${contractName}${constants.SOLIDITY_FILE_EXTENSION}`;
+ specifiedContracts.add(fileName);
});
return specifiedContracts;
}
diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts
index 2b0b81c44..149ca5d6d 100644
--- a/packages/deployer/src/compiler.ts
+++ b/packages/deployer/src/compiler.ts
@@ -5,32 +5,39 @@ import solc = require('solc');
import * as Web3 from 'web3';
import { binPaths } from './solc/bin_paths';
+import { constants } from './utils/constants';
import { fsWrapper } from './utils/fs_wrapper';
import {
CompilerOptions,
ContractArtifact,
- ContractData,
+ ContractNetworkData,
ContractNetworks,
+ ContractSourceData,
ContractSources,
+ ContractSpecificSourceData,
ImportContents,
} from './utils/types';
import { utils } from './utils/utils';
-const SOLIDITY_FILE_EXTENSION = '.sol';
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
export class Compiler {
private _contractsDir: string;
private _networkId: number;
private _optimizerEnabled: number;
private _artifactsDir: string;
- private _contractSourcesIfExists?: ContractSources;
- private _solcErrors: Set<string>;
- private _specifiedContracts: Set<string>;
+ private _contractSources?: ContractSources;
+ private _solcErrors: Set<string> = new Set();
+ private _specifiedContracts: Set<string> = new Set();
+ private _contractSourceData: ContractSourceData = {};
/**
* Recursively retrieves Solidity source code from directory.
* @param dirPath Directory to search.
- * @return Mapping of contract name to contract source.
+ * @return Mapping of contract fileName to contract source.
*/
private static async _getContractSourcesAsync(dirPath: string): Promise<ContractSources> {
let dirContents: string[] = [];
@@ -40,15 +47,16 @@ export class Compiler {
throw new Error(`No directory found at ${dirPath}`);
}
let sources: ContractSources = {};
- for (const name of dirContents) {
- const contentPath = `${dirPath}/${name}`;
- if (path.extname(name) === SOLIDITY_FILE_EXTENSION) {
+ for (const fileName of dirContents) {
+ const contentPath = `${dirPath}/${fileName}`;
+ if (path.extname(fileName) === constants.SOLIDITY_FILE_EXTENSION) {
try {
const opts = {
encoding: 'utf8',
};
- sources[name] = await fsWrapper.readFileAsync(contentPath, opts);
- utils.consoleLog(`Reading ${name} source...`);
+ const source = await fsWrapper.readFileAsync(contentPath, opts);
+ sources[fileName] = source;
+ utils.consoleLog(`Reading ${fileName} source...`);
} catch (err) {
utils.consoleLog(`Could not find file at ${contentPath}`);
}
@@ -60,19 +68,46 @@ export class Compiler {
...nestedSources,
};
} catch (err) {
- utils.consoleLog(`${contentPath} is not a directory or ${SOLIDITY_FILE_EXTENSION} file`);
+ utils.consoleLog(`${contentPath} is not a directory or ${constants.SOLIDITY_FILE_EXTENSION} file`);
}
}
}
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\s\^?)([0-9]{1,2}[.][0-9]{1,2}[.][0-9]{1,2})/);
+ const solcVersionMatch = source.match(SOLIDITY_VERSION_REGEX);
if (_.isNull(solcVersionMatch)) {
throw new Error('Could not find Solidity version in source');
}
@@ -88,7 +123,7 @@ export class Compiler {
* @return The error message with directories truncated from the contract path.
*/
private static _getNormalizedErrMsg(errMsg: string): string {
- const errPathMatch = errMsg.match(/(.*\.sol)/);
+ const errPathMatch = errMsg.match(SOLIDITY_FILE_EXTENSION_REGEX);
if (_.isNull(errPathMatch)) {
throw new Error('Could not find a path in error message');
}
@@ -107,7 +142,6 @@ export class Compiler {
this._networkId = opts.networkId;
this._optimizerEnabled = opts.optimizerEnabled;
this._artifactsDir = opts.artifactsDir;
- this._solcErrors = new Set();
this._specifiedContracts = opts.specifiedContracts;
}
/**
@@ -115,68 +149,52 @@ export class Compiler {
*/
public async compileAllAsync(): Promise<void> {
await this._createArtifactsDirIfDoesNotExistAsync();
- this._contractSourcesIfExists = await Compiler._getContractSourcesAsync(this._contractsDir);
- const contractBaseNames = _.keys(this._contractSourcesIfExists);
- const compiledContractPromises = _.map(contractBaseNames, async (contractBaseName: string): Promise<void> => {
- return this._compileContractAsync(contractBaseName);
+ this._contractSources = await Compiler._getContractSourcesAsync(this._contractsDir);
+ _.forIn(this._contractSources, (source, fileName) => {
+ this._contractSourceData[fileName] = Compiler._getContractSpecificSourceData(source);
});
- await Promise.all(compiledContractPromises);
-
+ 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)));
this._solcErrors.forEach(errMsg => {
utils.consoleLog(errMsg);
});
}
/**
* Compiles contract and saves artifact to artifactsDir.
- * @param contractBaseName Name of contract with '.sol' extension.
+ * @param fileName Name of contract with '.sol' extension.
*/
- private async _compileContractAsync(contractBaseName: string): Promise<void> {
- if (_.isUndefined(this._contractSourcesIfExists)) {
+ private async _compileContractAsync(fileName: string): Promise<void> {
+ if (_.isUndefined(this._contractSources)) {
throw new Error('Contract sources not yet initialized');
}
+ const contractSpecificSourceData = this._contractSourceData[fileName];
+ const currentArtifactIfExists = (await this._getContractArtifactIfExistsAsync(fileName)) as ContractArtifact;
+ const sourceHash = `0x${contractSpecificSourceData.sourceHash.toString('hex')}`;
+ const sourceTreeHash = `0x${contractSpecificSourceData.sourceTreeHashIfExists.toString('hex')}`;
- const source = this._contractSourcesIfExists[contractBaseName];
- const contractName = path.basename(contractBaseName, SOLIDITY_FILE_EXTENSION);
- const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
- const sourceHash = `0x${ethUtil.sha3(source).toString('hex')}`;
- const isContractSpecified =
- this._specifiedContracts.has(ALL_CONTRACTS_IDENTIFIER) || this._specifiedContracts.has(contractName);
-
- let currentArtifactString: string;
- let currentArtifact: ContractArtifact;
- let oldNetworks: ContractNetworks;
- let shouldCompile: boolean;
- try {
- const opts = {
- encoding: 'utf8',
- };
- currentArtifactString = await fsWrapper.readFileAsync(currentArtifactPath, opts);
- currentArtifact = JSON.parse(currentArtifactString);
- oldNetworks = currentArtifact.networks;
- const oldNetwork: ContractData = oldNetworks[this._networkId];
- shouldCompile =
- (_.isUndefined(oldNetwork) ||
- oldNetwork.keccak256 !== sourceHash ||
- oldNetwork.optimizer_enabled !== this._optimizerEnabled) &&
- isContractSpecified;
- } catch (err) {
- shouldCompile = isContractSpecified;
- }
-
+ const shouldCompile =
+ _.isUndefined(currentArtifactIfExists) ||
+ currentArtifactIfExists.networks[this._networkId].optimizer_enabled !== this._optimizerEnabled ||
+ currentArtifactIfExists.networks[this._networkId].source_tree_hash !== sourceTreeHash;
if (!shouldCompile) {
return;
}
- const input = {
- [contractBaseName]: source,
- };
- const solcVersion = Compiler._parseSolidityVersion(source);
- const fullSolcVersion = binPaths[solcVersion];
+ const fullSolcVersion = binPaths[contractSpecificSourceData.solcVersion];
const solcBinPath = `./solc/solc_bin/${fullSolcVersion}`;
const solcBin = require(solcBinPath);
const solcInstance = solc.setupMethods(solcBin);
- utils.consoleLog(`Compiling ${contractBaseName}...`);
+ utils.consoleLog(`Compiling ${fileName}...`);
+ const source = this._contractSources[fileName];
+ const input = {
+ [fileName]: source,
+ };
const sourcesToCompile = {
sources: input,
};
@@ -187,19 +205,21 @@ export class Compiler {
);
if (!_.isUndefined(compiled.errors)) {
- _.each(compiled.errors, errMsg => {
+ _.forEach(compiled.errors, errMsg => {
const normalizedErrMsg = Compiler._getNormalizedErrMsg(errMsg);
this._solcErrors.add(normalizedErrMsg);
});
}
- const contractIdentifier = `${contractBaseName}:${contractName}`;
+ const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION);
+ const contractIdentifier = `${fileName}:${contractName}`;
const abi: Web3.ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface);
const unlinked_binary = `0x${compiled.contracts[contractIdentifier].bytecode}`;
const updated_at = Date.now();
- const contractData: ContractData = {
- solc_version: solcVersion,
+ const contractNetworkData: ContractNetworkData = {
+ solc_version: contractSpecificSourceData.solcVersion,
keccak256: sourceHash,
+ source_tree_hash: sourceTreeHash,
optimizer_enabled: this._optimizerEnabled,
abi,
unlinked_binary,
@@ -207,26 +227,56 @@ export class Compiler {
};
let newArtifact: ContractArtifact;
- if (!_.isUndefined(currentArtifactString)) {
+ if (!_.isUndefined(currentArtifactIfExists)) {
newArtifact = {
- ...currentArtifact,
+ ...currentArtifactIfExists,
networks: {
- ...oldNetworks,
- [this._networkId]: contractData,
+ ...currentArtifactIfExists.networks,
+ [this._networkId]: contractNetworkData,
},
};
} else {
newArtifact = {
contract_name: contractName,
networks: {
- [this._networkId]: contractData,
+ [this._networkId]: contractNetworkData,
},
};
}
const artifactString = utils.stringifyWithFormatting(newArtifact);
+ const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
- utils.consoleLog(`${contractBaseName} artifact saved!`);
+ utils.consoleLog(`${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`.
@@ -235,11 +285,11 @@ export class Compiler {
* @return Import contents object containing source code of dependency.
*/
private _findImportsIfSourcesExist(importPath: string): ImportContents {
- if (_.isUndefined(this._contractSourcesIfExists)) {
- throw new Error('Contract sources not yet initialized');
+ const fileName = path.basename(importPath);
+ const source = this._contractSources[fileName];
+ if (_.isUndefined(source)) {
+ throw new Error(`Contract source not found for ${fileName}`);
}
- const contractBaseName = path.basename(importPath);
- const source = this._contractSourcesIfExists[contractBaseName];
const importContents: ImportContents = {
contents: source,
};
@@ -254,4 +304,25 @@ export class Compiler {
await fsWrapper.mkdirAsync(this._artifactsDir);
}
}
+ /**
+ * 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.
+ */
+ 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) {
+ utils.consoleLog(`Artifact for ${fileName} does not exist`);
+ return undefined;
+ }
+ }
}
diff --git a/packages/deployer/src/deployer.ts b/packages/deployer/src/deployer.ts
index 6f03581e8..021645fd1 100644
--- a/packages/deployer/src/deployer.ts
+++ b/packages/deployer/src/deployer.ts
@@ -6,7 +6,7 @@ import * as Web3 from 'web3';
import { Contract } from './utils/contract';
import { encoder } from './utils/encoder';
import { fsWrapper } from './utils/fs_wrapper';
-import { ContractArtifact, ContractData, DeployerOptions } from './utils/types';
+import { ContractArtifact, ContractNetworkData, DeployerOptions } from './utils/types';
import { utils } from './utils/utils';
// Gas added to gas estimate to make sure there is sufficient gas for deployment.
@@ -35,9 +35,11 @@ export class Deployer {
* @return Deployed contract instance.
*/
public async deployAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> {
- const contractArtifact: ContractArtifact = this._loadContractArtifactIfExists(contractName);
- const contractData: ContractData = this._getContractDataFromArtifactIfExists(contractArtifact);
- const data = contractData.unlinked_binary;
+ const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
+ const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
+ contractArtifactIfExists,
+ );
+ const data = contractNetworkDataIfExists.unlinked_binary;
const from = await this._getFromAddressAsync();
const gas = await this._getAllowableGasEstimateAsync(data);
const txData = {
@@ -46,7 +48,7 @@ export class Deployer {
data,
gas,
};
- const abi = contractData.abi;
+ const abi = contractNetworkDataIfExists.abi;
const web3ContractInstance = await this._deployFromAbiAsync(abi, args, txData);
utils.consoleLog(`${contractName}.sol successfully deployed at ${web3ContractInstance.address}`);
const contractInstance = new Contract(web3ContractInstance, this._defaults);
@@ -100,19 +102,21 @@ export class Deployer {
contractAddress: string,
args: any[],
): Promise<void> {
- const contractArtifact: ContractArtifact = this._loadContractArtifactIfExists(contractName);
- const contractData: ContractData = this._getContractDataFromArtifactIfExists(contractArtifact);
- const abi = contractData.abi;
+ const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
+ const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
+ contractArtifactIfExists,
+ );
+ const abi = contractNetworkDataIfExists.abi;
const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi);
const newContractData = {
- ...contractData,
+ ...contractNetworkDataIfExists,
address: contractAddress,
constructor_args: encodedConstructorArgs,
};
const newArtifact = {
- ...contractArtifact,
+ ...contractArtifactIfExists,
networks: {
- ...contractArtifact.networks,
+ ...contractArtifactIfExists.networks,
[this._networkId]: newContractData,
},
};
@@ -139,12 +143,12 @@ export class Deployer {
* @param contractArtifact The contract artifact.
* @return Network specific contract data.
*/
- private _getContractDataFromArtifactIfExists(contractArtifact: ContractArtifact): ContractData {
- const contractData = contractArtifact.networks[this._networkId];
- if (_.isUndefined(contractData)) {
+ private _getContractNetworkDataFromArtifactIfExists(contractArtifact: ContractArtifact): ContractNetworkData {
+ const contractNetworkDataIfExists = contractArtifact.networks[this._networkId];
+ if (_.isUndefined(contractNetworkDataIfExists)) {
throw new Error(`Data not found in artifact for contract: ${contractArtifact.contract_name}`);
}
- return contractData;
+ return contractNetworkDataIfExists;
}
/**
* Gets the address to use for sending a transaction.
diff --git a/packages/deployer/src/utils/constants.ts b/packages/deployer/src/utils/constants.ts
index 8871a470d..57f30dec8 100644
--- a/packages/deployer/src/utils/constants.ts
+++ b/packages/deployer/src/utils/constants.ts
@@ -1,3 +1,4 @@
export const constants = {
NULL_BYTES: '0x',
+ SOLIDITY_FILE_EXTENSION: '.sol',
};
diff --git a/packages/deployer/src/utils/encoder.ts b/packages/deployer/src/utils/encoder.ts
index d5f807774..e3acde252 100644
--- a/packages/deployer/src/utils/encoder.ts
+++ b/packages/deployer/src/utils/encoder.ts
@@ -9,7 +9,7 @@ export const encoder = {
const constructorTypes: string[] = [];
_.each(abi, (element: Web3.AbiDefinition) => {
if (element.type === AbiType.Constructor) {
- _.each(element.inputs, (input: Web3.FunctionParameter) => {
+ _.each(element.inputs, (input: Web3.DataItem) => {
constructorTypes.push(input.type);
});
}
diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts
index 46481828e..a3f722976 100644
--- a/packages/deployer/src/utils/types.ts
+++ b/packages/deployer/src/utils/types.ts
@@ -15,13 +15,14 @@ export interface ContractArtifact {
}
export interface ContractNetworks {
- [key: number]: ContractData;
+ [key: number]: ContractNetworkData;
}
-export interface ContractData {
+export interface ContractNetworkData {
solc_version: string;
optimizer_enabled: number;
keccak256: string;
+ source_tree_hash: string;
abi: Web3.ContractAbi;
unlinked_binary: string;
address?: string;
@@ -64,6 +65,17 @@ export interface ContractSources {
[key: string]: string;
}
+export interface ContractSourceData {
+ [key: string]: ContractSpecificSourceData;
+}
+
+export interface ContractSpecificSourceData {
+ dependencies: string[];
+ solcVersion: string;
+ sourceHash: Buffer;
+ sourceTreeHashIfExists?: Buffer;
+}
+
export interface ImportContents {
contents: string;
}