Merge pull request #2532 from 0xProject/feat/sol-compiler/support-0.6
sol-compiler: 0.6 support
This commit is contained in:
commit
2086bfff25
@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "4.1.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Refactor + add solidity 0.6 support",
|
||||||
|
"pr": 2532
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1582623685,
|
"timestamp": 1582623685,
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
|
@ -36,13 +36,16 @@ const SEPARATOR = ',';
|
|||||||
: argv.contracts === DEFAULT_CONTRACTS_LIST
|
: argv.contracts === DEFAULT_CONTRACTS_LIST
|
||||||
? DEFAULT_CONTRACTS_LIST
|
? DEFAULT_CONTRACTS_LIST
|
||||||
: argv.contracts.split(SEPARATOR);
|
: argv.contracts.split(SEPARATOR);
|
||||||
const opts = {
|
const opts = _.omitBy(
|
||||||
contractsDir: argv.contractsDir,
|
{
|
||||||
artifactsDir: argv.artifactsDir,
|
contractsDir: argv.contractsDir,
|
||||||
contracts,
|
artifactsDir: argv.artifactsDir,
|
||||||
isOfflineMode: process.env.SOLC_OFFLINE ? true : undefined,
|
contracts,
|
||||||
};
|
isOfflineMode: process.env.SOLC_OFFLINE ? true : undefined,
|
||||||
const compiler = new Compiler(opts);
|
},
|
||||||
|
v => v === undefined,
|
||||||
|
);
|
||||||
|
const compiler = new Compiler(await Compiler.getCompilerOptionsAsync(opts));
|
||||||
if (argv.watch) {
|
if (argv.watch) {
|
||||||
await compiler.watchAsync();
|
await compiler.watchAsync();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { assert } from '@0x/assert';
|
import { assert } from '@0x/assert';
|
||||||
import {
|
import {
|
||||||
|
ContractSource,
|
||||||
FallthroughResolver,
|
FallthroughResolver,
|
||||||
FSResolver,
|
FSResolver,
|
||||||
NameResolver,
|
NameResolver,
|
||||||
@ -10,7 +11,6 @@ import {
|
|||||||
URLResolver,
|
URLResolver,
|
||||||
} from '@0x/sol-resolver';
|
} from '@0x/sol-resolver';
|
||||||
import { logUtils } from '@0x/utils';
|
import { logUtils } from '@0x/utils';
|
||||||
import { execSync } from 'child_process';
|
|
||||||
import * as chokidar from 'chokidar';
|
import * as chokidar from 'chokidar';
|
||||||
import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types';
|
import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
@ -18,59 +18,46 @@ import * as _ from 'lodash';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as pluralize from 'pluralize';
|
import * as pluralize from 'pluralize';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import solc = require('solc');
|
import { StandardInput } from 'solc';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
|
||||||
import { compilerOptionsSchema } from './schemas/compiler_options_schema';
|
import { compilerOptionsSchema } from './schemas/compiler_options_schema';
|
||||||
import {
|
import {
|
||||||
addHexPrefixToContractBytecode,
|
CompiledSources,
|
||||||
compileDockerAsync,
|
|
||||||
compileSolcJSAsync,
|
|
||||||
createDirIfDoesNotExistAsync,
|
createDirIfDoesNotExistAsync,
|
||||||
getContractArtifactIfExistsAsync,
|
getContractArtifactIfExistsAsync,
|
||||||
getDependencyNameToPackagePath,
|
getDependencyNameToPackagePath,
|
||||||
getSolcJSAsync,
|
|
||||||
getSolcJSFromPath,
|
|
||||||
getSolcJSReleasesAsync,
|
getSolcJSReleasesAsync,
|
||||||
getSolcJSVersionFromPath,
|
getSolcJSVersionFromPath,
|
||||||
getSourcesWithDependencies,
|
getSourcesWithDependencies,
|
||||||
getSourceTreeHash,
|
getSourceTreeHash,
|
||||||
makeContractPathsRelative,
|
normalizeSolcVersion,
|
||||||
parseSolidityVersionRange,
|
parseSolidityVersionRange,
|
||||||
printCompilationErrorsAndWarnings,
|
|
||||||
} from './utils/compiler';
|
} from './utils/compiler';
|
||||||
import { constants } from './utils/constants';
|
import { constants } from './utils/constants';
|
||||||
import { fsWrapper } from './utils/fs_wrapper';
|
import { fsWrapper } from './utils/fs_wrapper';
|
||||||
import { utils } from './utils/utils';
|
import { utils } from './utils/utils';
|
||||||
|
|
||||||
type TYPE_ALL_FILES_IDENTIFIER = '*';
|
import { ContractContentsByPath, ImportPrefixRemappings, SolcWrapper } from './solc_wrapper';
|
||||||
const ALL_CONTRACTS_IDENTIFIER = '*';
|
import { SolcWrapperV04 } from './solc_wrapper_v04';
|
||||||
const ALL_FILES_IDENTIFIER = '*';
|
import { SolcWrapperV05 } from './solc_wrapper_v05';
|
||||||
const DEFAULT_CONTRACTS_DIR = path.resolve('contracts');
|
import { SolcWrapperV06 } from './solc_wrapper_v06';
|
||||||
const DEFAULT_ARTIFACTS_DIR = path.resolve('artifacts');
|
|
||||||
const DEFAULT_USE_DOCKERISED_SOLC = false;
|
|
||||||
const DEFAULT_IS_OFFLINE_MODE = false;
|
|
||||||
const DEFAULT_SHOULD_SAVE_STANDARD_INPUT = false;
|
|
||||||
|
|
||||||
// Solc compiler settings cannot be configured from the commandline.
|
export type TYPE_ALL_FILES_IDENTIFIER = '*';
|
||||||
// If you need this configured, please create a `compiler.json` config file
|
export const ALL_CONTRACTS_IDENTIFIER = '*';
|
||||||
// with your desired configurations.
|
export const ALL_FILES_IDENTIFIER = '*';
|
||||||
const DEFAULT_COMPILER_SETTINGS: solc.CompilerSettings = {
|
|
||||||
optimizer: {
|
const DEFAULT_COMPILER_OPTS: CompilerOptions = {
|
||||||
enabled: false,
|
contractsDir: path.resolve('contracts'),
|
||||||
},
|
artifactsDir: path.resolve('artifacts'),
|
||||||
outputSelection: {
|
contracts: ALL_CONTRACTS_IDENTIFIER as TYPE_ALL_FILES_IDENTIFIER,
|
||||||
[ALL_FILES_IDENTIFIER]: {
|
useDockerisedSolc: false,
|
||||||
[ALL_CONTRACTS_IDENTIFIER]: ['abi', 'evm.bytecode.object'],
|
isOfflineMode: false,
|
||||||
},
|
shouldSaveStandardInput: false,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const CONFIG_FILE = 'compiler.json';
|
|
||||||
|
|
||||||
interface VersionToInputs {
|
interface ContractsByVersion {
|
||||||
[solcVersion: string]: {
|
[solcVersion: string]: ContractContentsByPath;
|
||||||
standardInput: solc.StandardInput;
|
|
||||||
contractsToCompile: string[];
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContractPathToData {
|
interface ContractPathToData {
|
||||||
@ -83,60 +70,74 @@ interface ContractData {
|
|||||||
contractName: string;
|
contractName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tslint:disable no-non-null-assertion
|
||||||
/**
|
/**
|
||||||
* The Compiler facilitates compiling Solidity smart contracts and saves the results
|
* The Compiler facilitates compiling Solidity smart contracts and saves the results
|
||||||
* to artifact files.
|
* to artifact files.
|
||||||
*/
|
*/
|
||||||
export class Compiler {
|
export class Compiler {
|
||||||
|
private readonly _opts: CompilerOptions;
|
||||||
private readonly _resolver: Resolver;
|
private readonly _resolver: Resolver;
|
||||||
private readonly _nameResolver: NameResolver;
|
private readonly _nameResolver: NameResolver;
|
||||||
private readonly _contractsDir: string;
|
private readonly _contractsDir: string;
|
||||||
private readonly _compilerSettings: solc.CompilerSettings;
|
|
||||||
private readonly _artifactsDir: string;
|
private readonly _artifactsDir: string;
|
||||||
private readonly _solcVersionIfExists: string | undefined;
|
private readonly _solcVersionIfExists: string | undefined;
|
||||||
private readonly _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
|
private readonly _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
|
||||||
private readonly _useDockerisedSolc: boolean;
|
|
||||||
private readonly _isOfflineMode: boolean;
|
private readonly _isOfflineMode: boolean;
|
||||||
private readonly _shouldSaveStandardInput: boolean;
|
private readonly _shouldSaveStandardInput: boolean;
|
||||||
|
private readonly _solcWrappersByVersion: { [version: string]: SolcWrapper } = {};
|
||||||
|
|
||||||
|
public static async getCompilerOptionsAsync(
|
||||||
|
overrides: Partial<CompilerOptions> = {},
|
||||||
|
file: string = 'compiler.json',
|
||||||
|
): Promise<CompilerOptions> {
|
||||||
|
const fileConfig: CompilerOptions = (await promisify(fs.stat)(file)).isFile
|
||||||
|
? JSON.parse((await promisify(fs.readFile)(file, 'utf8')).toString())
|
||||||
|
: {};
|
||||||
|
assert.doesConformToSchema('compiler.json', fileConfig, compilerOptionsSchema);
|
||||||
|
return {
|
||||||
|
...fileConfig,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _createDefaultResolver(
|
||||||
|
contractsDir: string,
|
||||||
|
// tslint:disable-next-line: trailing-comma
|
||||||
|
...appendedResolvers: Resolver[]
|
||||||
|
): Resolver {
|
||||||
|
const resolver = new FallthroughResolver();
|
||||||
|
resolver.appendResolver(new URLResolver());
|
||||||
|
resolver.appendResolver(new NPMResolver(contractsDir));
|
||||||
|
resolver.appendResolver(new RelativeFSResolver(contractsDir));
|
||||||
|
resolver.appendResolver(new FSResolver());
|
||||||
|
for (const appendedResolver of appendedResolvers) {
|
||||||
|
resolver.appendResolver(appendedResolver);
|
||||||
|
}
|
||||||
|
return resolver;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new instance of the Compiler class.
|
* Instantiates a new instance of the Compiler class.
|
||||||
* @param opts Optional compiler options
|
* @param opts Optional compiler options
|
||||||
* @return An instance of the Compiler class.
|
* @return An instance of the Compiler class.
|
||||||
*/
|
*/
|
||||||
constructor(opts?: CompilerOptions) {
|
constructor(opts: CompilerOptions = {}) {
|
||||||
const passedOpts = opts || {};
|
this._opts = { ...DEFAULT_COMPILER_OPTS, ...opts };
|
||||||
assert.doesConformToSchema('opts', passedOpts, compilerOptionsSchema);
|
assert.doesConformToSchema('opts', this._opts, compilerOptionsSchema);
|
||||||
// TODO: Look for config file in parent directories if not found in current directory
|
this._contractsDir = path.resolve(this._opts.contractsDir!);
|
||||||
const config: CompilerOptions = fs.existsSync(CONFIG_FILE)
|
|
||||||
? JSON.parse(fs.readFileSync(CONFIG_FILE).toString())
|
|
||||||
: {};
|
|
||||||
assert.doesConformToSchema('compiler.json', config, compilerOptionsSchema);
|
|
||||||
this._contractsDir = path.resolve(passedOpts.contractsDir || config.contractsDir || DEFAULT_CONTRACTS_DIR);
|
|
||||||
this._solcVersionIfExists =
|
this._solcVersionIfExists =
|
||||||
process.env.SOLCJS_PATH !== undefined
|
process.env.SOLCJS_PATH !== undefined
|
||||||
? getSolcJSVersionFromPath(process.env.SOLCJS_PATH)
|
? getSolcJSVersionFromPath(process.env.SOLCJS_PATH)
|
||||||
: passedOpts.solcVersion || config.solcVersion;
|
: this._opts.solcVersion;
|
||||||
this._compilerSettings = {
|
this._artifactsDir = this._opts.artifactsDir!;
|
||||||
...DEFAULT_COMPILER_SETTINGS,
|
this._specifiedContracts = this._opts.contracts!;
|
||||||
...config.compilerSettings,
|
this._isOfflineMode = this._opts.isOfflineMode!;
|
||||||
...passedOpts.compilerSettings,
|
this._shouldSaveStandardInput = this._opts.shouldSaveStandardInput!;
|
||||||
};
|
|
||||||
this._artifactsDir = passedOpts.artifactsDir || config.artifactsDir || DEFAULT_ARTIFACTS_DIR;
|
|
||||||
this._specifiedContracts = passedOpts.contracts || config.contracts || ALL_CONTRACTS_IDENTIFIER;
|
|
||||||
this._useDockerisedSolc =
|
|
||||||
passedOpts.useDockerisedSolc || config.useDockerisedSolc || DEFAULT_USE_DOCKERISED_SOLC;
|
|
||||||
this._isOfflineMode = passedOpts.isOfflineMode || config.isOfflineMode || DEFAULT_IS_OFFLINE_MODE;
|
|
||||||
this._shouldSaveStandardInput =
|
|
||||||
passedOpts.shouldSaveStandardInput || config.shouldSaveStandardInput || DEFAULT_SHOULD_SAVE_STANDARD_INPUT;
|
|
||||||
this._nameResolver = new NameResolver(this._contractsDir);
|
this._nameResolver = new NameResolver(this._contractsDir);
|
||||||
const resolver = new FallthroughResolver();
|
this._resolver = Compiler._createDefaultResolver(this._contractsDir, this._nameResolver);
|
||||||
resolver.appendResolver(new URLResolver());
|
|
||||||
resolver.appendResolver(new NPMResolver(this._contractsDir));
|
|
||||||
resolver.appendResolver(new RelativeFSResolver(this._contractsDir));
|
|
||||||
resolver.appendResolver(new FSResolver());
|
|
||||||
resolver.appendResolver(this._nameResolver);
|
|
||||||
this._resolver = resolver;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles selected Solidity files found in `contractsDir` and writes JSON artifacts to `artifactsDir`.
|
* Compiles selected Solidity files found in `contractsDir` and writes JSON artifacts to `artifactsDir`.
|
||||||
*/
|
*/
|
||||||
@ -145,6 +146,7 @@ export class Compiler {
|
|||||||
await createDirIfDoesNotExistAsync(constants.SOLC_BIN_DIR);
|
await createDirIfDoesNotExistAsync(constants.SOLC_BIN_DIR);
|
||||||
await this._compileContractsAsync(this.getContractNamesToCompile(), true);
|
await this._compileContractsAsync(this.getContractNamesToCompile(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles Solidity files specified during instantiation, and returns the
|
* Compiles Solidity files specified during instantiation, and returns the
|
||||||
* compiler output given by solc. Return value is an array of outputs:
|
* compiler output given by solc. Return value is an array of outputs:
|
||||||
@ -157,6 +159,10 @@ export class Compiler {
|
|||||||
const promisedOutputs = this._compileContractsAsync(this.getContractNamesToCompile(), false);
|
const promisedOutputs = this._compileContractsAsync(this.getContractNamesToCompile(), false);
|
||||||
return promisedOutputs;
|
return promisedOutputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch contracts in the current project directory and recompile on changes.
|
||||||
|
*/
|
||||||
public async watchAsync(): Promise<void> {
|
public async watchAsync(): Promise<void> {
|
||||||
console.clear(); // tslint:disable-line:no-console
|
console.clear(); // tslint:disable-line:no-console
|
||||||
logUtils.logWithTime('Starting compilation in watch mode...');
|
logUtils.logWithTime('Starting compilation in watch mode...');
|
||||||
@ -183,7 +189,7 @@ export class Compiler {
|
|||||||
watcher.add(pathsToWatch);
|
watcher.add(pathsToWatch);
|
||||||
};
|
};
|
||||||
await onFileChangedAsync();
|
await onFileChangedAsync();
|
||||||
watcher.on('change', (changedFilePath: string) => {
|
watcher.on('change', () => {
|
||||||
console.clear(); // tslint:disable-line:no-console
|
console.clear(); // tslint:disable-line:no-console
|
||||||
logUtils.logWithTime('File change detected. Starting incremental compilation...');
|
logUtils.logWithTime('File change detected. Starting incremental compilation...');
|
||||||
// NOTE: We can't await it here because that's a callback.
|
// NOTE: We can't await it here because that's a callback.
|
||||||
@ -191,6 +197,7 @@ export class Compiler {
|
|||||||
onFileChangedAsync(); // tslint:disable-line no-floating-promises
|
onFileChangedAsync(); // tslint:disable-line no-floating-promises
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of contracts to compile.
|
* Gets a list of contracts to compile.
|
||||||
*/
|
*/
|
||||||
@ -206,6 +213,7 @@ export class Compiler {
|
|||||||
}
|
}
|
||||||
return contractNamesToCompile;
|
return contractNamesToCompile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getPathsToWatch(): string[] {
|
private _getPathsToWatch(): string[] {
|
||||||
const contractNames = this.getContractNamesToCompile();
|
const contractNames = this.getContractNamesToCompile();
|
||||||
const spyResolver = new SpyResolver(this._resolver);
|
const spyResolver = new SpyResolver(this._resolver);
|
||||||
@ -220,6 +228,7 @@ export class Compiler {
|
|||||||
const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath));
|
const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath));
|
||||||
return pathsToWatch;
|
return pathsToWatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles contracts, and, if `shouldPersist` is true, saves artifacts to artifactsDir.
|
* Compiles contracts, and, if `shouldPersist` is true, saves artifacts to artifactsDir.
|
||||||
* @param fileName Name of contract with '.sol' extension.
|
* @param fileName Name of contract with '.sol' extension.
|
||||||
@ -227,13 +236,12 @@ export class Compiler {
|
|||||||
*/
|
*/
|
||||||
private async _compileContractsAsync(contractNames: string[], shouldPersist: boolean): Promise<StandardOutput[]> {
|
private async _compileContractsAsync(contractNames: string[], shouldPersist: boolean): Promise<StandardOutput[]> {
|
||||||
// batch input contracts together based on the version of the compiler that they require.
|
// batch input contracts together based on the version of the compiler that they require.
|
||||||
const versionToInputs: VersionToInputs = {};
|
const contractsByVersion: ContractsByVersion = {};
|
||||||
|
|
||||||
// map contract paths to data about them for later verification and persistence
|
// map contract paths to data about them for later verification and persistence
|
||||||
const contractPathToData: ContractPathToData = {};
|
const contractPathToData: ContractPathToData = {};
|
||||||
|
|
||||||
const solcJSReleases = await getSolcJSReleasesAsync(this._isOfflineMode);
|
const solcJSReleases = await getSolcJSReleasesAsync(this._isOfflineMode);
|
||||||
const resolvedContractSources = [];
|
const resolvedContractSources: ContractSource[] = [];
|
||||||
for (const contractName of contractNames) {
|
for (const contractName of contractNames) {
|
||||||
const spyResolver = new SpyResolver(this._resolver);
|
const spyResolver = new SpyResolver(this._resolver);
|
||||||
const contractSource = spyResolver.resolve(contractName);
|
const contractSource = spyResolver.resolve(contractName);
|
||||||
@ -246,161 +254,155 @@ export class Compiler {
|
|||||||
if (!this._shouldCompile(contractData)) {
|
if (!this._shouldCompile(contractData)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
contractPathToData[contractSource.path] = contractData;
|
contractPathToData[contractSource.absolutePath] = contractData;
|
||||||
const solcVersion =
|
let solcVersion: string | undefined;
|
||||||
this._solcVersionIfExists === undefined
|
if (this._solcVersionIfExists) {
|
||||||
? semver.maxSatisfying(_.keys(solcJSReleases), parseSolidityVersionRange(contractSource.source))
|
solcVersion = this._solcVersionIfExists;
|
||||||
: this._solcVersionIfExists;
|
} else {
|
||||||
if (solcVersion === null) {
|
const solidityVersion = semver.maxSatisfying(
|
||||||
|
_.keys(solcJSReleases),
|
||||||
|
parseSolidityVersionRange(contractSource.source),
|
||||||
|
);
|
||||||
|
if (solidityVersion) {
|
||||||
|
solcVersion = normalizeSolcVersion(solcJSReleases[solidityVersion]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (solcVersion === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Couldn't find any solidity version satisfying the constraint ${parseSolidityVersionRange(
|
`Couldn't find any solidity version satisfying the constraint ${parseSolidityVersionRange(
|
||||||
contractSource.source,
|
contractSource.source,
|
||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const isFirstContractWithThisVersion = versionToInputs[solcVersion] === undefined;
|
|
||||||
if (isFirstContractWithThisVersion) {
|
|
||||||
versionToInputs[solcVersion] = {
|
|
||||||
standardInput: {
|
|
||||||
language: 'Solidity',
|
|
||||||
sources: {},
|
|
||||||
settings: this._compilerSettings,
|
|
||||||
},
|
|
||||||
contractsToCompile: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// add input to the right version batch
|
// add input to the right version batch
|
||||||
for (const resolvedContractSource of spyResolver.resolvedContractSources) {
|
for (const resolvedContractSource of spyResolver.resolvedContractSources) {
|
||||||
versionToInputs[solcVersion].standardInput.sources[resolvedContractSource.absolutePath] = {
|
contractsByVersion[solcVersion] = contractsByVersion[solcVersion] || {};
|
||||||
content: resolvedContractSource.source,
|
contractsByVersion[solcVersion][resolvedContractSource.absolutePath] = resolvedContractSource.source;
|
||||||
};
|
resolvedContractSources.push(resolvedContractSource);
|
||||||
}
|
}
|
||||||
resolvedContractSources.push(...spyResolver.resolvedContractSources);
|
|
||||||
versionToInputs[solcVersion].contractsToCompile.push(contractSource.path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const dependencyNameToPath = getDependencyNameToPackagePath(resolvedContractSources);
|
const importRemappings = getDependencyNameToPackagePath(resolvedContractSources);
|
||||||
|
const versions = Object.keys(contractsByVersion);
|
||||||
|
|
||||||
const compilerOutputs: StandardOutput[] = [];
|
const compilationResults = await Promise.all(
|
||||||
for (const solcVersion of _.keys(versionToInputs)) {
|
versions.map(async solcVersion => {
|
||||||
const input = versionToInputs[solcVersion];
|
const contracts = contractsByVersion[solcVersion];
|
||||||
logUtils.warn(
|
logUtils.warn(
|
||||||
`Compiling ${input.contractsToCompile.length} contracts (${
|
`Compiling ${Object.keys(contracts).length} contracts (${Object.keys(contracts).map(p =>
|
||||||
input.contractsToCompile
|
path.basename(p),
|
||||||
}) with Solidity v${solcVersion}...`,
|
)}) with Solidity ${solcVersion}...`,
|
||||||
);
|
);
|
||||||
let compilerOutput;
|
return this._getSolcWrapperForVersion(solcVersion).compileAsync(contracts, importRemappings);
|
||||||
let fullSolcVersion;
|
}),
|
||||||
input.standardInput.settings.remappings = _.map(
|
);
|
||||||
dependencyNameToPath,
|
|
||||||
(dependencyPackagePath: string, dependencyName: string) => `${dependencyName}=${dependencyPackagePath}`,
|
|
||||||
);
|
|
||||||
if (this._useDockerisedSolc) {
|
|
||||||
const dockerCommand = `docker run ethereum/solc:${solcVersion} --version`;
|
|
||||||
const versionCommandOutput = execSync(dockerCommand).toString();
|
|
||||||
const versionCommandOutputParts = versionCommandOutput.split(' ');
|
|
||||||
fullSolcVersion = versionCommandOutputParts[versionCommandOutputParts.length - 1].trim();
|
|
||||||
compilerOutput = await compileDockerAsync(solcVersion, input.standardInput);
|
|
||||||
} else {
|
|
||||||
fullSolcVersion = solcJSReleases[solcVersion];
|
|
||||||
const solcInstance =
|
|
||||||
process.env.SOLCJS_PATH !== undefined
|
|
||||||
? getSolcJSFromPath(process.env.SOLCJS_PATH)
|
|
||||||
: await getSolcJSAsync(solcVersion, this._isOfflineMode);
|
|
||||||
compilerOutput = await compileSolcJSAsync(solcInstance, input.standardInput);
|
|
||||||
}
|
|
||||||
if (compilerOutput.errors !== undefined) {
|
|
||||||
printCompilationErrorsAndWarnings(compilerOutput.errors);
|
|
||||||
}
|
|
||||||
compilerOutput.sources = makeContractPathsRelative(
|
|
||||||
compilerOutput.sources,
|
|
||||||
this._contractsDir,
|
|
||||||
dependencyNameToPath,
|
|
||||||
);
|
|
||||||
compilerOutput.contracts = makeContractPathsRelative(
|
|
||||||
compilerOutput.contracts,
|
|
||||||
this._contractsDir,
|
|
||||||
dependencyNameToPath,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const contractPath of input.contractsToCompile) {
|
if (shouldPersist) {
|
||||||
const contractName = contractPathToData[contractPath].contractName;
|
await Promise.all(
|
||||||
if (compilerOutput.contracts[contractPath] !== undefined) {
|
versions.map(async (solcVersion, i) => {
|
||||||
const compiledContract = compilerOutput.contracts[contractPath][contractName];
|
const compilationResult = compilationResults[i];
|
||||||
if (compiledContract === undefined) {
|
const contracts = contractsByVersion[solcVersion];
|
||||||
throw new Error(
|
// tslint:disable-next-line: forin
|
||||||
`Contract ${contractName} not found in ${contractPath}. Please make sure your contract has the same name as it's file name`,
|
await Promise.all(
|
||||||
);
|
Object.keys(contracts).map(async contractPath => {
|
||||||
}
|
const contractData = contractPathToData[contractPath];
|
||||||
if (this._shouldSaveStandardInput) {
|
if (contractData === undefined) {
|
||||||
await fsWrapper.writeFileAsync(
|
return;
|
||||||
`${this._artifactsDir}/${contractName}.input.json`,
|
}
|
||||||
utils.stringifyWithFormatting(input.standardInput),
|
const { contractName } = contractData;
|
||||||
);
|
const compiledContract = compilationResult.output.contracts[contractPath][contractName];
|
||||||
}
|
if (compiledContract === undefined) {
|
||||||
addHexPrefixToContractBytecode(compiledContract);
|
throw new Error(
|
||||||
}
|
`Contract ${contractName} not found in ${contractPath}. Please make sure your contract has the same name as it's file name`,
|
||||||
|
);
|
||||||
if (shouldPersist) {
|
}
|
||||||
await this._persistCompiledContractAsync(
|
await this._persistCompiledContractAsync(
|
||||||
contractPath,
|
contractPath,
|
||||||
contractPathToData[contractPath].currentArtifactIfExists,
|
contractPathToData[contractPath].currentArtifactIfExists,
|
||||||
contractPathToData[contractPath].sourceTreeHashHex,
|
contractPathToData[contractPath].sourceTreeHashHex,
|
||||||
contractName,
|
contractName,
|
||||||
fullSolcVersion,
|
solcVersion,
|
||||||
compilerOutput,
|
contracts,
|
||||||
|
compilationResult.input,
|
||||||
|
compilationResult.output,
|
||||||
|
importRemappings,
|
||||||
|
);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}),
|
||||||
}
|
);
|
||||||
|
|
||||||
compilerOutputs.push(compilerOutput);
|
|
||||||
}
|
}
|
||||||
|
return compilationResults.map(r => r.output);
|
||||||
return compilerOutputs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _shouldCompile(contractData: ContractData): boolean {
|
private _shouldCompile(contractData: ContractData): boolean {
|
||||||
if (contractData.currentArtifactIfExists === undefined) {
|
if (contractData.currentArtifactIfExists === undefined) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
const currentArtifact = contractData.currentArtifactIfExists as ContractArtifact;
|
const currentArtifact = contractData.currentArtifactIfExists as ContractArtifact;
|
||||||
|
const solc = this._getSolcWrapperForVersion(currentArtifact.compiler.version);
|
||||||
const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
|
const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
|
||||||
const didCompilerSettingsChange = !_.isEqual(
|
const didCompilerSettingsChange = solc.areCompilerSettingsDifferent(currentArtifact.compiler.settings);
|
||||||
_.omit(currentArtifact.compiler.settings, 'remappings'),
|
|
||||||
_.omit(this._compilerSettings, 'remappings'),
|
|
||||||
);
|
|
||||||
const didSourceChange = currentArtifact.sourceTreeHashHex !== contractData.sourceTreeHashHex;
|
const didSourceChange = currentArtifact.sourceTreeHashHex !== contractData.sourceTreeHashHex;
|
||||||
return !isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange;
|
return !isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getSolcWrapperForVersion(solcVersion: string): SolcWrapper {
|
||||||
|
const normalizedVersion = normalizeSolcVersion(solcVersion);
|
||||||
|
return (
|
||||||
|
this._solcWrappersByVersion[normalizedVersion] ||
|
||||||
|
(this._solcWrappersByVersion[normalizedVersion] = this._createSolcInstance(normalizedVersion))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _createSolcInstance(solcVersion: string): SolcWrapper {
|
||||||
|
if (solcVersion.startsWith('0.4.')) {
|
||||||
|
return new SolcWrapperV04(solcVersion, this._opts);
|
||||||
|
}
|
||||||
|
if (solcVersion.startsWith('0.5.')) {
|
||||||
|
return new SolcWrapperV05(solcVersion, this._opts);
|
||||||
|
}
|
||||||
|
if (solcVersion.startsWith('0.6')) {
|
||||||
|
return new SolcWrapperV06(solcVersion, this._opts);
|
||||||
|
}
|
||||||
|
throw new Error(`Missing Solc wrapper implementation for version ${solcVersion}`);
|
||||||
|
}
|
||||||
|
|
||||||
private async _persistCompiledContractAsync(
|
private async _persistCompiledContractAsync(
|
||||||
contractPath: string,
|
contractPath: string,
|
||||||
currentArtifactIfExists: ContractArtifact | void,
|
currentArtifactIfExists: ContractArtifact | void,
|
||||||
sourceTreeHashHex: string,
|
sourceTreeHashHex: string,
|
||||||
contractName: string,
|
contractName: string,
|
||||||
fullSolcVersion: string,
|
solcVersion: string,
|
||||||
compilerOutput: solc.StandardOutput,
|
sourcesByPath: ContractContentsByPath,
|
||||||
|
compilerInput: StandardInput,
|
||||||
|
compilerOutput: StandardOutput,
|
||||||
|
importRemappings: ImportPrefixRemappings,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const compiledContract = compilerOutput.contracts[contractPath][contractName];
|
const compiledContract = compilerOutput.contracts[contractPath][contractName];
|
||||||
|
|
||||||
// need to gather sourceCodes for this artifact, but compilerOutput.sources (the list of contract modules)
|
// need to gather sourceCodes for this artifact, but compilerOutput.sources (the list of contract modules)
|
||||||
// contains listings for every contract compiled during the compiler invocation that compiled the contract
|
// contains listings 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
|
// to be persisted, which could include many that are irrelevant to the contract at hand. So, gather up only
|
||||||
// the relevant sources:
|
// the relevant sources:
|
||||||
const { sourceCodes, sources } = getSourcesWithDependencies(
|
const allSources: CompiledSources = {};
|
||||||
this._resolver,
|
// tslint:disable-next-line: forin
|
||||||
contractPath,
|
for (const sourceContractPath in sourcesByPath) {
|
||||||
compilerOutput.sources,
|
const content = sourcesByPath[sourceContractPath];
|
||||||
);
|
const { id } = compilerOutput.sources[sourceContractPath];
|
||||||
|
allSources[sourceContractPath] = { id, content };
|
||||||
|
}
|
||||||
|
const usedSources = getSourcesWithDependencies(contractPath, allSources, importRemappings);
|
||||||
|
|
||||||
const contractVersion: ContractVersionData = {
|
const contractVersion: ContractVersionData = {
|
||||||
compilerOutput: compiledContract,
|
compilerOutput: compiledContract,
|
||||||
sources,
|
|
||||||
sourceCodes,
|
|
||||||
sourceTreeHashHex,
|
sourceTreeHashHex,
|
||||||
|
sources: _.mapValues(usedSources, ({ id }) => ({ id })),
|
||||||
|
sourceCodes: _.mapValues(usedSources, ({ content }) => content),
|
||||||
compiler: {
|
compiler: {
|
||||||
name: 'solc',
|
name: 'solc',
|
||||||
version: fullSolcVersion,
|
version: solcVersion,
|
||||||
settings: this._compilerSettings,
|
settings: compilerInput.settings,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -424,5 +426,20 @@ export class Compiler {
|
|||||||
const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
|
const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
|
||||||
await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
|
await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
|
||||||
logUtils.warn(`${contractName} artifact saved!`);
|
logUtils.warn(`${contractName} artifact saved!`);
|
||||||
|
|
||||||
|
if (this._shouldSaveStandardInput) {
|
||||||
|
await fsWrapper.writeFileAsync(
|
||||||
|
`${this._artifactsDir}/${contractName}.input.json`,
|
||||||
|
utils.stringifyWithFormatting({
|
||||||
|
...compilerInput,
|
||||||
|
// Insert solcVersion into input.
|
||||||
|
settings: {
|
||||||
|
...compilerInput.settings,
|
||||||
|
version: solcVersion,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
logUtils.warn(`${contractName} input artifact saved!`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
35
packages/sol-compiler/src/solc_wrapper.ts
Normal file
35
packages/sol-compiler/src/solc_wrapper.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { StandardOutput } from 'ethereum-types';
|
||||||
|
import { StandardInput } from 'solc';
|
||||||
|
|
||||||
|
export interface ContractContentsByPath {
|
||||||
|
[path: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImportPrefixRemappings {
|
||||||
|
[prefix: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompilationResult {
|
||||||
|
input: StandardInput;
|
||||||
|
output: StandardOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class SolcWrapper {
|
||||||
|
/**
|
||||||
|
* Get the solc version.
|
||||||
|
*/
|
||||||
|
public abstract get version(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the configured compiler settings is different from another.
|
||||||
|
*/
|
||||||
|
public abstract areCompilerSettingsDifferent(settings: any): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile contracts, returning standard input and output.
|
||||||
|
*/
|
||||||
|
public abstract compileAsync(
|
||||||
|
contractsByPath: ContractContentsByPath,
|
||||||
|
dependencies: ImportPrefixRemappings,
|
||||||
|
): Promise<CompilationResult>;
|
||||||
|
}
|
3
packages/sol-compiler/src/solc_wrapper_v04.ts
Normal file
3
packages/sol-compiler/src/solc_wrapper_v04.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { SolcWrapperV05 } from './solc_wrapper_v05';
|
||||||
|
|
||||||
|
export const SolcWrapperV04 = SolcWrapperV05;
|
102
packages/sol-compiler/src/solc_wrapper_v05.ts
Normal file
102
packages/sol-compiler/src/solc_wrapper_v05.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { CompilerOptions, StandardOutput } from 'ethereum-types';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import solc = require('solc');
|
||||||
|
|
||||||
|
import {
|
||||||
|
addHexPrefixToContractBytecode,
|
||||||
|
compileDockerAsync,
|
||||||
|
compileSolcJSAsync,
|
||||||
|
getSolcJSAsync,
|
||||||
|
getSolidityVersionFromSolcVersion,
|
||||||
|
printCompilationErrorsAndWarnings,
|
||||||
|
} from './utils/compiler';
|
||||||
|
|
||||||
|
import { CompilationResult, ContractContentsByPath, ImportPrefixRemappings, SolcWrapper } from './solc_wrapper';
|
||||||
|
|
||||||
|
// Solc compiler settings cannot be configured from the commandline.
|
||||||
|
// If you need this configured, please create a `compiler.json` config file
|
||||||
|
// with your desired configurations.
|
||||||
|
export const DEFAULT_COMPILER_SETTINGS: solc.CompilerSettings = {
|
||||||
|
optimizer: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
outputSelection: {
|
||||||
|
'*': {
|
||||||
|
'*': ['abi', 'evm.bytecode.object'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// tslint:disable no-non-null-assertion
|
||||||
|
|
||||||
|
export class SolcWrapperV05 extends SolcWrapper {
|
||||||
|
protected readonly _compilerSettings: solc.CompilerSettings;
|
||||||
|
|
||||||
|
public static normalizeOutput(output: StandardOutput): StandardOutput {
|
||||||
|
const _output = _.cloneDeep(output);
|
||||||
|
// tslint:disable-next-line forin
|
||||||
|
for (const contractPath in _output.contracts) {
|
||||||
|
// tslint:disable-next-line forin
|
||||||
|
for (const contract of Object.values(_output.contracts[contractPath])) {
|
||||||
|
addHexPrefixToContractBytecode(contract);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _output;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(protected readonly _solcVersion: string, protected readonly _opts: CompilerOptions) {
|
||||||
|
super();
|
||||||
|
this._compilerSettings = {
|
||||||
|
...DEFAULT_COMPILER_SETTINGS,
|
||||||
|
..._opts.compilerSettings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get version(): string {
|
||||||
|
return this._solcVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get solidityVersion(): string {
|
||||||
|
return getSolidityVersionFromSolcVersion(this._solcVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public areCompilerSettingsDifferent(settings: any): boolean {
|
||||||
|
return !_.isEqual(_.omit(settings, 'remappings'), _.omit(this._compilerSettings, 'remappings'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async compileAsync(
|
||||||
|
contractsByPath: ContractContentsByPath,
|
||||||
|
importRemappings: ImportPrefixRemappings,
|
||||||
|
): Promise<CompilationResult> {
|
||||||
|
const input: solc.StandardInput = {
|
||||||
|
language: 'Solidity',
|
||||||
|
sources: {},
|
||||||
|
settings: {
|
||||||
|
remappings: [],
|
||||||
|
...this._compilerSettings,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (const [contractPath, contractContent] of Object.entries(contractsByPath)) {
|
||||||
|
input.sources[contractPath] = { content: contractContent };
|
||||||
|
}
|
||||||
|
for (const [prefix, _path] of Object.entries(importRemappings)) {
|
||||||
|
input.settings.remappings!.push(`${prefix}=${_path}`);
|
||||||
|
}
|
||||||
|
const output = await this._compileInputAsync(input);
|
||||||
|
if (output.errors !== undefined) {
|
||||||
|
printCompilationErrorsAndWarnings(output.errors);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
input,
|
||||||
|
output: SolcWrapperV05.normalizeOutput(output),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _compileInputAsync(input: solc.StandardInput): Promise<StandardOutput> {
|
||||||
|
if (this._opts.useDockerisedSolc) {
|
||||||
|
return compileDockerAsync(this.solidityVersion, input);
|
||||||
|
}
|
||||||
|
const solcInstance = await getSolcJSAsync(this.solidityVersion, !!this._opts.isOfflineMode);
|
||||||
|
return compileSolcJSAsync(solcInstance, input);
|
||||||
|
}
|
||||||
|
}
|
25
packages/sol-compiler/src/solc_wrapper_v06.ts
Normal file
25
packages/sol-compiler/src/solc_wrapper_v06.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { CompilerOptions, StandardOutput } from 'ethereum-types';
|
||||||
|
import solc = require('solc');
|
||||||
|
|
||||||
|
import { compileSolcJSAsync, getSolcJSAsync } from './utils/compiler';
|
||||||
|
|
||||||
|
import { SolcWrapperV05 } from './solc_wrapper_v05';
|
||||||
|
|
||||||
|
// 0.6.x has a `compile()` function in lieu of `compileStandardWrapper`.
|
||||||
|
type SolcV06 = solc.SolcInstance & { compile(input: string): string };
|
||||||
|
|
||||||
|
export class SolcWrapperV06 extends SolcWrapperV05 {
|
||||||
|
constructor(solcVersion: string, opts: CompilerOptions) {
|
||||||
|
super(solcVersion, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _compileInputAsync(input: solc.StandardInput): Promise<StandardOutput> {
|
||||||
|
if (this._opts.useDockerisedSolc) {
|
||||||
|
return super._compileInputAsync(input);
|
||||||
|
}
|
||||||
|
// Shim the old `compileStandardWrapper` function.
|
||||||
|
const solcInstance = (await getSolcJSAsync(this.solidityVersion, !!this._opts.isOfflineMode)) as SolcV06;
|
||||||
|
solcInstance.compileStandardWrapper = solcInstance.compile;
|
||||||
|
return compileSolcJSAsync(solcInstance, input);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,14 @@
|
|||||||
import { ContractSource, Resolver } from '@0x/sol-resolver';
|
import { ContractSource, Resolver } from '@0x/sol-resolver';
|
||||||
import { fetchAsync, logUtils } from '@0x/utils';
|
import { fetchAsync, logUtils } from '@0x/utils';
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { execSync } from 'child_process';
|
import { exec, spawn } from 'child_process';
|
||||||
import { ContractArtifact } from 'ethereum-types';
|
import { ContractArtifact } from 'ethereum-types';
|
||||||
import * as ethUtil from 'ethereumjs-util';
|
import * as ethUtil from 'ethereumjs-util';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as requireFromString from 'require-from-string';
|
import * as requireFromString from 'require-from-string';
|
||||||
import * as solc from 'solc';
|
import * as solc from 'solc';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
|
||||||
import { constants } from './constants';
|
import { constants } from './constants';
|
||||||
import { fsWrapper } from './fs_wrapper';
|
import { fsWrapper } from './fs_wrapper';
|
||||||
@ -152,18 +153,39 @@ export async function compileSolcJSAsync(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles the contracts and prints errors/warnings
|
* Compiles the contracts and prints errors/warnings
|
||||||
* @param solcVersion Version of a solc compiler
|
* @param solidityVersion Solidity version
|
||||||
* @param standardInput Solidity standard JSON input
|
* @param standardInput Solidity standard JSON input
|
||||||
*/
|
*/
|
||||||
export async function compileDockerAsync(
|
export async function compileDockerAsync(
|
||||||
solcVersion: string,
|
solidityVersion: string,
|
||||||
standardInput: solc.StandardInput,
|
standardInput: solc.StandardInput,
|
||||||
): Promise<solc.StandardOutput> {
|
): Promise<solc.StandardOutput> {
|
||||||
const standardInputStr = JSON.stringify(standardInput, null, 2);
|
const standardInputStr = JSON.stringify(standardInput, null, 2);
|
||||||
const dockerCommand = `docker run -i -a stdin -a stdout -a stderr ethereum/solc:${solcVersion} solc --standard-json`;
|
// prettier-ignore
|
||||||
const standardOutputStr = execSync(dockerCommand, { input: standardInputStr }).toString();
|
const dockerArgs = [
|
||||||
const compiled: solc.StandardOutput = JSON.parse(standardOutputStr);
|
'run',
|
||||||
return compiled;
|
'-i',
|
||||||
|
'-a', 'stdin',
|
||||||
|
'-a', 'stdout',
|
||||||
|
'-a', 'stderr',
|
||||||
|
`ethereum/solc:${solidityVersion}`,
|
||||||
|
'solc', '--standard-json',
|
||||||
|
];
|
||||||
|
return new Promise<solc.StandardOutput>((accept, reject) => {
|
||||||
|
const p = spawn('docker', dockerArgs, { shell: true, stdio: ['pipe', 'inherit', 'inherit'] });
|
||||||
|
p.stdin.write(standardInputStr);
|
||||||
|
p.stdin.end();
|
||||||
|
let fullOutput = '';
|
||||||
|
p.stdout.on('data', (chunk: string) => {
|
||||||
|
fullOutput += chunk;
|
||||||
|
});
|
||||||
|
p.on('close', code => {
|
||||||
|
if (code !== 0) {
|
||||||
|
reject('Compilation failed');
|
||||||
|
}
|
||||||
|
accept(JSON.parse(fullOutput));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -250,43 +272,60 @@ export function getSourceTreeHash(resolver: Resolver, importPath: string): Buffe
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For the given @param contractPath, populates JSON objects to be used in the ContractVersionData interface's
|
* Mapping of absolute contract path to compilation ID and source code.
|
||||||
* 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
|
export interface CompiledSources {
|
||||||
* `resolver.resolve().source`), as are its imports, recursively. The ID numbers for @return `sources` are
|
[sourcePath: string]: { id: number; content: string };
|
||||||
* taken from the corresponding ID's in @param fullSources, and the content for @return sourceCodes is read from
|
}
|
||||||
* disk (via the aforementioned `resolver.source`).
|
|
||||||
|
/**
|
||||||
|
* Contract sources by import path.
|
||||||
|
*/
|
||||||
|
export interface CompiledImports {
|
||||||
|
[importPath: string]: { id: number; content: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively parses imports from sources starting from `contractPath`.
|
||||||
|
* @return Sources required by imports.
|
||||||
*/
|
*/
|
||||||
export function getSourcesWithDependencies(
|
export function getSourcesWithDependencies(
|
||||||
resolver: Resolver,
|
|
||||||
contractPath: string,
|
contractPath: string,
|
||||||
fullSources: { [sourceName: string]: { id: number } },
|
sourcesByAbsolutePath: CompiledSources,
|
||||||
): { sourceCodes: { [sourceName: string]: string }; sources: { [sourceName: string]: { id: number } } } {
|
importRemappings: { [prefix: string]: string },
|
||||||
const sources = { [contractPath]: fullSources[contractPath] };
|
): CompiledImports {
|
||||||
const sourceCodes = { [contractPath]: resolver.resolve(contractPath).source };
|
const compiledImports = { [`./${path.basename(contractPath)}`]: sourcesByAbsolutePath[contractPath] };
|
||||||
recursivelyGatherDependencySources(
|
recursivelyGatherDependencySources(
|
||||||
resolver,
|
|
||||||
contractPath,
|
contractPath,
|
||||||
sourceCodes[contractPath],
|
path.dirname(contractPath),
|
||||||
fullSources,
|
sourcesByAbsolutePath,
|
||||||
sources,
|
importRemappings,
|
||||||
sourceCodes,
|
compiledImports,
|
||||||
);
|
);
|
||||||
return { sourceCodes, sources };
|
return compiledImports;
|
||||||
}
|
}
|
||||||
|
|
||||||
function recursivelyGatherDependencySources(
|
function recursivelyGatherDependencySources(
|
||||||
resolver: Resolver,
|
|
||||||
contractPath: string,
|
contractPath: string,
|
||||||
contractSource: string,
|
rootDir: string,
|
||||||
fullSources: { [sourceName: string]: { id: number } },
|
sourcesByAbsolutePath: CompiledSources,
|
||||||
sourcesToAppendTo: { [sourceName: string]: { id: number } },
|
importRemappings: { [prefix: string]: string },
|
||||||
sourceCodesToAppendTo: { [sourceName: string]: string },
|
compiledImports: CompiledImports,
|
||||||
|
visitedAbsolutePaths: { [absPath: string]: boolean } = {},
|
||||||
|
importRootDir?: string,
|
||||||
): void {
|
): void {
|
||||||
|
if (visitedAbsolutePaths[contractPath]) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
visitedAbsolutePaths[contractPath] = true;
|
||||||
|
}
|
||||||
|
const contractSource = sourcesByAbsolutePath[contractPath].content;
|
||||||
const importStatementMatches = contractSource.match(/\nimport[^;]*;/g);
|
const importStatementMatches = contractSource.match(/\nimport[^;]*;/g);
|
||||||
if (importStatementMatches === null) {
|
if (importStatementMatches === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const lastPathSeparatorPos = contractPath.lastIndexOf('/');
|
||||||
|
const contractFolder = lastPathSeparatorPos === -1 ? '' : contractPath.slice(0, lastPathSeparatorPos + 1);
|
||||||
for (const importStatementMatch of importStatementMatches) {
|
for (const importStatementMatch of importStatementMatches) {
|
||||||
const importPathMatches = importStatementMatch.match(/\"([^\"]*)\"/);
|
const importPathMatches = importStatementMatch.match(/\"([^\"]*)\"/);
|
||||||
if (importPathMatches === null || importPathMatches.length === 0) {
|
if (importPathMatches === null || importPathMatches.length === 0) {
|
||||||
@ -294,59 +333,54 @@ function recursivelyGatherDependencySources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let importPath = importPathMatches[1];
|
let importPath = importPathMatches[1];
|
||||||
// HACK(albrow): We have, e.g.:
|
let absPath = importPath;
|
||||||
//
|
let _importRootDir = importRootDir;
|
||||||
// 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);
|
|
||||||
if (importPath.startsWith('.')) {
|
if (importPath.startsWith('.')) {
|
||||||
/**
|
absPath = path.join(contractFolder, importPath);
|
||||||
* Some imports path are relative ("../Token.sol", "./Wallet.sol")
|
if (_importRootDir) {
|
||||||
* while others are absolute ("Token.sol", "@0x/contracts/Wallet.sol")
|
// If there's an `_importRootDir`, we're in a package, so express
|
||||||
* And we need to append the base path for relative imports.
|
// the import path as within the package.
|
||||||
*/
|
importPath = path.join(_importRootDir, importPath);
|
||||||
importPath = path.resolve(`/${contractFolder}`, importPath).replace('/', '');
|
} else {
|
||||||
}
|
// Express relative imports paths as paths from the root directory.
|
||||||
|
importPath = path.relative(rootDir, absPath);
|
||||||
if (sourcesToAppendTo[importPath] === undefined) {
|
if (!importPath.startsWith('.')) {
|
||||||
sourcesToAppendTo[importPath] = { id: fullSources[importPath].id };
|
importPath = `./${importPath}`;
|
||||||
sourceCodesToAppendTo[importPath] = resolver.resolve(importPath).source;
|
}
|
||||||
|
}
|
||||||
recursivelyGatherDependencySources(
|
} else {
|
||||||
resolver,
|
for (const [prefix, replacement] of Object.entries(importRemappings)) {
|
||||||
importPath,
|
if (importPath.startsWith(prefix)) {
|
||||||
resolver.resolve(importPath).source,
|
absPath = `${replacement}${importPath.substr(prefix.length)}`;
|
||||||
fullSources,
|
_importRootDir = path.dirname(importPath);
|
||||||
sourcesToAppendTo,
|
break;
|
||||||
sourceCodesToAppendTo,
|
}
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
compiledImports[importPath] = sourcesByAbsolutePath[absPath];
|
||||||
|
recursivelyGatherDependencySources(
|
||||||
|
absPath,
|
||||||
|
rootDir,
|
||||||
|
sourcesByAbsolutePath,
|
||||||
|
importRemappings,
|
||||||
|
compiledImports,
|
||||||
|
visitedAbsolutePaths,
|
||||||
|
_importRootDir,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the solidity compiler instance. If the compiler is already cached - gets it from FS,
|
* Gets the solidity compiler instance. If the compiler is already cached - gets it from FS,
|
||||||
* otherwise - fetches it and caches it.
|
* otherwise - fetches it and caches it.
|
||||||
* @param solcVersion The compiler version. e.g. 0.5.0
|
* @param solidityVersion The solidity version. e.g. 0.5.0
|
||||||
* @param isOfflineMode Offline mode flag
|
* @param isOfflineMode Offline mode flag
|
||||||
*/
|
*/
|
||||||
export async function getSolcJSAsync(solcVersion: string, isOfflineMode: boolean): Promise<solc.SolcInstance> {
|
export async function getSolcJSAsync(solidityVersion: string, isOfflineMode: boolean): Promise<solc.SolcInstance> {
|
||||||
const solcJSReleases = await getSolcJSReleasesAsync(isOfflineMode);
|
const solcJSReleases = await getSolcJSReleasesAsync(isOfflineMode);
|
||||||
const fullSolcVersion = solcJSReleases[solcVersion];
|
const fullSolcVersion = solcJSReleases[solidityVersion];
|
||||||
if (fullSolcVersion === undefined) {
|
if (fullSolcVersion === undefined) {
|
||||||
throw new Error(`${solcVersion} is not a known compiler version`);
|
throw new Error(`${solidityVersion} is not a known compiler version`);
|
||||||
}
|
}
|
||||||
const compilerBinFilename = path.join(constants.SOLC_BIN_DIR, fullSolcVersion);
|
const compilerBinFilename = path.join(constants.SOLC_BIN_DIR, fullSolcVersion);
|
||||||
let solcjs: string;
|
let solcjs: string;
|
||||||
@ -383,7 +417,7 @@ export function getSolcJSFromPath(modulePath: string): solc.SolcInstance {
|
|||||||
* @param path The path to the solc module.
|
* @param path The path to the solc module.
|
||||||
*/
|
*/
|
||||||
export function getSolcJSVersionFromPath(modulePath: string): string {
|
export function getSolcJSVersionFromPath(modulePath: string): string {
|
||||||
return require(modulePath).version();
|
return normalizeSolcVersion(require(modulePath).version());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -437,3 +471,47 @@ export function getDependencyNameToPackagePath(
|
|||||||
});
|
});
|
||||||
return dependencyNameToPath;
|
return dependencyNameToPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the solidity version (e.g., '0.5.9') from a solc version (e.g., `0.5.9+commit.34d3134f`).
|
||||||
|
*/
|
||||||
|
export function getSolidityVersionFromSolcVersion(solcVersion: string): string {
|
||||||
|
const m = /(\d+\.\d+\.\d+)\+commit\.[a-fA-F0-9]{8}/.exec(solcVersion);
|
||||||
|
if (!m) {
|
||||||
|
throw new Error(`Unable to parse solc version string "${solcVersion}"`);
|
||||||
|
}
|
||||||
|
return m[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strips any extra characters before and after the version + commit hash of a solc version string.
|
||||||
|
*/
|
||||||
|
export function normalizeSolcVersion(fullSolcVersion: string): string {
|
||||||
|
const m = /\d+\.\d+\.\d+\+commit\.[a-fA-F0-9]{8}/.exec(fullSolcVersion);
|
||||||
|
if (!m) {
|
||||||
|
throw new Error(`Unable to parse solc version string "${fullSolcVersion}"`);
|
||||||
|
}
|
||||||
|
return m[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the full version string of a dockerized solc.
|
||||||
|
*/
|
||||||
|
export async function getDockerFullSolcVersionAsync(solidityVersion: string): Promise<string> {
|
||||||
|
const dockerCommand = `docker run ethereum/solc:${solidityVersion} --version`;
|
||||||
|
const versionCommandOutput = (await promisify(exec)(dockerCommand)).stdout.toString();
|
||||||
|
const versionCommandOutputParts = versionCommandOutput.split(' ');
|
||||||
|
return normalizeSolcVersion(versionCommandOutputParts[versionCommandOutputParts.length - 1].trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the full version string of a JS module solc.
|
||||||
|
*/
|
||||||
|
export async function getJSFullSolcVersionAsync(
|
||||||
|
solidityVersion: string,
|
||||||
|
isOfflineMode: boolean = false,
|
||||||
|
): Promise<string> {
|
||||||
|
return normalizeSolcVersion((await getSolcJSAsync(solidityVersion, isOfflineMode)).version());
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line: max-file-line-count
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import { join } from 'path';
|
import { hexUtils } from '@0x/utils';
|
||||||
|
|
||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import { CompilerOptions, ContractArtifact } from 'ethereum-types';
|
import { CompilerOptions, ContractArtifact } from 'ethereum-types';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
import { Compiler } from '../src/compiler';
|
import { Compiler } from '../src/compiler';
|
||||||
import { fsWrapper } from '../src/utils/fs_wrapper';
|
import { fsWrapper } from '../src/utils/fs_wrapper';
|
||||||
|
|
||||||
import { exchange_binary } from './fixtures/exchange_bin';
|
import { exchange_binary } from './fixtures/exchange_bin';
|
||||||
|
import { v6_contract_binary } from './fixtures/v6_contract_bin';
|
||||||
import { chaiSetup } from './util/chai_setup';
|
import { chaiSetup } from './util/chai_setup';
|
||||||
import { constants } from './util/constants';
|
import { constants } from './util/constants';
|
||||||
|
|
||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
const METADATA_SIZE = 43;
|
||||||
|
|
||||||
describe('#Compiler', function(): void {
|
describe('#Compiler', function(): void {
|
||||||
this.timeout(constants.timeoutMs); // tslint:disable-line:no-invalid-this
|
this.timeout(constants.timeoutMs); // tslint:disable-line:no-invalid-this
|
||||||
const artifactsDir = `${__dirname}/fixtures/artifacts`;
|
const artifactsDir = `${__dirname}/fixtures/artifacts`;
|
||||||
@ -41,14 +44,12 @@ describe('#Compiler', function(): void {
|
|||||||
};
|
};
|
||||||
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
|
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
|
||||||
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
|
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
|
||||||
// The last 43 bytes of the binaries are metadata which may not be equivalent
|
const unlinkedBinaryWithoutMetadata = hexUtils.slice(
|
||||||
const metadataByteLength = 43;
|
exchangeArtifact.compilerOutput.evm.bytecode.object,
|
||||||
const metadataHexLength = metadataByteLength * 2;
|
0,
|
||||||
const unlinkedBinaryWithoutMetadata = exchangeArtifact.compilerOutput.evm.bytecode.object.slice(
|
-METADATA_SIZE,
|
||||||
2,
|
|
||||||
-metadataHexLength,
|
|
||||||
);
|
);
|
||||||
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -metadataHexLength);
|
const exchangeBinaryWithoutMetadata = hexUtils.slice(exchange_binary, 0, -METADATA_SIZE);
|
||||||
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
|
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
|
||||||
});
|
});
|
||||||
it("should throw when Whatever.sol doesn't contain a Whatever contract", async () => {
|
it("should throw when Whatever.sol doesn't contain a Whatever contract", async () => {
|
||||||
@ -114,4 +115,27 @@ describe('#Compiler', function(): void {
|
|||||||
expect(artifact).to.equal('EmptyContract.json');
|
expect(artifact).to.equal('EmptyContract.json');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
it('should compile a V0.6 contract', async () => {
|
||||||
|
compilerOpts.contracts = ['V6Contract'];
|
||||||
|
|
||||||
|
const artifactPath = `${artifactsDir}/V6Contract.json`;
|
||||||
|
if (fsWrapper.doesPathExistSync(artifactPath)) {
|
||||||
|
await fsWrapper.removeFileAsync(artifactPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Compiler(compilerOpts).compileAsync();
|
||||||
|
|
||||||
|
const opts = {
|
||||||
|
encoding: 'utf8',
|
||||||
|
};
|
||||||
|
const exchangeArtifactString = await fsWrapper.readFileAsync(artifactPath, opts);
|
||||||
|
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
|
||||||
|
const actualBinaryWithoutMetadata = hexUtils.slice(
|
||||||
|
exchangeArtifact.compilerOutput.evm.bytecode.object,
|
||||||
|
0,
|
||||||
|
-METADATA_SIZE,
|
||||||
|
);
|
||||||
|
const expectedBinaryWithoutMetadata = hexUtils.slice(v6_contract_binary, 0, -METADATA_SIZE);
|
||||||
|
expect(actualBinaryWithoutMetadata).to.eq(expectedBinaryWithoutMetadata);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
37
packages/sol-compiler/test/fixtures/contracts/V6Contract.sol
vendored
Normal file
37
packages/sol-compiler/test/fixtures/contracts/V6Contract.sol
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
Copyright 2020 ZeroEx Intl.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
pragma solidity 0.6.4;
|
||||||
|
|
||||||
|
|
||||||
|
contract V6Contract {
|
||||||
|
|
||||||
|
uint256 private _privateNumber;
|
||||||
|
|
||||||
|
constructor(uint256 privateNumber) public {
|
||||||
|
_privateNumber = privateNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
fallback() external {
|
||||||
|
revert('nope');
|
||||||
|
}
|
||||||
|
|
||||||
|
receive() payable external {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
2
packages/sol-compiler/test/fixtures/v6_contract_bin.ts
vendored
Normal file
2
packages/sol-compiler/test/fixtures/v6_contract_bin.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const v6_contract_binary =
|
||||||
|
'0x6080604052348015600f57600080fd5b5060405161011238038061011283398181016040526020811015603157600080fd5b8101908080519060200190929190505050806000819055505060ba806100586000396000f3fe608060405236600a57005b348015601557600080fd5b506040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260048152602001807f6e6f70650000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fdfea26469706673582212208084a572151fb41e40aa4d1197e387cba7b4f0cbd982e34682974038667b564764736f6c63430006040033';
|
@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "5.1.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add `version()` to `SolcInstance`",
|
||||||
|
"pr": 2532
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1581204851,
|
"timestamp": 1581204851,
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
|
@ -105,6 +105,7 @@ declare module 'solc' {
|
|||||||
findImports: (importPath: string) => ImportContents,
|
findImports: (importPath: string) => ImportContents,
|
||||||
): CompilationResult;
|
): CompilationResult;
|
||||||
compileStandardWrapper(input: string, findImports?: (importPath: string) => ImportContents): string;
|
compileStandardWrapper(input: string, findImports?: (importPath: string) => ImportContents): string;
|
||||||
|
version(): string;
|
||||||
}
|
}
|
||||||
export function loadRemoteVersion(
|
export function loadRemoteVersion(
|
||||||
versionName: string,
|
versionName: string,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user