aboutsummaryrefslogtreecommitdiffstats
path: root/packages/deployer
diff options
context:
space:
mode:
Diffstat (limited to 'packages/deployer')
-rw-r--r--packages/deployer/CHANGELOG.json3
-rw-r--r--packages/deployer/CHANGELOG.md4
-rw-r--r--packages/deployer/README.md2
-rw-r--r--packages/deployer/package.json20
-rw-r--r--packages/deployer/src/cli.ts41
-rw-r--r--packages/deployer/src/compiler.ts107
-rw-r--r--packages/deployer/src/deployer.ts12
-rw-r--r--packages/deployer/src/utils/compiler.ts65
-rw-r--r--packages/deployer/src/utils/contract.ts4
-rw-r--r--packages/deployer/src/utils/encoder.ts4
-rw-r--r--packages/deployer/src/utils/types.ts19
-rw-r--r--packages/deployer/test/compiler_test.ts16
-rw-r--r--packages/deployer/test/compiler_utils_test.ts22
-rw-r--r--packages/deployer/test/deployer_test.ts19
-rw-r--r--packages/deployer/test/fixtures/contracts/main/Exchange.sol (renamed from packages/deployer/test/fixtures/contracts/Exchange.sol)6
-rw-r--r--packages/deployer/test/fixtures/contracts/main/TokenTransferProxy.sol (renamed from packages/deployer/test/fixtures/contracts/TokenTransferProxy.sol)6
-rw-r--r--packages/deployer/test/util/constants.ts1
-rw-r--r--packages/deployer/test/util/provider.ts9
18 files changed, 260 insertions, 100 deletions
diff --git a/packages/deployer/CHANGELOG.json b/packages/deployer/CHANGELOG.json
index f9691466b..b130405dc 100644
--- a/packages/deployer/CHANGELOG.json
+++ b/packages/deployer/CHANGELOG.json
@@ -6,7 +6,8 @@
"note": "Changed the config key `web3Provider` to `provider` to be consistent with other tools",
"pr": 501
}
- ]
+ ],
+ "timestamp": 1523462196
},
{
"version": "0.3.5",
diff --git a/packages/deployer/CHANGELOG.md b/packages/deployer/CHANGELOG.md
index b11aa988c..4b49092ae 100644
--- a/packages/deployer/CHANGELOG.md
+++ b/packages/deployer/CHANGELOG.md
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
+## v0.4.0 - _April 11, 2018_
+
+ * Changed the config key `web3Provider` to `provider` to be consistent with other tools (#501)
+
## v0.3.5 - _April 2, 2018_
* Don't try to write contract artifact if an error occured (#485)
diff --git a/packages/deployer/README.md b/packages/deployer/README.md
index d8b049bdf..ef0ddd59d 100644
--- a/packages/deployer/README.md
+++ b/packages/deployer/README.md
@@ -2,7 +2,7 @@
This repository contains a CLI tool that facilitates compiling and deployment of smart contracts.
-### Read the [Documentation](0xproject.com/docs/deployer).
+### Read the [Documentation](https://0xproject.com/docs/deployer).
## Installation
diff --git a/packages/deployer/package.json b/packages/deployer/package.json
index f6eff9973..1050c095f 100644
--- a/packages/deployer/package.json
+++ b/packages/deployer/package.json
@@ -1,6 +1,6 @@
{
"name": "@0xproject/deployer",
- "version": "0.3.5",
+ "version": "0.4.0",
"description": "Smart contract deployer of 0x protocol",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
@@ -8,7 +8,7 @@
"build:watch": "tsc -w",
"build": "yarn clean && copyfiles 'test/fixtures/contracts/**/*' ./lib && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
"test": "run-s build run_mocha",
- "run_mocha": "mocha lib/test/*_test.js",
+ "run_mocha": "mocha lib/test/*_test.js --bail --exit",
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"compile": "npm run build; node lib/src/cli.js compile",
@@ -47,12 +47,14 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/deployer/README.md",
"devDependencies": {
- "@0xproject/monorepo-scripts": "^0.1.16",
- "@0xproject/tslint-config": "^0.4.14",
+ "@0xproject/dev-utils": "^0.3.4",
+ "@0xproject/monorepo-scripts": "^0.1.17",
+ "@0xproject/tslint-config": "^0.4.15",
"@types/require-from-string": "^1.2.0",
"@types/semver": "^5.5.0",
"@types/yargs": "^11.0.0",
"chai": "^4.0.1",
+ "chai-as-promised": "^7.1.0",
"copyfiles": "^1.2.0",
"dirty-chai": "^2.0.1",
"mocha": "^4.0.1",
@@ -66,11 +68,11 @@
"web3-typescript-typings": "^0.10.2"
},
"dependencies": {
- "@0xproject/json-schemas": "^0.7.19",
- "@0xproject/types": "^0.5.0",
- "@0xproject/typescript-typings": "^0.0.3",
- "@0xproject/utils": "^0.5.0",
- "@0xproject/web3-wrapper": "^0.5.0",
+ "@0xproject/json-schemas": "^0.7.20",
+ "@0xproject/types": "^0.6.0",
+ "@0xproject/typescript-typings": "^0.1.0",
+ "@0xproject/utils": "^0.5.1",
+ "@0xproject/web3-wrapper": "^0.6.0",
"ethereumjs-util": "^5.1.1",
"isomorphic-fetch": "^2.2.1",
"lodash": "^4.17.4",
diff --git a/packages/deployer/src/cli.ts b/packages/deployer/src/cli.ts
index d1bd645b3..7b32187c4 100644
--- a/packages/deployer/src/cli.ts
+++ b/packages/deployer/src/cli.ts
@@ -11,7 +11,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';
+import { CliOptions, CompilerOptions, ContractDirectory, DeployerOptions } from './utils/types';
const DEFAULT_OPTIMIZER_ENABLED = false;
const DEFAULT_CONTRACTS_DIR = path.resolve('src/contracts');
@@ -27,7 +27,7 @@ const DEFAULT_CONTRACTS_LIST = '*';
*/
async function onCompileCommandAsync(argv: CliOptions): Promise<void> {
const opts: CompilerOptions = {
- contractsDir: argv.contractsDir,
+ contractDirs: getContractDirectoriesFromList(argv.contractDirs),
networkId: argv.networkId,
optimizerEnabled: argv.shouldOptimize,
artifactsDir: argv.artifactsDir,
@@ -41,11 +41,11 @@ async function onCompileCommandAsync(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);
+ const provider = new Web3.providers.HttpProvider(url);
+ const web3Wrapper = new Web3Wrapper(provider);
const networkId = await web3Wrapper.getNetworkIdAsync();
const compilerOpts: CompilerOptions = {
- contractsDir: argv.contractsDir,
+ contractDirs: getContractDirectoriesFromList(argv.contractDirs),
networkId,
optimizerEnabled: argv.shouldOptimize,
artifactsDir: argv.artifactsDir,
@@ -69,6 +69,29 @@ async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
}
/**
* Creates a set of contracts to compile.
+ * @param contractDirectoriesList Comma separated list of contract directories
+ * @return Set of contract directories
+ */
+function getContractDirectoriesFromList(contractDirectoriesList: string): Set<ContractDirectory> {
+ const directories = new Set();
+ const possiblyNamespacedDirectories = contractDirectoriesList.split(',');
+ _.forEach(possiblyNamespacedDirectories, namespacedDirectory => {
+ const directoryComponents = namespacedDirectory.split(':');
+ if (directoryComponents.length === 1) {
+ const directory = { namespace: '', path: directoryComponents[0] };
+ directories.add(directory);
+ } else if (directoryComponents.length === 2) {
+ const directory = { namespace: directoryComponents[0], path: directoryComponents[1] };
+ directories.add(directory);
+ } else {
+ throw new Error(`Unable to parse contracts directory: '${namespacedDirectory}'`);
+ }
+ });
+
+ return directories;
+}
+/**
+ * Creates a set of contracts to compile.
* @param contracts Comma separated list of contracts to compile
*/
function getContractsSetFromList(contracts: string): Set<string> {
@@ -78,8 +101,7 @@ function getContractsSetFromList(contracts: string): Set<string> {
}
const contractsArray = contracts.split(',');
_.forEach(contractsArray, contractName => {
- const fileName = `${contractName}${constants.SOLIDITY_FILE_EXTENSION}`;
- specifiedContracts.add(fileName);
+ specifiedContracts.add(contractName);
});
return specifiedContracts;
}
@@ -104,10 +126,11 @@ function deployCommandBuilder(yargsInstance: any) {
(() => {
const identityCommandBuilder = _.identity;
return yargs
- .option('contracts-dir', {
+ .option('contract-dirs', {
type: 'string',
default: DEFAULT_CONTRACTS_DIR,
- description: 'path of contracts directory to compile',
+ description:
+ "comma separated list of contract directories.\nTo avoid filename clashes, directories should be prefixed with a namespace as follows: 'namespace:/path/to/dir'.",
})
.option('network-id', {
type: 'number',
diff --git a/packages/deployer/src/compiler.ts b/packages/deployer/src/compiler.ts
index ba360cb57..e3ecc6c72 100644
--- a/packages/deployer/src/compiler.ts
+++ b/packages/deployer/src/compiler.ts
@@ -1,4 +1,4 @@
-import { ContractAbi } from '@0xproject/types';
+import { AbiType, ContractAbi, MethodAbi } from '@0xproject/types';
import { logUtils, promisify } from '@0xproject/utils';
import * as ethUtil from 'ethereumjs-util';
import * as fs from 'fs';
@@ -11,6 +11,8 @@ import solc = require('solc');
import { binPaths } from './solc/bin_paths';
import {
+ constructContractId,
+ constructUniqueSourceFileId,
createDirIfDoesNotExistAsync,
findImportIfExist,
getContractArtifactIfExistsAsync,
@@ -23,11 +25,14 @@ import { fsWrapper } from './utils/fs_wrapper';
import {
CompilerOptions,
ContractArtifact,
+ ContractDirectory,
+ ContractIdToSourceFileId,
ContractNetworkData,
ContractNetworks,
- ContractSourceData,
+ ContractSourceDataByFileId,
ContractSources,
ContractSpecificSourceData,
+ FunctionNameToSeenCount,
} from './utils/types';
import { utils } from './utils/utils';
@@ -39,20 +44,22 @@ const SOLC_BIN_DIR = path.join(__dirname, '..', '..', 'solc_bin');
* to artifact files.
*/
export class Compiler {
- private _contractsDir: string;
+ private _contractDirs: Set<ContractDirectory>;
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 _specifiedContracts: Set<string> = new Set();
- private _contractSourceData: ContractSourceData = {};
+ private _contractSourceDataByFileId: ContractSourceDataByFileId = {};
+
/**
* Recursively retrieves Solidity source code from directory.
* @param dirPath Directory to search.
- * @return Mapping of contract fileName to contract source.
+ * @param contractBaseDir Base contracts directory of search tree.
+ * @return Mapping of sourceFilePath to the contract source.
*/
- private static async _getContractSourcesAsync(dirPath: string): Promise<ContractSources> {
+ private static async _getContractSourcesAsync(dirPath: string, contractBaseDir: string): Promise<ContractSources> {
let dirContents: string[] = [];
try {
dirContents = await fsWrapper.readdirAsync(dirPath);
@@ -68,14 +75,18 @@ export class Compiler {
encoding: 'utf8',
};
const source = await fsWrapper.readFileAsync(contentPath, opts);
- sources[fileName] = source;
- logUtils.log(`Reading ${fileName} source...`);
+ if (!_.startsWith(contentPath, contractBaseDir)) {
+ throw new Error(`Expected content path '${contentPath}' to begin with '${contractBaseDir}'`);
+ }
+ const sourceFilePath = contentPath.slice(contractBaseDir.length);
+ sources[sourceFilePath] = source;
+ logUtils.log(`Reading ${sourceFilePath} source...`);
} catch (err) {
logUtils.log(`Could not find file at ${contentPath}`);
}
} else {
try {
- const nestedSources = await Compiler._getContractSourcesAsync(contentPath);
+ const nestedSources = await Compiler._getContractSourcesAsync(contentPath, contractBaseDir);
sources = {
...sources,
...nestedSources,
@@ -93,7 +104,7 @@ export class Compiler {
* @return An instance of the Compiler class.
*/
constructor(opts: CompilerOptions) {
- this._contractsDir = opts.contractsDir;
+ this._contractDirs = opts.contractDirs;
this._networkId = opts.networkId;
this._optimizerEnabled = opts.optimizerEnabled;
this._artifactsDir = opts.artifactsDir;
@@ -105,25 +116,49 @@ export class Compiler {
public async compileAsync(): Promise<void> {
await createDirIfDoesNotExistAsync(this._artifactsDir);
await createDirIfDoesNotExistAsync(SOLC_BIN_DIR);
- this._contractSources = await Compiler._getContractSourcesAsync(this._contractsDir);
+ this._contractSources = {};
+ const contractIdToSourceFileId: ContractIdToSourceFileId = {};
+ const contractDirs = Array.from(this._contractDirs.values());
+ for (const contractDir of contractDirs) {
+ const sources = await Compiler._getContractSourcesAsync(contractDir.path, contractDir.path);
+ _.forIn(sources, (source, sourceFilePath) => {
+ const sourceFileId = constructUniqueSourceFileId(contractDir.namespace, sourceFilePath);
+ // Record the file's source and data
+ if (!_.isUndefined(this._contractSources[sourceFileId])) {
+ throw new Error(`Found duplicate source files with ID '${sourceFileId}'`);
+ }
+ this._contractSources[sourceFileId] = source;
+ // Create a mapping between the contract id and its source file id
+ const contractId = constructContractId(contractDir.namespace, sourceFilePath);
+ if (!_.isUndefined(contractIdToSourceFileId[contractId])) {
+ throw new Error(`Found duplicate contract with ID '${contractId}'`);
+ }
+ contractIdToSourceFileId[contractId] = sourceFileId;
+ });
+ }
_.forIn(this._contractSources, this._setContractSpecificSourceData.bind(this));
- const fileNames = this._specifiedContracts.has(ALL_CONTRACTS_IDENTIFIER)
- ? _.keys(this._contractSources)
+ const specifiedContractIds = this._specifiedContracts.has(ALL_CONTRACTS_IDENTIFIER)
+ ? _.keys(contractIdToSourceFileId)
: Array.from(this._specifiedContracts.values());
- for (const fileName of fileNames) {
- await this._compileContractAsync(fileName);
- }
+ await Promise.all(
+ _.map(specifiedContractIds, async contractId =>
+ this._compileContractAsync(contractIdToSourceFileId[contractId]),
+ ),
+ );
}
/**
* Compiles contract and saves artifact to artifactsDir.
- * @param fileName Name of contract with '.sol' extension.
+ * @param sourceFileId Unique ID of the source file.
*/
- private async _compileContractAsync(fileName: string): Promise<void> {
+ private async _compileContractAsync(sourceFileId: string): Promise<void> {
if (_.isUndefined(this._contractSources)) {
throw new Error('Contract sources not yet initialized');
}
- const contractSpecificSourceData = this._contractSourceData[fileName];
- const currentArtifactIfExists = await getContractArtifactIfExistsAsync(this._artifactsDir, fileName);
+ if (_.isUndefined(this._contractSourceDataByFileId[sourceFileId])) {
+ throw new Error(`Contract source for ${sourceFileId} not yet initialized`);
+ }
+ const contractSpecificSourceData = this._contractSourceDataByFileId[sourceFileId];
+ const currentArtifactIfExists = await getContractArtifactIfExistsAsync(this._artifactsDir, sourceFileId);
const sourceHash = `0x${contractSpecificSourceData.sourceHash.toString('hex')}`;
const sourceTreeHash = `0x${contractSpecificSourceData.sourceTreeHash.toString('hex')}`;
@@ -162,16 +197,17 @@ export class Compiler {
}
const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename));
- logUtils.log(`Compiling ${fileName} with Solidity v${solcVersion}...`);
- const source = this._contractSources[fileName];
+ logUtils.log(`Compiling ${sourceFileId} with Solidity v${solcVersion}...`);
+ const source = this._contractSources[sourceFileId];
const input = {
- [fileName]: source,
+ [sourceFileId]: source,
};
const sourcesToCompile = {
sources: input,
};
+
const compiled = solcInstance.compile(sourcesToCompile, Number(this._optimizerEnabled), importPath =>
- findImportIfExist(this._contractSources, importPath),
+ findImportIfExist(this._contractSources, sourceFileId, importPath),
);
if (!_.isUndefined(compiled.errors)) {
@@ -193,11 +229,11 @@ export class Compiler {
});
}
}
- const contractName = path.basename(fileName, constants.SOLIDITY_FILE_EXTENSION);
- const contractIdentifier = `${fileName}:${contractName}`;
+ const contractName = path.basename(sourceFileId, constants.SOLIDITY_FILE_EXTENSION);
+ const contractIdentifier = `${sourceFileId}:${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 it's file name`,
+ `Contract ${contractName} not found in ${sourceFileId}. Please make sure your contract has the same name as it's file name`,
);
}
const abi: ContractAbi = JSON.parse(compiled.contracts[contractIdentifier].interface);
@@ -207,6 +243,7 @@ export class Compiler {
const sourceMapRuntime = compiled.contracts[contractIdentifier].srcmapRuntime;
const sources = _.keys(compiled.sources);
const updated_at = Date.now();
+
const contractNetworkData: ContractNetworkData = {
solc_version: solcVersion,
keccak256: sourceHash,
@@ -243,28 +280,30 @@ export class Compiler {
const artifactString = utils.stringifyWithFormatting(newArtifact);
const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
- logUtils.log(`${fileName} artifact saved!`);
+ logUtils.log(`${sourceFileId} artifact saved!`);
}
/**
* Gets contract dependendencies and keccak256 hash from source.
* @param source Source code of contract.
+ * @param fileId FileId of the contract source file.
* @return Object with contract dependencies and keccak256 hash of source.
*/
- private _setContractSpecificSourceData(source: string, fileName: string): void {
- if (!_.isUndefined(this._contractSourceData[fileName])) {
+ private _setContractSpecificSourceData(source: string, fileId: string): void {
+ if (!_.isUndefined(this._contractSourceDataByFileId[fileId])) {
return;
}
const sourceHash = ethUtil.sha3(source);
const solcVersionRange = parseSolidityVersionRange(source);
- const dependencies = parseDependencies(source);
- const sourceTreeHash = this._getSourceTreeHash(fileName, sourceHash, dependencies);
- this._contractSourceData[fileName] = {
+ const dependencies = parseDependencies(source, fileId);
+ const sourceTreeHash = this._getSourceTreeHash(fileId, sourceHash, dependencies);
+ this._contractSourceDataByFileId[fileId] = {
dependencies,
solcVersionRange,
sourceHash,
sourceTreeHash,
};
}
+
/**
* Gets the source tree hash for a file and its dependencies.
* @param fileName Name of contract file.
@@ -276,7 +315,7 @@ export class Compiler {
const dependencySourceTreeHashes = _.map(dependencies, dependency => {
const source = this._contractSources[dependency];
this._setContractSpecificSourceData(source, dependency);
- const sourceData = this._contractSourceData[dependency];
+ const sourceData = this._contractSourceDataByFileId[dependency];
return this._getSourceTreeHash(dependency, sourceData.sourceHash, sourceData.dependencies);
});
const sourceTreeHashesBuffer = Buffer.concat([sourceHash, ...dependencySourceTreeHashes]);
diff --git a/packages/deployer/src/deployer.ts b/packages/deployer/src/deployer.ts
index 84392997c..ad05417b1 100644
--- a/packages/deployer/src/deployer.ts
+++ b/packages/deployer/src/deployer.ts
@@ -38,17 +38,17 @@ export class Deployer {
this._artifactsDir = opts.artifactsDir;
this._networkId = opts.networkId;
this._defaults = opts.defaults;
- let web3Provider: Provider;
+ let provider: Provider;
if (_.isUndefined((opts as ProviderDeployerOptions).provider)) {
const jsonrpcUrl = (opts as UrlDeployerOptions).jsonrpcUrl;
if (_.isUndefined(jsonrpcUrl)) {
- throw new Error(`Deployer options don't contain web3Provider nor jsonrpcUrl. Please pass one of them`);
+ throw new Error(`Deployer options don't contain provider nor jsonrpcUrl. Please pass one of them`);
}
- web3Provider = new Web3.providers.HttpProvider(jsonrpcUrl);
+ provider = new Web3.providers.HttpProvider(jsonrpcUrl);
} else {
- web3Provider = (opts as ProviderDeployerOptions).provider;
+ provider = (opts as ProviderDeployerOptions).provider;
}
- this.web3Wrapper = new Web3Wrapper(web3Provider, this._defaults);
+ this.web3Wrapper = new Web3Wrapper(provider, this._defaults);
}
/**
* Loads a contract's corresponding artifacts and deploys it with the supplied constructor arguments.
@@ -170,7 +170,7 @@ export class Deployer {
const contractArtifact: ContractArtifact = require(artifactPath);
return contractArtifact;
} catch (err) {
- throw new Error(`Artifact not found for contract: ${contractName}`);
+ throw new Error(`Artifact not found for contract: ${contractName} at ${artifactPath}`);
}
}
/**
diff --git a/packages/deployer/src/utils/compiler.ts b/packages/deployer/src/utils/compiler.ts
index d5137d394..600495693 100644
--- a/packages/deployer/src/utils/compiler.ts
+++ b/packages/deployer/src/utils/compiler.ts
@@ -1,3 +1,4 @@
+import { AbiType, ContractAbi, MethodAbi } from '@0xproject/types';
import { logUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import * as path from 'path';
@@ -5,9 +6,49 @@ import * as solc from 'solc';
import { constants } from './constants';
import { fsWrapper } from './fs_wrapper';
-import { ContractArtifact, ContractSources } from './types';
+import { ContractArtifact, ContractSources, FunctionNameToSeenCount } from './types';
/**
+ * Constructs a system-wide unique identifier for a source file.
+ * @param directoryNamespace Namespace of the source file's root contract directory.
+ * @param sourceFilePath Path to a source file, relative to contractBaseDir.
+ * @return sourceFileId A system-wide unique identifier for the source file.
+ */
+export function constructUniqueSourceFileId(directoryNamespace: string, sourceFilePath: string): string {
+ const namespacePrefix = !_.isEmpty(directoryNamespace) ? `/${directoryNamespace}` : '';
+ const sourceFilePathNoLeadingSlash = sourceFilePath.replace(/^\/+/g, '');
+ const sourceFileId = `${namespacePrefix}/${sourceFilePathNoLeadingSlash}`;
+ return sourceFileId;
+}
+/**
+ * Constructs a system-wide unique identifier for a dependency file.
+ * @param dependencyFilePath Path from a sourceFile to a dependency.
+ * @param contractBaseDir Base contracts directory of search tree.
+ * @return sourceFileId A system-wide unique identifier for the source file.
+ */
+export function constructDependencyFileId(dependencyFilePath: string, sourceFilePath: string): string {
+ if (_.startsWith(dependencyFilePath, '/')) {
+ // Path of the form /namespace/path/to/dependency.sol
+ return dependencyFilePath;
+ } else {
+ // Dependency is relative to the source file: ./dependency.sol, ../../some/path/dependency.sol, etc.
+ // Join the two paths to construct a valid source file id: /namespace/path/to/dependency.sol
+ return path.join(path.dirname(sourceFilePath), dependencyFilePath);
+ }
+}
+/**
+ * Constructs a system-wide unique identifier for a contract.
+ * @param directoryNamespace Namespace of the source file's root contract directory.
+ * @param sourceFilePath Path to a source file, relative to contractBaseDir.
+ * @return sourceFileId A system-wide unique identifier for contract.
+ */
+export function constructContractId(directoryNamespace: string, sourceFilePath: string): string {
+ const namespacePrefix = !_.isEmpty(directoryNamespace) ? `${directoryNamespace}:` : '';
+ const sourceFileName = path.basename(sourceFilePath, constants.SOLIDITY_FILE_EXTENSION);
+ const contractId = `${namespacePrefix}${sourceFileName}`;
+ return contractId;
+}
+/**
* 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.
@@ -82,9 +123,10 @@ export function getNormalizedErrMsg(errMsg: string): string {
/**
* Parses the contract source code and extracts the dendencies
* @param source Contract source code
+ * @param sourceFilePath File path of the source code.
* @return List of dependendencies
*/
-export function parseDependencies(source: string): string[] {
+export function parseDependencies(source: string, sourceFileId: 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
@@ -95,8 +137,8 @@ export function parseDependencies(source: string): string[] {
const dependencyMatch = line.match(DEPENDENCY_PATH_REGEX);
if (!_.isNull(dependencyMatch)) {
const dependencyPath = dependencyMatch[1];
- const basenName = path.basename(dependencyPath);
- dependencies.push(basenName);
+ const dependencyId = constructDependencyFileId(dependencyPath, sourceFileId);
+ dependencies.push(dependencyId);
}
}
});
@@ -107,14 +149,19 @@ 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.
+ * @param sourceFileId ID of the source file.
+ * @param importPath Path of dependency source file.
* @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];
+export function findImportIfExist(
+ contractSources: ContractSources,
+ sourceFileId: string,
+ importPath: string,
+): solc.ImportContents {
+ const dependencyFileId = constructDependencyFileId(importPath, sourceFileId);
+ const source = contractSources[dependencyFileId];
if (_.isUndefined(source)) {
- throw new Error(`Contract source not found for ${fileName}`);
+ throw new Error(`Contract source not found for ${dependencyFileId}`);
}
const importContents: solc.ImportContents = {
contents: source,
diff --git a/packages/deployer/src/utils/contract.ts b/packages/deployer/src/utils/contract.ts
index 9b7baac11..e8dd5218a 100644
--- a/packages/deployer/src/utils/contract.ts
+++ b/packages/deployer/src/utils/contract.ts
@@ -1,11 +1,9 @@
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
-import { ContractAbi, EventAbi, FunctionAbi, MethodAbi, TxData } from '@0xproject/types';
+import { AbiType, ContractAbi, EventAbi, FunctionAbi, MethodAbi, TxData } from '@0xproject/types';
import { promisify } from '@0xproject/utils';
import * as _ from 'lodash';
import * as Web3 from 'web3';
-import { AbiType } from './types';
-
export class Contract implements Web3.ContractInstance {
public address: string;
public abi: ContractAbi;
diff --git a/packages/deployer/src/utils/encoder.ts b/packages/deployer/src/utils/encoder.ts
index 4f62662e1..806efbbca 100644
--- a/packages/deployer/src/utils/encoder.ts
+++ b/packages/deployer/src/utils/encoder.ts
@@ -1,9 +1,7 @@
-import { AbiDefinition, ContractAbi, DataItem } from '@0xproject/types';
+import { AbiDefinition, AbiType, ContractAbi, DataItem } from '@0xproject/types';
import * as _ from 'lodash';
import * as web3Abi from 'web3-eth-abi';
-import { AbiType } from './types';
-
export const encoder = {
encodeConstructorArgsFromAbi(args: any[], abi: ContractAbi): string {
const constructorTypes: string[] = [];
diff --git a/packages/deployer/src/utils/types.ts b/packages/deployer/src/utils/types.ts
index 7d131f5ce..1a866b873 100644
--- a/packages/deployer/src/utils/types.ts
+++ b/packages/deployer/src/utils/types.ts
@@ -18,6 +18,11 @@ export interface ContractNetworks {
[key: number]: ContractNetworkData;
}
+export interface ContractDirectory {
+ path: string;
+ namespace: string;
+}
+
export interface ContractNetworkData {
solc_version: string;
optimizer_enabled: boolean;
@@ -40,7 +45,7 @@ export interface SolcErrors {
export interface CliOptions extends yargs.Arguments {
artifactsDir: string;
- contractsDir: string;
+ contractDirs: string;
jsonrpcUrl: string;
networkId: number;
shouldOptimize: boolean;
@@ -51,7 +56,7 @@ export interface CliOptions extends yargs.Arguments {
}
export interface CompilerOptions {
- contractsDir: string;
+ contractDirs: Set<ContractDirectory>;
networkId: number;
optimizerEnabled: boolean;
artifactsDir: string;
@@ -78,7 +83,11 @@ export interface ContractSources {
[key: string]: string;
}
-export interface ContractSourceData {
+export interface ContractIdToSourceFileId {
+ [key: string]: string;
+}
+
+export interface ContractSourceDataByFileId {
[key: string]: ContractSpecificSourceData;
}
@@ -98,4 +107,8 @@ export interface Token {
swarmHash: string;
}
+export interface FunctionNameToSeenCount {
+ [key: string]: number;
+}
+
export type DoneCallback = (err?: Error) => void;
diff --git a/packages/deployer/test/compiler_test.ts b/packages/deployer/test/compiler_test.ts
index b03ae7935..817a3b3f9 100644
--- a/packages/deployer/test/compiler_test.ts
+++ b/packages/deployer/test/compiler_test.ts
@@ -3,7 +3,13 @@ import 'mocha';
import { Compiler } from '../src/compiler';
import { fsWrapper } from '../src/utils/fs_wrapper';
-import { CompilerOptions, ContractArtifact, ContractNetworkData, DoneCallback } from '../src/utils/types';
+import {
+ CompilerOptions,
+ ContractArtifact,
+ ContractDirectory,
+ ContractNetworkData,
+ DoneCallback,
+} from '../src/utils/types';
import { exchange_binary } from './fixtures/exchange_bin';
import { constants } from './util/constants';
@@ -13,11 +19,15 @@ const expect = chai.expect;
describe('#Compiler', function() {
this.timeout(constants.timeoutMs);
const artifactsDir = `${__dirname}/fixtures/artifacts`;
- const contractsDir = `${__dirname}/fixtures/contracts`;
+ const mainContractDir: ContractDirectory = { path: `${__dirname}/fixtures/contracts/main`, namespace: 'main' };
+ const baseContractDir: ContractDirectory = { path: `${__dirname}/fixtures/contracts/base`, namespace: 'base' };
+ const contractDirs: Set<ContractDirectory> = new Set();
+ contractDirs.add(mainContractDir);
+ contractDirs.add(baseContractDir);
const exchangeArtifactPath = `${artifactsDir}/Exchange.json`;
const compilerOpts: CompilerOptions = {
artifactsDir,
- contractsDir,
+ contractDirs,
networkId: constants.networkId,
optimizerEnabled: constants.optimizerEnabled,
specifiedContracts: new Set(constants.specifiedContracts),
diff --git a/packages/deployer/test/compiler_utils_test.ts b/packages/deployer/test/compiler_utils_test.ts
index 246304858..5377d3308 100644
--- a/packages/deployer/test/compiler_utils_test.ts
+++ b/packages/deployer/test/compiler_utils_test.ts
@@ -47,28 +47,34 @@ describe('Compiler utils', () => {
});
describe('#parseDependencies', () => {
it('correctly parses Exchange dependencies', async () => {
- const exchangeSource = await fsWrapper.readFileAsync(`${__dirname}/fixtures/contracts/Exchange.sol`, {
+ const exchangeSource = await fsWrapper.readFileAsync(`${__dirname}/fixtures/contracts/main/Exchange.sol`, {
encoding: 'utf8',
});
- expect(parseDependencies(exchangeSource)).to.be.deep.equal([
- 'TokenTransferProxy.sol',
- 'Token.sol',
- 'SafeMath.sol',
+ const sourceFileId = '/main/Exchange.sol';
+ expect(parseDependencies(exchangeSource, sourceFileId)).to.be.deep.equal([
+ '/main/TokenTransferProxy.sol',
+ '/base/Token.sol',
+ '/base/SafeMath.sol',
]);
});
it('correctly parses TokenTransferProxy dependencies', async () => {
const exchangeSource = await fsWrapper.readFileAsync(
- `${__dirname}/fixtures/contracts/TokenTransferProxy.sol`,
+ `${__dirname}/fixtures/contracts/main/TokenTransferProxy.sol`,
{
encoding: 'utf8',
},
);
- expect(parseDependencies(exchangeSource)).to.be.deep.equal(['Token.sol', 'Ownable.sol']);
+ const sourceFileId = '/main/TokenTransferProxy.sol';
+ expect(parseDependencies(exchangeSource, sourceFileId)).to.be.deep.equal([
+ '/base/Token.sol',
+ '/base/Ownable.sol',
+ ]);
});
// TODO: For now that doesn't work. This will work after we switch to a grammar-based parser
it.skip('correctly parses commented out dependencies', async () => {
const contractWithCommentedOutDependencies = `// import "./TokenTransferProxy.sol";`;
- expect(parseDependencies(contractWithCommentedOutDependencies)).to.be.deep.equal([]);
+ const sourceFileId = '/main/TokenTransferProxy.sol';
+ expect(parseDependencies(contractWithCommentedOutDependencies, sourceFileId)).to.be.deep.equal([]);
});
});
});
diff --git a/packages/deployer/test/deployer_test.ts b/packages/deployer/test/deployer_test.ts
index 9c34d74aa..a213932f9 100644
--- a/packages/deployer/test/deployer_test.ts
+++ b/packages/deployer/test/deployer_test.ts
@@ -4,20 +4,31 @@ import 'mocha';
import { Compiler } from '../src/compiler';
import { Deployer } from '../src/deployer';
import { fsWrapper } from '../src/utils/fs_wrapper';
-import { CompilerOptions, ContractArtifact, ContractNetworkData, DoneCallback } from '../src/utils/types';
+import {
+ CompilerOptions,
+ ContractArtifact,
+ ContractDirectory,
+ ContractNetworkData,
+ DoneCallback,
+} from '../src/utils/types';
import { constructor_args, exchange_binary } from './fixtures/exchange_bin';
import { constants } from './util/constants';
+import { provider } from './util/provider';
const expect = chai.expect;
describe('#Deployer', () => {
const artifactsDir = `${__dirname}/fixtures/artifacts`;
- const contractsDir = `${__dirname}/fixtures/contracts`;
const exchangeArtifactPath = `${artifactsDir}/Exchange.json`;
+ const mainContractDir: ContractDirectory = { path: `${__dirname}/fixtures/contracts/main`, namespace: '' };
+ const baseContractDir: ContractDirectory = { path: `${__dirname}/fixtures/contracts/base`, namespace: 'base' };
+ const contractDirs: Set<ContractDirectory> = new Set();
+ contractDirs.add(mainContractDir);
+ contractDirs.add(baseContractDir);
const compilerOpts: CompilerOptions = {
artifactsDir,
- contractsDir,
+ contractDirs,
networkId: constants.networkId,
optimizerEnabled: constants.optimizerEnabled,
specifiedContracts: new Set(constants.specifiedContracts),
@@ -26,7 +37,7 @@ describe('#Deployer', () => {
const deployerOpts = {
artifactsDir,
networkId: constants.networkId,
- jsonrpcUrl: constants.jsonrpcUrl,
+ provider,
defaults: {
gasPrice: constants.gasPrice,
},
diff --git a/packages/deployer/test/fixtures/contracts/Exchange.sol b/packages/deployer/test/fixtures/contracts/main/Exchange.sol
index 1b6819700..ea9ca3afa 100644
--- a/packages/deployer/test/fixtures/contracts/Exchange.sol
+++ b/packages/deployer/test/fixtures/contracts/main/Exchange.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,8 +19,8 @@
pragma solidity 0.4.14;
import "./TokenTransferProxy.sol";
-import "./base/Token.sol";
-import "./base/SafeMath.sol";
+import "/base/Token.sol";
+import "/base/SafeMath.sol";
/// @title Exchange - Facilitates exchange of ERC20 tokens.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
diff --git a/packages/deployer/test/fixtures/contracts/TokenTransferProxy.sol b/packages/deployer/test/fixtures/contracts/main/TokenTransferProxy.sol
index 90c8e7d66..99d16cb57 100644
--- a/packages/deployer/test/fixtures/contracts/TokenTransferProxy.sol
+++ b/packages/deployer/test/fixtures/contracts/main/TokenTransferProxy.sol
@@ -1,6 +1,6 @@
/*
- Copyright 2017 ZeroEx Intl.
+ Copyright 2018 ZeroEx Intl.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,8 +18,8 @@
pragma solidity 0.4.14;
-import "./base/Token.sol";
-import "./base/Ownable.sol";
+import "/base/Token.sol";
+import "/base/Ownable.sol";
/// @title TokenTransferProxy - Transfers tokens on behalf of contracts that have been approved via decentralized governance.
/// @author Amir Bandeali - <amir@0xProject.com>, Will Warren - <will@0xProject.com>
diff --git a/packages/deployer/test/util/constants.ts b/packages/deployer/test/util/constants.ts
index 5385b8d17..b93081a80 100644
--- a/packages/deployer/test/util/constants.ts
+++ b/packages/deployer/test/util/constants.ts
@@ -2,7 +2,6 @@ import { BigNumber } from '@0xproject/utils';
export const constants = {
networkId: 0,
- jsonrpcUrl: 'http://localhost:8545',
optimizerEnabled: false,
gasPrice: new BigNumber(20000000000),
timeoutMs: 30000,
diff --git a/packages/deployer/test/util/provider.ts b/packages/deployer/test/util/provider.ts
new file mode 100644
index 000000000..e0fcb362a
--- /dev/null
+++ b/packages/deployer/test/util/provider.ts
@@ -0,0 +1,9 @@
+import { web3Factory } from '@0xproject/dev-utils';
+import { Provider } from '@0xproject/types';
+import * as Web3 from 'web3';
+
+const providerConfigs = { shouldUseInProcessGanache: true };
+const web3Instance = web3Factory.create(providerConfigs);
+const provider: Provider = web3Instance.currentProvider;
+
+export { provider };