aboutsummaryrefslogtreecommitdiffstats
path: root/packages/sol-compiler
diff options
context:
space:
mode:
authorBrandon Millman <brandon.millman@gmail.com>2018-10-05 07:06:05 +0800
committerBrandon Millman <brandon.millman@gmail.com>2018-10-05 07:06:05 +0800
commite5153737d8386380675f28dd7cda70deeb1ea37c (patch)
tree81b061d2fa1af5952acc5abb41003f043ff8fce1 /packages/sol-compiler
parent88766a02c7e6688e72d5c4c69ce68028b322f154 (diff)
parentb04b649ec044b05f5c37bec214b7f992feb5998e (diff)
downloaddexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar
dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.gz
dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.bz2
dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.lz
dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.xz
dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.tar.zst
dexon-0x-contracts-e5153737d8386380675f28dd7cda70deeb1ea37c.zip
Merge branch 'development'
* development: (939 commits) Add asset-buyer to published packages section in README Publish Updated CHANGELOGS Update BuyQuote interface force re-build Add website build to instructions Revert format and re-add changes Build website in parallel with other tests since no other test relies on it being built to run Add back sourceMap support for both dev/prod Upgrade webpack Add missing default options Remove unused constants Add fee order with a takerFee Add additional order factory methods and refactor test to use them Add comments about buy quote calculation Update CHANGELOG Fix linter Add additional test for slippage Add buy_quote_calculator_test Add 0x Instant to bundle analysis ...
Diffstat (limited to 'packages/sol-compiler')
-rw-r--r--packages/sol-compiler/CHANGELOG.json82
-rw-r--r--packages/sol-compiler/CHANGELOG.md34
-rw-r--r--packages/sol-compiler/package.json51
-rw-r--r--packages/sol-compiler/src/compiler.ts395
-rw-r--r--packages/sol-compiler/src/index.ts30
-rw-r--r--packages/sol-compiler/src/monorepo_scripts/postpublish.ts8
-rw-r--r--packages/sol-compiler/src/monorepo_scripts/stage_docs.ts8
-rw-r--r--packages/sol-compiler/src/utils/compiler.ts6
-rw-r--r--packages/sol-compiler/src/utils/fs_wrapper.ts15
-rw-r--r--packages/sol-compiler/src/utils/types.ts46
-rw-r--r--packages/sol-compiler/test/compiler_test.ts90
-rw-r--r--packages/sol-compiler/test/fixtures/contracts/BadContractName.sol3
-rw-r--r--packages/sol-compiler/test/fixtures/contracts/EmptyContract.sol3
-rw-r--r--packages/sol-compiler/test/util/chai_setup.ts13
-rw-r--r--packages/sol-compiler/tsconfig.json1
-rw-r--r--packages/sol-compiler/typedoc-tsconfig.json8
16 files changed, 577 insertions, 216 deletions
diff --git a/packages/sol-compiler/CHANGELOG.json b/packages/sol-compiler/CHANGELOG.json
index a351839a4..3b19a253a 100644
--- a/packages/sol-compiler/CHANGELOG.json
+++ b/packages/sol-compiler/CHANGELOG.json
@@ -1,5 +1,87 @@
[
{
+ "version": "1.1.7",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ],
+ "timestamp": 1538693146
+ },
+ {
+ "timestamp": 1538157789,
+ "version": "1.1.6",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ]
+ },
+ {
+ "timestamp": 1537907159,
+ "version": "1.1.5",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ]
+ },
+ {
+ "timestamp": 1537875740,
+ "version": "1.1.4",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ]
+ },
+ {
+ "timestamp": 1537541580,
+ "version": "1.1.3",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ]
+ },
+ {
+ "timestamp": 1536142250,
+ "version": "1.1.2",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ]
+ },
+ {
+ "timestamp": 1535377027,
+ "version": "1.1.1",
+ "changes": [
+ {
+ "note": "Dependencies updated"
+ }
+ ]
+ },
+ {
+ "version": "1.1.0",
+ "changes": [
+ {
+ "note":
+ "Quicken compilation by sending multiple contracts to the same solcjs invocation, batching them together based on compiler version requirements.",
+ "pr": 965
+ },
+ {
+ "note": "Stop exporting types: `ContractArtifact`, `ContractNetworks`",
+ "pr": 924
+ },
+ {
+ "note": "Export types: `CompilerSettings`, `OutputField`",
+ "pr": 924
+ }
+ ],
+ "timestamp": 1535133899
+ },
+ {
"timestamp": 1534210131,
"version": "1.0.5",
"changes": [
diff --git a/packages/sol-compiler/CHANGELOG.md b/packages/sol-compiler/CHANGELOG.md
index ee9eadeaa..d436462c9 100644
--- a/packages/sol-compiler/CHANGELOG.md
+++ b/packages/sol-compiler/CHANGELOG.md
@@ -5,6 +5,40 @@ Edit the package's CHANGELOG.json file only.
CHANGELOG
+## v1.1.7 - _October 4, 2018_
+
+ * Dependencies updated
+
+## v1.1.6 - _September 28, 2018_
+
+ * Dependencies updated
+
+## v1.1.5 - _September 25, 2018_
+
+ * Dependencies updated
+
+## v1.1.4 - _September 25, 2018_
+
+ * Dependencies updated
+
+## v1.1.3 - _September 21, 2018_
+
+ * Dependencies updated
+
+## v1.1.2 - _September 5, 2018_
+
+ * Dependencies updated
+
+## v1.1.1 - _August 27, 2018_
+
+ * Dependencies updated
+
+## v1.1.0 - _August 24, 2018_
+
+ * Quicken compilation by sending multiple contracts to the same solcjs invocation, batching them together based on compiler version requirements. (#965)
+ * Stop exporting types: `ContractArtifact`, `ContractNetworks` (#924)
+ * Export types: `CompilerSettings`, `OutputField` (#924)
+
## v1.0.5 - _August 13, 2018_
* Dependencies updated
diff --git a/packages/sol-compiler/package.json b/packages/sol-compiler/package.json
index 7cb07e970..9ee88a5ef 100644
--- a/packages/sol-compiler/package.json
+++ b/packages/sol-compiler/package.json
@@ -1,6 +1,6 @@
{
"name": "@0xproject/sol-compiler",
- "version": "1.0.5",
+ "version": "1.1.7",
"engines": {
"node": ">=6.12"
},
@@ -8,8 +8,8 @@
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
- "watch_without_deps": "yarn pre_build && tsc -w",
- "build": "yarn pre_build && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
+ "build": "yarn pre_build && tsc -b",
+ "build:ci": "yarn build",
"pre_build": "run-s update_contract_fixtures",
"update_contract_fixtures": "copyfiles 'test/fixtures/contracts/**/*' ./lib",
"test": "yarn run_mocha",
@@ -17,26 +17,15 @@
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 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",
- "clean": "shx rm -rf lib scripts",
+ "clean": "shx rm -rf lib generated_docs",
"migrate": "npm run build; node lib/src/cli.js migrate",
"lint": "tslint --project .",
"test:circleci": "yarn test:coverage",
- "docs:stage": "node scripts/stage_docs.js",
- "manual:postpublish": "yarn build; node ./scripts/postpublish.js",
- "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
- "upload_docs_json": "aws s3 cp generated_docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type application/json"
+ "docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
},
"config": {
"postpublish": {
- "assets": [],
- "docPublishConfigs": {
- "extraFileIncludes": [
- "../types/src/index.ts",
- "../ethereum-types/src/index.ts"
- ],
- "s3BucketPath": "s3://doc-jsons/sol-compiler/",
- "s3StagingBucketPath": "s3://staging-doc-jsons/sol-compiler/"
- }
+ "assets": []
}
},
"bin": {
@@ -53,15 +42,15 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-compiler/README.md",
"devDependencies": {
- "@0xproject/dev-utils": "^1.0.4",
- "@0xproject/monorepo-scripts": "^1.0.5",
- "@0xproject/tslint-config": "^1.0.5",
+ "@0xproject/dev-utils": "^1.0.12",
+ "@0xproject/tslint-config": "^1.0.8",
"@types/mkdirp": "^0.5.2",
"@types/require-from-string": "^1.2.0",
"@types/semver": "^5.5.0",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
- "copyfiles": "^1.2.0",
+ "chai-bignumber": "^2.0.2",
+ "copyfiles": "^2.0.0",
"dirty-chai": "^2.0.1",
"make-promises-safe": "^1.1.0",
"mocha": "^4.1.0",
@@ -69,23 +58,23 @@
"nyc": "^11.0.1",
"shx": "^0.2.2",
"tslint": "5.11.0",
- "typedoc": "0xProject/typedoc",
+ "typedoc": "0.12.0",
"types-bn": "^0.0.1",
- "typescript": "2.9.2",
+ "typescript": "3.0.1",
"web3-typescript-typings": "^0.10.2",
"zeppelin-solidity": "1.8.0"
},
"dependencies": {
- "@0xproject/assert": "^1.0.5",
- "@0xproject/json-schemas": "^1.0.1-rc.4",
- "@0xproject/sol-resolver": "^1.0.5",
- "@0xproject/types": "^1.0.1-rc.4",
- "@0xproject/typescript-typings": "^1.0.4",
- "@0xproject/utils": "^1.0.5",
- "@0xproject/web3-wrapper": "^1.2.0",
+ "@0xproject/assert": "^1.0.13",
+ "@0xproject/json-schemas": "^1.0.7",
+ "@0xproject/sol-resolver": "^1.0.14",
+ "@0xproject/types": "^1.1.4",
+ "@0xproject/typescript-typings": "^3.0.2",
+ "@0xproject/utils": "^2.0.2",
+ "@0xproject/web3-wrapper": "^3.0.3",
"@types/yargs": "^11.0.0",
"chalk": "^2.3.0",
- "ethereum-types": "^1.0.4",
+ "ethereum-types": "^1.0.11",
"ethereumjs-util": "^5.1.1",
"lodash": "^4.17.5",
"mkdirp": "^0.5.1",
diff --git a/packages/sol-compiler/src/compiler.ts b/packages/sol-compiler/src/compiler.ts
index 3620a3ec1..7eefc1474 100644
--- a/packages/sol-compiler/src/compiler.ts
+++ b/packages/sol-compiler/src/compiler.ts
@@ -10,6 +10,7 @@ import {
} from '@0xproject/sol-resolver';
import { fetchAsync, logUtils } from '@0xproject/utils';
import chalk from 'chalk';
+import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as fs from 'fs';
import * as _ from 'lodash';
@@ -29,7 +30,6 @@ import {
} from './utils/compiler';
import { constants } from './utils/constants';
import { fsWrapper } from './utils/fs_wrapper';
-import { CompilerOptions, ContractArtifact, ContractVersionData } from './utils/types';
import { utils } from './utils/utils';
type TYPE_ALL_FILES_IDENTIFIER = '*';
@@ -53,6 +53,23 @@ const DEFAULT_COMPILER_SETTINGS: solc.CompilerSettings = {
};
const CONFIG_FILE = 'compiler.json';
+interface VersionToInputs {
+ [solcVersion: string]: {
+ standardInput: solc.StandardInput;
+ contractsToCompile: string[];
+ };
+}
+
+interface ContractPathToData {
+ [contractPath: string]: ContractData;
+}
+
+interface ContractData {
+ currentArtifactIfExists: ContractArtifact | void;
+ sourceTreeHashHex: string;
+ contractName: string;
+}
+
/**
* The Compiler facilitates compiling Solidity smart contracts and saves the results
* to artifact files.
@@ -65,8 +82,52 @@ export class Compiler {
private readonly _artifactsDir: string;
private readonly _solcVersionIfExists: string | undefined;
private readonly _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
+ private static async _getSolcAsync(
+ solcVersion: string,
+ ): Promise<{ solcInstance: solc.SolcInstance; fullSolcVersion: string }> {
+ const fullSolcVersion = binPaths[solcVersion];
+ if (_.isUndefined(fullSolcVersion)) {
+ throw new Error(`${solcVersion} is not a known compiler version`);
+ }
+ const compilerBinFilename = path.join(SOLC_BIN_DIR, fullSolcVersion);
+ let solcjs: string;
+ if (await fsWrapper.doesFileExistAsync(compilerBinFilename)) {
+ solcjs = (await fsWrapper.readFileAsync(compilerBinFilename)).toString();
+ } else {
+ logUtils.warn(`Downloading ${fullSolcVersion}...`);
+ const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`;
+ const response = await fetchAsync(url);
+ const SUCCESS_STATUS = 200;
+ if (response.status !== SUCCESS_STATUS) {
+ throw new Error(`Failed to load ${fullSolcVersion}`);
+ }
+ solcjs = await response.text();
+ await fsWrapper.writeFileAsync(compilerBinFilename, solcjs);
+ }
+ if (solcjs.length === 0) {
+ throw new Error('No compiler available');
+ }
+ const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename));
+ return { solcInstance, fullSolcVersion };
+ }
+ private static _addHexPrefixToContractBytecode(compiledContract: solc.StandardContractOutput): void {
+ if (!_.isUndefined(compiledContract.evm)) {
+ if (!_.isUndefined(compiledContract.evm.bytecode) && !_.isUndefined(compiledContract.evm.bytecode.object)) {
+ compiledContract.evm.bytecode.object = ethUtil.addHexPrefix(compiledContract.evm.bytecode.object);
+ }
+ if (
+ !_.isUndefined(compiledContract.evm.deployedBytecode) &&
+ !_.isUndefined(compiledContract.evm.deployedBytecode.object)
+ ) {
+ compiledContract.evm.deployedBytecode.object = ethUtil.addHexPrefix(
+ compiledContract.evm.deployedBytecode.object,
+ );
+ }
+ }
+ }
/**
* Instantiates a new instance of the Compiler class.
+ * @param opts Optional compiler options
* @return An instance of the Compiler class.
*/
constructor(opts?: CompilerOptions) {
@@ -98,129 +159,153 @@ export class Compiler {
public async compileAsync(): Promise<void> {
await createDirIfDoesNotExistAsync(this._artifactsDir);
await createDirIfDoesNotExistAsync(SOLC_BIN_DIR);
- let contractNamesToCompile: string[] = [];
+ await this._compileContractsAsync(this._getContractNamesToCompile(), true);
+ }
+ /**
+ * Compiles Solidity files specified during instantiation, and returns the
+ * compiler output given by solc. Return value is an array of outputs:
+ * Solidity modules are batched together by version required, and each
+ * element of the returned array corresponds to a compiler version, and
+ * each element contains the output for all of the modules compiled with
+ * that version.
+ */
+ public async getCompilerOutputsAsync(): Promise<StandardOutput[]> {
+ const promisedOutputs = this._compileContractsAsync(this._getContractNamesToCompile(), false);
+ return promisedOutputs;
+ }
+ private _getContractNamesToCompile(): string[] {
+ let contractNamesToCompile;
if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) {
const allContracts = this._nameResolver.getAll();
contractNamesToCompile = _.map(allContracts, contractSource =>
path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION),
);
} else {
- contractNamesToCompile = this._specifiedContracts;
- }
- for (const contractNameToCompile of contractNamesToCompile) {
- await this._compileContractAsync(contractNameToCompile);
+ contractNamesToCompile = this._specifiedContracts.map(specifiedContract =>
+ path.basename(specifiedContract, constants.SOLIDITY_FILE_EXTENSION),
+ );
}
+ return contractNamesToCompile;
}
/**
- * Compiles contract and saves artifact to artifactsDir.
+ * Compiles contracts, and, if `shouldPersist` is true, saves artifacts to artifactsDir.
* @param fileName Name of contract with '.sol' extension.
+ * @return an array of compiler outputs, where each element corresponds to a different version of solc-js.
*/
- private async _compileContractAsync(contractName: string): Promise<void> {
- const contractSource = this._resolver.resolve(contractName);
- const absoluteContractPath = path.join(this._contractsDir, contractSource.path);
- const currentArtifactIfExists = await getContractArtifactIfExistsAsync(this._artifactsDir, contractName);
- const sourceTreeHashHex = `0x${this._getSourceTreeHash(absoluteContractPath).toString('hex')}`;
- let shouldCompile = false;
- if (_.isUndefined(currentArtifactIfExists)) {
- shouldCompile = true;
- } else {
- const currentArtifact = currentArtifactIfExists as ContractArtifact;
- const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
- const didCompilerSettingsChange = !_.isEqual(currentArtifact.compiler.settings, this._compilerSettings);
- const didSourceChange = currentArtifact.sourceTreeHashHex !== sourceTreeHashHex;
- shouldCompile = !isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange;
- }
- if (!shouldCompile) {
- return;
- }
- let solcVersion = this._solcVersionIfExists;
- if (_.isUndefined(solcVersion)) {
- const solcVersionRange = parseSolidityVersionRange(contractSource.source);
- const availableCompilerVersions = _.keys(binPaths);
- solcVersion = semver.maxSatisfying(availableCompilerVersions, solcVersionRange);
- }
- const fullSolcVersion = binPaths[solcVersion];
- const compilerBinFilename = path.join(SOLC_BIN_DIR, fullSolcVersion);
- let solcjs: string;
- const isCompilerAvailableLocally = fs.existsSync(compilerBinFilename);
- if (isCompilerAvailableLocally) {
- solcjs = fs.readFileSync(compilerBinFilename).toString();
- } else {
- logUtils.log(`Downloading ${fullSolcVersion}...`);
- const url = `${constants.BASE_COMPILER_URL}${fullSolcVersion}`;
- const response = await fetchAsync(url);
- const SUCCESS_STATUS = 200;
- if (response.status !== SUCCESS_STATUS) {
- throw new Error(`Failed to load ${fullSolcVersion}`);
- }
- solcjs = await response.text();
- fs.writeFileSync(compilerBinFilename, solcjs);
- }
- const solcInstance = solc.setupMethods(requireFromString(solcjs, compilerBinFilename));
+ private async _compileContractsAsync(contractNames: string[], shouldPersist: boolean): Promise<StandardOutput[]> {
+ // batch input contracts together based on the version of the compiler that they require.
+ const versionToInputs: VersionToInputs = {};
- logUtils.log(`Compiling ${contractName} with Solidity v${solcVersion}...`);
- const standardInput: solc.StandardInput = {
- language: 'Solidity',
- sources: {
- [contractSource.path]: {
- content: contractSource.source,
- },
- },
- settings: this._compilerSettings,
- };
- const compiled: solc.StandardOutput = JSON.parse(
- solcInstance.compileStandardWrapper(JSON.stringify(standardInput), importPath => {
- const sourceCodeIfExists = this._resolver.resolve(importPath);
- return { contents: sourceCodeIfExists.source };
- }),
- );
+ // map contract paths to data about them for later verification and persistence
+ const contractPathToData: ContractPathToData = {};
- if (!_.isUndefined(compiled.errors)) {
- const SOLIDITY_WARNING = 'warning';
- const errors = _.filter(compiled.errors, entry => entry.severity !== SOLIDITY_WARNING);
- const warnings = _.filter(compiled.errors, entry => entry.severity === SOLIDITY_WARNING);
- if (!_.isEmpty(errors)) {
- errors.forEach(error => {
- const normalizedErrMsg = getNormalizedErrMsg(error.formattedMessage || error.message);
- logUtils.log(chalk.red(normalizedErrMsg));
- });
- process.exit(1);
- } else {
- warnings.forEach(warning => {
- const normalizedWarningMsg = getNormalizedErrMsg(warning.formattedMessage || warning.message);
- logUtils.log(chalk.yellow(normalizedWarningMsg));
- });
+ for (const contractName of contractNames) {
+ const contractSource = this._resolver.resolve(contractName);
+ const contractData = {
+ contractName,
+ currentArtifactIfExists: await getContractArtifactIfExistsAsync(this._artifactsDir, contractName),
+ sourceTreeHashHex: `0x${this._getSourceTreeHash(
+ path.join(this._contractsDir, contractSource.path),
+ ).toString('hex')}`,
+ };
+ if (!this._shouldCompile(contractData)) {
+ continue;
}
+ contractPathToData[contractSource.path] = contractData;
+ const solcVersion = _.isUndefined(this._solcVersionIfExists)
+ ? semver.maxSatisfying(_.keys(binPaths), parseSolidityVersionRange(contractSource.source))
+ : this._solcVersionIfExists;
+ const isFirstContractWithThisVersion = _.isUndefined(versionToInputs[solcVersion]);
+ if (isFirstContractWithThisVersion) {
+ versionToInputs[solcVersion] = {
+ standardInput: {
+ language: 'Solidity',
+ sources: {},
+ settings: this._compilerSettings,
+ },
+ contractsToCompile: [],
+ };
+ }
+ // add input to the right version batch
+ versionToInputs[solcVersion].standardInput.sources[contractSource.path] = {
+ content: contractSource.source,
+ };
+ versionToInputs[solcVersion].contractsToCompile.push(contractSource.path);
}
- const compiledData = compiled.contracts[contractSource.path][contractName];
- if (_.isUndefined(compiledData)) {
- throw new Error(
- `Contract ${contractName} not found in ${
- contractSource.path
- }. Please make sure your contract has the same name as it's file name`,
+
+ const compilerOutputs: StandardOutput[] = [];
+
+ const solcVersions = _.keys(versionToInputs);
+ for (const solcVersion of solcVersions) {
+ const input = versionToInputs[solcVersion];
+ logUtils.warn(
+ `Compiling ${input.contractsToCompile.length} contracts (${
+ input.contractsToCompile
+ }) with Solidity v${solcVersion}...`,
);
- }
- if (!_.isUndefined(compiledData.evm)) {
- if (!_.isUndefined(compiledData.evm.bytecode) && !_.isUndefined(compiledData.evm.bytecode.object)) {
- compiledData.evm.bytecode.object = ethUtil.addHexPrefix(compiledData.evm.bytecode.object);
- }
- if (
- !_.isUndefined(compiledData.evm.deployedBytecode) &&
- !_.isUndefined(compiledData.evm.deployedBytecode.object)
- ) {
- compiledData.evm.deployedBytecode.object = ethUtil.addHexPrefix(
- compiledData.evm.deployedBytecode.object,
- );
+
+ const { solcInstance, fullSolcVersion } = await Compiler._getSolcAsync(solcVersion);
+
+ const compilerOutput = this._compile(solcInstance, input.standardInput);
+ compilerOutputs.push(compilerOutput);
+
+ for (const contractPath of input.contractsToCompile) {
+ const contractName = contractPathToData[contractPath].contractName;
+
+ const compiledContract = compilerOutput.contracts[contractPath][contractName];
+ if (_.isUndefined(compiledContract)) {
+ throw new Error(
+ `Contract ${contractName} not found in ${contractPath}. Please make sure your contract has the same name as it's file name`,
+ );
+ }
+
+ Compiler._addHexPrefixToContractBytecode(compiledContract);
+
+ if (shouldPersist) {
+ await this._persistCompiledContractAsync(
+ contractPath,
+ contractPathToData[contractPath].currentArtifactIfExists,
+ contractPathToData[contractPath].sourceTreeHashHex,
+ contractName,
+ fullSolcVersion,
+ compilerOutput,
+ );
+ }
}
}
- const sourceCodes = _.mapValues(
- compiled.sources,
- (_1, sourceFilePath) => this._resolver.resolve(sourceFilePath).source,
- );
+ return compilerOutputs;
+ }
+ private _shouldCompile(contractData: ContractData): boolean {
+ if (_.isUndefined(contractData.currentArtifactIfExists)) {
+ return true;
+ } else {
+ const currentArtifact = contractData.currentArtifactIfExists as ContractArtifact;
+ const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
+ const didCompilerSettingsChange = !_.isEqual(currentArtifact.compiler.settings, this._compilerSettings);
+ const didSourceChange = currentArtifact.sourceTreeHashHex !== contractData.sourceTreeHashHex;
+ return !isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange;
+ }
+ }
+ private async _persistCompiledContractAsync(
+ contractPath: string,
+ currentArtifactIfExists: ContractArtifact | void,
+ sourceTreeHashHex: string,
+ contractName: string,
+ fullSolcVersion: string,
+ compilerOutput: solc.StandardOutput,
+ ): Promise<void> {
+ const compiledContract = compilerOutput.contracts[contractPath][contractName];
+
+ // need to gather sourceCodes for this artifact, but compilerOutput.sources (the list of contract modules)
+ // contains listings for for every contract compiled during the compiler invocation that compiled the contract
+ // to be persisted, which could include many that are irrelevant to the contract at hand. So, gather up only
+ // the relevant sources:
+ const { sourceCodes, sources } = this._getSourcesWithDependencies(contractPath, compilerOutput.sources);
+
const contractVersion: ContractVersionData = {
- compilerOutput: compiledData,
- sources: compiled.sources,
+ compilerOutput: compiledContract,
+ sources,
sourceCodes,
sourceTreeHashHex,
compiler: {
@@ -249,7 +334,107 @@ export class Compiler {
const artifactString = utils.stringifyWithFormatting(newArtifact);
const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
- logUtils.log(`${contractName} artifact saved!`);
+ logUtils.warn(`${contractName} artifact saved!`);
+ }
+ /**
+ * For the given @param contractPath, populates JSON objects to be used in the ContractVersionData interface's
+ * properties `sources` (source code file names mapped to ID numbers) and `sourceCodes` (source code content of
+ * contracts) for that contract. The source code pointed to by contractPath is read and parsed directly (via
+ * `this._resolver.resolve().source`), as are its imports, recursively. The ID numbers for @return `sources` are
+ * taken from the corresponding ID's in @param fullSources, and the content for @return sourceCodes is read from
+ * disk (via the aforementioned `resolver.source`).
+ */
+ private _getSourcesWithDependencies(
+ contractPath: string,
+ fullSources: { [sourceName: string]: { id: number } },
+ ): { sourceCodes: { [sourceName: string]: string }; sources: { [sourceName: string]: { id: number } } } {
+ const sources = { [contractPath]: { id: fullSources[contractPath].id } };
+ const sourceCodes = { [contractPath]: this._resolver.resolve(contractPath).source };
+ this._recursivelyGatherDependencySources(
+ contractPath,
+ sourceCodes[contractPath],
+ fullSources,
+ sources,
+ sourceCodes,
+ );
+ return { sourceCodes, sources };
+ }
+ private _recursivelyGatherDependencySources(
+ contractPath: string,
+ contractSource: string,
+ fullSources: { [sourceName: string]: { id: number } },
+ sourcesToAppendTo: { [sourceName: string]: { id: number } },
+ sourceCodesToAppendTo: { [sourceName: string]: string },
+ ): void {
+ const importStatementMatches = contractSource.match(/\nimport[^;]*;/g);
+ if (importStatementMatches === null) {
+ return;
+ }
+ for (const importStatementMatch of importStatementMatches) {
+ const importPathMatches = importStatementMatch.match(/\"([^\"]*)\"/);
+ if (importPathMatches === null || importPathMatches.length === 0) {
+ continue;
+ }
+
+ let importPath = importPathMatches[1];
+ // HACK(ablrow): We have, e.g.:
+ //
+ // importPath = "../../utils/LibBytes/LibBytes.sol"
+ // contractPath = "2.0.0/protocol/AssetProxyOwner/AssetProxyOwner.sol"
+ //
+ // Resolver doesn't understand "../" so we want to pass
+ // "2.0.0/utils/LibBytes/LibBytes.sol" to resolver.
+ //
+ // This hack involves using path.resolve. But path.resolve returns
+ // absolute directories by default. We trick it into thinking that
+ // contractPath is a root directory by prepending a '/' and then
+ // removing the '/' the end.
+ //
+ // path.resolve("/a/b/c", ""../../d/e") === "/a/d/e"
+ //
+ const lastPathSeparatorPos = contractPath.lastIndexOf('/');
+ const contractFolder = lastPathSeparatorPos === -1 ? '' : contractPath.slice(0, lastPathSeparatorPos + 1);
+ importPath = path.resolve('/' + contractFolder, importPath).replace('/', '');
+
+ if (_.isUndefined(sourcesToAppendTo[importPath])) {
+ sourcesToAppendTo[importPath] = { id: fullSources[importPath].id };
+ sourceCodesToAppendTo[importPath] = this._resolver.resolve(importPath).source;
+
+ this._recursivelyGatherDependencySources(
+ importPath,
+ this._resolver.resolve(importPath).source,
+ fullSources,
+ sourcesToAppendTo,
+ sourceCodesToAppendTo,
+ );
+ }
+ }
+ }
+ private _compile(solcInstance: solc.SolcInstance, standardInput: solc.StandardInput): solc.StandardOutput {
+ const compiled: solc.StandardOutput = JSON.parse(
+ solcInstance.compileStandardWrapper(JSON.stringify(standardInput), importPath => {
+ const sourceCodeIfExists = this._resolver.resolve(importPath);
+ return { contents: sourceCodeIfExists.source };
+ }),
+ );
+ if (!_.isUndefined(compiled.errors)) {
+ const SOLIDITY_WARNING = 'warning';
+ const errors = _.filter(compiled.errors, entry => entry.severity !== SOLIDITY_WARNING);
+ const warnings = _.filter(compiled.errors, entry => entry.severity === SOLIDITY_WARNING);
+ if (!_.isEmpty(errors)) {
+ errors.forEach(error => {
+ const normalizedErrMsg = getNormalizedErrMsg(error.formattedMessage || error.message);
+ logUtils.warn(chalk.red(normalizedErrMsg));
+ });
+ throw new Error('Compilation errors encountered');
+ } else {
+ warnings.forEach(warning => {
+ const normalizedWarningMsg = getNormalizedErrMsg(warning.formattedMessage || warning.message);
+ logUtils.warn(chalk.yellow(normalizedWarningMsg));
+ });
+ }
+ }
+ return compiled;
}
/**
* Gets the source tree hash for a file and its dependencies.
diff --git a/packages/sol-compiler/src/index.ts b/packages/sol-compiler/src/index.ts
index 15c166992..d8a60666f 100644
--- a/packages/sol-compiler/src/index.ts
+++ b/packages/sol-compiler/src/index.ts
@@ -1,3 +1,29 @@
export { Compiler } from './compiler';
-export { CompilerOptions } from './utils/types';
-export { ContractArtifact, ContractNetworks } from './utils/types';
+export {
+ AbiDefinition,
+ CompilerOptions,
+ CompilerSettings,
+ DataItem,
+ DevdocOutput,
+ ErrorSeverity,
+ ErrorType,
+ EventAbi,
+ EventParameter,
+ EvmBytecodeOutput,
+ EvmOutput,
+ FallbackAbi,
+ FunctionAbi,
+ MethodAbi,
+ ConstructorAbi,
+ ConstructorStateMutability,
+ ContractAbi,
+ OutputField,
+ CompilerSettingsMetadata,
+ OptimizerSettings,
+ ParamDescription,
+ SolcError,
+ StandardContractOutput,
+ StandardOutput,
+ StateMutability,
+ SourceLocation,
+} from 'ethereum-types';
diff --git a/packages/sol-compiler/src/monorepo_scripts/postpublish.ts b/packages/sol-compiler/src/monorepo_scripts/postpublish.ts
deleted file mode 100644
index dcb99d0f7..000000000
--- a/packages/sol-compiler/src/monorepo_scripts/postpublish.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { postpublishUtils } from '@0xproject/monorepo-scripts';
-
-import * as packageJSON from '../package.json';
-import * as tsConfigJSON from '../tsconfig.json';
-
-const cwd = `${__dirname}/..`;
-// tslint:disable-next-line:no-floating-promises
-postpublishUtils.runAsync(packageJSON, tsConfigJSON, cwd);
diff --git a/packages/sol-compiler/src/monorepo_scripts/stage_docs.ts b/packages/sol-compiler/src/monorepo_scripts/stage_docs.ts
deleted file mode 100644
index e732ac8eb..000000000
--- a/packages/sol-compiler/src/monorepo_scripts/stage_docs.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { postpublishUtils } from '@0xproject/monorepo-scripts';
-
-import * as packageJSON from '../package.json';
-import * as tsConfigJSON from '../tsconfig.json';
-
-const cwd = `${__dirname}/..`;
-// tslint:disable-next-line:no-floating-promises
-postpublishUtils.publishDocsToStagingAsync(packageJSON, tsConfigJSON, cwd);
diff --git a/packages/sol-compiler/src/utils/compiler.ts b/packages/sol-compiler/src/utils/compiler.ts
index 968fcc5b2..c153beb0f 100644
--- a/packages/sol-compiler/src/utils/compiler.ts
+++ b/packages/sol-compiler/src/utils/compiler.ts
@@ -1,10 +1,10 @@
import { ContractSource } from '@0xproject/sol-resolver';
import { logUtils } from '@0xproject/utils';
+import { ContractArtifact } from 'ethereum-types';
import * as _ from 'lodash';
import * as path from 'path';
import { fsWrapper } from './fs_wrapper';
-import { ContractArtifact } from './types';
/**
* Gets contract data on network or returns if an artifact does not exist.
@@ -26,7 +26,7 @@ export async function getContractArtifactIfExistsAsync(
contractArtifact = JSON.parse(contractArtifactString);
return contractArtifact;
} catch (err) {
- logUtils.log(`Artifact for ${contractName} does not exist`);
+ logUtils.warn(`Artifact for ${contractName} does not exist`);
return undefined;
}
}
@@ -37,7 +37,7 @@ export async function getContractArtifactIfExistsAsync(
*/
export async function createDirIfDoesNotExistAsync(dirPath: string): Promise<void> {
if (!fsWrapper.doesPathExistSync(dirPath)) {
- logUtils.log(`Creating directory at ${dirPath}...`);
+ logUtils.warn(`Creating directory at ${dirPath}...`);
await fsWrapper.mkdirpAsync(dirPath);
}
}
diff --git a/packages/sol-compiler/src/utils/fs_wrapper.ts b/packages/sol-compiler/src/utils/fs_wrapper.ts
index cc7b06175..8d6800276 100644
--- a/packages/sol-compiler/src/utils/fs_wrapper.ts
+++ b/packages/sol-compiler/src/utils/fs_wrapper.ts
@@ -10,4 +10,19 @@ export const fsWrapper = {
doesPathExistSync: fs.existsSync,
rmdirSync: fs.rmdirSync,
removeFileAsync: promisify<undefined>(fs.unlink),
+ statAsync: promisify<fs.Stats>(fs.stat),
+ appendFileAsync: promisify<undefined>(fs.appendFile),
+ accessAsync: promisify<boolean>(fs.access),
+ doesFileExistAsync: async (filePath: string): Promise<boolean> => {
+ try {
+ await fsWrapper.accessAsync(
+ filePath,
+ // node says we need to use bitwise, but tslint says no:
+ fs.constants.F_OK | fs.constants.R_OK, // tslint:disable-line:no-bitwise
+ );
+ } catch (err) {
+ return false;
+ }
+ return true;
+ },
};
diff --git a/packages/sol-compiler/src/utils/types.ts b/packages/sol-compiler/src/utils/types.ts
index 4321a2235..b211cfcbc 100644
--- a/packages/sol-compiler/src/utils/types.ts
+++ b/packages/sol-compiler/src/utils/types.ts
@@ -1,5 +1,3 @@
-import * as solc from 'solc';
-
export enum AbiType {
Function = 'function',
Constructor = 'constructor',
@@ -7,54 +5,10 @@ export enum AbiType {
Fallback = 'fallback',
}
-export interface ContractArtifact extends ContractVersionData {
- schemaVersion: string;
- contractName: string;
- networks: ContractNetworks;
-}
-
-export interface ContractVersionData {
- compiler: {
- name: 'solc';
- version: string;
- settings: solc.CompilerSettings;
- };
- sources: {
- [sourceName: string]: {
- id: number;
- };
- };
- sourceCodes: {
- [sourceName: string]: string;
- };
- sourceTreeHashHex: string;
- compilerOutput: solc.StandardContractOutput;
-}
-
-export interface ContractNetworks {
- [networkId: number]: ContractNetworkData;
-}
-
-export interface ContractNetworkData {
- address: string;
- links: {
- [linkName: string]: string;
- };
- constructorArgs: string;
-}
-
export interface SolcErrors {
[key: string]: boolean;
}
-export interface CompilerOptions {
- contractsDir?: string;
- artifactsDir?: string;
- compilerSettings?: solc.CompilerSettings;
- contracts?: string[] | '*';
- solcVersion?: string;
-}
-
export interface ContractSourceData {
[contractName: string]: ContractSpecificSourceData;
}
diff --git a/packages/sol-compiler/test/compiler_test.ts b/packages/sol-compiler/test/compiler_test.ts
index c9e141ee9..464aa8bb6 100644
--- a/packages/sol-compiler/test/compiler_test.ts
+++ b/packages/sol-compiler/test/compiler_test.ts
@@ -1,37 +1,38 @@
-import { DoneCallback } from '@0xproject/types';
+import { join } from 'path';
+
import * as chai from 'chai';
+import { CompilerOptions, ContractArtifact } from 'ethereum-types';
import 'mocha';
import { Compiler } from '../src/compiler';
import { fsWrapper } from '../src/utils/fs_wrapper';
-import { CompilerOptions, ContractArtifact } from '../src/utils/types';
import { exchange_binary } from './fixtures/exchange_bin';
+import { chaiSetup } from './util/chai_setup';
import { constants } from './util/constants';
+chaiSetup.configure();
const expect = chai.expect;
describe('#Compiler', function(): void {
this.timeout(constants.timeoutMs); // tslint:disable-line:no-invalid-this
const artifactsDir = `${__dirname}/fixtures/artifacts`;
const contractsDir = `${__dirname}/fixtures/contracts`;
- const exchangeArtifactPath = `${artifactsDir}/Exchange.json`;
const compilerOpts: CompilerOptions = {
artifactsDir,
contractsDir,
contracts: constants.contracts,
};
- const compiler = new Compiler(compilerOpts);
- beforeEach((done: DoneCallback) => {
- (async () => {
- if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) {
- await fsWrapper.removeFileAsync(exchangeArtifactPath);
- }
- await compiler.compileAsync();
- done();
- })().catch(done);
- });
it('should create an Exchange artifact with the correct unlinked binary', async () => {
+ compilerOpts.contracts = ['Exchange'];
+
+ const exchangeArtifactPath = `${artifactsDir}/Exchange.json`;
+ if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) {
+ await fsWrapper.removeFileAsync(exchangeArtifactPath);
+ }
+
+ await new Compiler(compilerOpts).compileAsync();
+
const opts = {
encoding: 'utf8',
};
@@ -47,4 +48,67 @@ describe('#Compiler', function(): void {
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -metadataHexLength);
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
});
+ it("should throw when Whatever.sol doesn't contain a Whatever contract", async () => {
+ const contract = 'BadContractName';
+
+ const exchangeArtifactPath = `${artifactsDir}/${contract}.json`;
+ if (fsWrapper.doesPathExistSync(exchangeArtifactPath)) {
+ await fsWrapper.removeFileAsync(exchangeArtifactPath);
+ }
+
+ compilerOpts.contracts = [contract];
+ const compiler = new Compiler(compilerOpts);
+
+ expect(compiler.compileAsync()).to.be.rejected();
+ });
+ describe('after a successful compilation', () => {
+ const contract = 'Exchange';
+ let artifactPath: string;
+ let artifactCreatedAtMs: number;
+ beforeEach(async () => {
+ compilerOpts.contracts = [contract];
+
+ artifactPath = `${artifactsDir}/${contract}.json`;
+ if (fsWrapper.doesPathExistSync(artifactPath)) {
+ await fsWrapper.removeFileAsync(artifactPath);
+ }
+
+ await new Compiler(compilerOpts).compileAsync();
+
+ artifactCreatedAtMs = (await fsWrapper.statAsync(artifactPath)).mtimeMs;
+ });
+ it('recompilation should update artifact when source has changed', async () => {
+ // append some meaningless data to the contract, so that its hash
+ // will change, so that the compiler will decide to recompile it.
+ await fsWrapper.appendFileAsync(join(contractsDir, `${contract}.sol`), ' ');
+
+ await new Compiler(compilerOpts).compileAsync();
+
+ const artifactModifiedAtMs = (await fsWrapper.statAsync(artifactPath)).mtimeMs;
+
+ expect(artifactModifiedAtMs).to.be.greaterThan(artifactCreatedAtMs);
+ });
+ it("recompilation should NOT update artifact when source hasn't changed", async () => {
+ await new Compiler(compilerOpts).compileAsync();
+
+ const artifactModifiedAtMs = (await fsWrapper.statAsync(artifactPath)).mtimeMs;
+
+ expect(artifactModifiedAtMs).to.equal(artifactCreatedAtMs);
+ });
+ });
+ it('should only compile what was requested', async () => {
+ // remove all artifacts
+ for (const artifact of await fsWrapper.readdirAsync(artifactsDir)) {
+ await fsWrapper.removeFileAsync(join(artifactsDir, artifact));
+ }
+
+ // compile EmptyContract
+ compilerOpts.contracts = ['EmptyContract'];
+ await new Compiler(compilerOpts).compileAsync();
+
+ // make sure the artifacts dir only contains EmptyContract.json
+ for (const artifact of await fsWrapper.readdirAsync(artifactsDir)) {
+ expect(artifact).to.equal('EmptyContract.json');
+ }
+ });
});
diff --git a/packages/sol-compiler/test/fixtures/contracts/BadContractName.sol b/packages/sol-compiler/test/fixtures/contracts/BadContractName.sol
new file mode 100644
index 000000000..3193cc0eb
--- /dev/null
+++ b/packages/sol-compiler/test/fixtures/contracts/BadContractName.sol
@@ -0,0 +1,3 @@
+pragma solidity ^0.4.14;
+
+contract ContractNameThatDoesntMatchFilename { }
diff --git a/packages/sol-compiler/test/fixtures/contracts/EmptyContract.sol b/packages/sol-compiler/test/fixtures/contracts/EmptyContract.sol
new file mode 100644
index 000000000..971ca7826
--- /dev/null
+++ b/packages/sol-compiler/test/fixtures/contracts/EmptyContract.sol
@@ -0,0 +1,3 @@
+pragma solidity ^0.4.14;
+
+contract EmptyContract { }
diff --git a/packages/sol-compiler/test/util/chai_setup.ts b/packages/sol-compiler/test/util/chai_setup.ts
new file mode 100644
index 000000000..1a8733093
--- /dev/null
+++ b/packages/sol-compiler/test/util/chai_setup.ts
@@ -0,0 +1,13 @@
+import * as chai from 'chai';
+import chaiAsPromised = require('chai-as-promised');
+import ChaiBigNumber = require('chai-bignumber');
+import * as dirtyChai from 'dirty-chai';
+
+export const chaiSetup = {
+ configure(): void {
+ chai.config.includeStack = true;
+ chai.use(ChaiBigNumber());
+ chai.use(dirtyChai);
+ chai.use(chaiAsPromised);
+ },
+};
diff --git a/packages/sol-compiler/tsconfig.json b/packages/sol-compiler/tsconfig.json
index 63cbc75c3..c6ffbb99b 100644
--- a/packages/sol-compiler/tsconfig.json
+++ b/packages/sol-compiler/tsconfig.json
@@ -2,6 +2,7 @@
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib",
+ "rootDir": ".",
"strictFunctionTypes": false
},
"include": ["./src/**/*", "./test/**/*"]
diff --git a/packages/sol-compiler/typedoc-tsconfig.json b/packages/sol-compiler/typedoc-tsconfig.json
new file mode 100644
index 000000000..22897c131
--- /dev/null
+++ b/packages/sol-compiler/typedoc-tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../typedoc-tsconfig",
+ "compilerOptions": {
+ "outDir": "lib",
+ "strictFunctionTypes": false
+ },
+ "include": ["./src/**/*", "./test/**/*"]
+}