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,
|
||||
"version": "4.0.8",
|
||||
|
@ -36,13 +36,16 @@ const SEPARATOR = ',';
|
||||
: argv.contracts === DEFAULT_CONTRACTS_LIST
|
||||
? DEFAULT_CONTRACTS_LIST
|
||||
: argv.contracts.split(SEPARATOR);
|
||||
const opts = {
|
||||
contractsDir: argv.contractsDir,
|
||||
artifactsDir: argv.artifactsDir,
|
||||
contracts,
|
||||
isOfflineMode: process.env.SOLC_OFFLINE ? true : undefined,
|
||||
};
|
||||
const compiler = new Compiler(opts);
|
||||
const opts = _.omitBy(
|
||||
{
|
||||
contractsDir: argv.contractsDir,
|
||||
artifactsDir: argv.artifactsDir,
|
||||
contracts,
|
||||
isOfflineMode: process.env.SOLC_OFFLINE ? true : undefined,
|
||||
},
|
||||
v => v === undefined,
|
||||
);
|
||||
const compiler = new Compiler(await Compiler.getCompilerOptionsAsync(opts));
|
||||
if (argv.watch) {
|
||||
await compiler.watchAsync();
|
||||
} else {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { assert } from '@0x/assert';
|
||||
import {
|
||||
ContractSource,
|
||||
FallthroughResolver,
|
||||
FSResolver,
|
||||
NameResolver,
|
||||
@ -10,7 +11,6 @@ import {
|
||||
URLResolver,
|
||||
} from '@0x/sol-resolver';
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { execSync } from 'child_process';
|
||||
import * as chokidar from 'chokidar';
|
||||
import { CompilerOptions, ContractArtifact, ContractVersionData, StandardOutput } from 'ethereum-types';
|
||||
import * as fs from 'fs';
|
||||
@ -18,59 +18,46 @@ import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as pluralize from 'pluralize';
|
||||
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 {
|
||||
addHexPrefixToContractBytecode,
|
||||
compileDockerAsync,
|
||||
compileSolcJSAsync,
|
||||
CompiledSources,
|
||||
createDirIfDoesNotExistAsync,
|
||||
getContractArtifactIfExistsAsync,
|
||||
getDependencyNameToPackagePath,
|
||||
getSolcJSAsync,
|
||||
getSolcJSFromPath,
|
||||
getSolcJSReleasesAsync,
|
||||
getSolcJSVersionFromPath,
|
||||
getSourcesWithDependencies,
|
||||
getSourceTreeHash,
|
||||
makeContractPathsRelative,
|
||||
normalizeSolcVersion,
|
||||
parseSolidityVersionRange,
|
||||
printCompilationErrorsAndWarnings,
|
||||
} from './utils/compiler';
|
||||
import { constants } from './utils/constants';
|
||||
import { fsWrapper } from './utils/fs_wrapper';
|
||||
import { utils } from './utils/utils';
|
||||
|
||||
type TYPE_ALL_FILES_IDENTIFIER = '*';
|
||||
const ALL_CONTRACTS_IDENTIFIER = '*';
|
||||
const ALL_FILES_IDENTIFIER = '*';
|
||||
const DEFAULT_CONTRACTS_DIR = path.resolve('contracts');
|
||||
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;
|
||||
import { ContractContentsByPath, ImportPrefixRemappings, SolcWrapper } from './solc_wrapper';
|
||||
import { SolcWrapperV04 } from './solc_wrapper_v04';
|
||||
import { SolcWrapperV05 } from './solc_wrapper_v05';
|
||||
import { SolcWrapperV06 } from './solc_wrapper_v06';
|
||||
|
||||
// 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.
|
||||
const DEFAULT_COMPILER_SETTINGS: solc.CompilerSettings = {
|
||||
optimizer: {
|
||||
enabled: false,
|
||||
},
|
||||
outputSelection: {
|
||||
[ALL_FILES_IDENTIFIER]: {
|
||||
[ALL_CONTRACTS_IDENTIFIER]: ['abi', 'evm.bytecode.object'],
|
||||
},
|
||||
},
|
||||
export type TYPE_ALL_FILES_IDENTIFIER = '*';
|
||||
export const ALL_CONTRACTS_IDENTIFIER = '*';
|
||||
export const ALL_FILES_IDENTIFIER = '*';
|
||||
|
||||
const DEFAULT_COMPILER_OPTS: CompilerOptions = {
|
||||
contractsDir: path.resolve('contracts'),
|
||||
artifactsDir: path.resolve('artifacts'),
|
||||
contracts: ALL_CONTRACTS_IDENTIFIER as TYPE_ALL_FILES_IDENTIFIER,
|
||||
useDockerisedSolc: false,
|
||||
isOfflineMode: false,
|
||||
shouldSaveStandardInput: false,
|
||||
};
|
||||
const CONFIG_FILE = 'compiler.json';
|
||||
|
||||
interface VersionToInputs {
|
||||
[solcVersion: string]: {
|
||||
standardInput: solc.StandardInput;
|
||||
contractsToCompile: string[];
|
||||
};
|
||||
interface ContractsByVersion {
|
||||
[solcVersion: string]: ContractContentsByPath;
|
||||
}
|
||||
|
||||
interface ContractPathToData {
|
||||
@ -83,60 +70,74 @@ interface ContractData {
|
||||
contractName: string;
|
||||
}
|
||||
|
||||
// tslint:disable no-non-null-assertion
|
||||
/**
|
||||
* The Compiler facilitates compiling Solidity smart contracts and saves the results
|
||||
* to artifact files.
|
||||
*/
|
||||
export class Compiler {
|
||||
private readonly _opts: CompilerOptions;
|
||||
private readonly _resolver: Resolver;
|
||||
private readonly _nameResolver: NameResolver;
|
||||
private readonly _contractsDir: string;
|
||||
private readonly _compilerSettings: solc.CompilerSettings;
|
||||
private readonly _artifactsDir: string;
|
||||
private readonly _solcVersionIfExists: string | undefined;
|
||||
private readonly _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
|
||||
private readonly _useDockerisedSolc: boolean;
|
||||
private readonly _isOfflineMode: 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.
|
||||
* @param opts Optional compiler options
|
||||
* @return An instance of the Compiler class.
|
||||
*/
|
||||
constructor(opts?: CompilerOptions) {
|
||||
const passedOpts = opts || {};
|
||||
assert.doesConformToSchema('opts', passedOpts, compilerOptionsSchema);
|
||||
// TODO: Look for config file in parent directories if not found in current directory
|
||||
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);
|
||||
constructor(opts: CompilerOptions = {}) {
|
||||
this._opts = { ...DEFAULT_COMPILER_OPTS, ...opts };
|
||||
assert.doesConformToSchema('opts', this._opts, compilerOptionsSchema);
|
||||
this._contractsDir = path.resolve(this._opts.contractsDir!);
|
||||
this._solcVersionIfExists =
|
||||
process.env.SOLCJS_PATH !== undefined
|
||||
? getSolcJSVersionFromPath(process.env.SOLCJS_PATH)
|
||||
: passedOpts.solcVersion || config.solcVersion;
|
||||
this._compilerSettings = {
|
||||
...DEFAULT_COMPILER_SETTINGS,
|
||||
...config.compilerSettings,
|
||||
...passedOpts.compilerSettings,
|
||||
};
|
||||
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._opts.solcVersion;
|
||||
this._artifactsDir = this._opts.artifactsDir!;
|
||||
this._specifiedContracts = this._opts.contracts!;
|
||||
this._isOfflineMode = this._opts.isOfflineMode!;
|
||||
this._shouldSaveStandardInput = this._opts.shouldSaveStandardInput!;
|
||||
this._nameResolver = new NameResolver(this._contractsDir);
|
||||
const resolver = new FallthroughResolver();
|
||||
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;
|
||||
this._resolver = Compiler._createDefaultResolver(this._contractsDir, this._nameResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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:
|
||||
@ -157,6 +159,10 @@ export class Compiler {
|
||||
const promisedOutputs = this._compileContractsAsync(this.getContractNamesToCompile(), false);
|
||||
return promisedOutputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch contracts in the current project directory and recompile on changes.
|
||||
*/
|
||||
public async watchAsync(): Promise<void> {
|
||||
console.clear(); // tslint:disable-line:no-console
|
||||
logUtils.logWithTime('Starting compilation in watch mode...');
|
||||
@ -183,7 +189,7 @@ export class Compiler {
|
||||
watcher.add(pathsToWatch);
|
||||
};
|
||||
await onFileChangedAsync();
|
||||
watcher.on('change', (changedFilePath: string) => {
|
||||
watcher.on('change', () => {
|
||||
console.clear(); // tslint:disable-line:no-console
|
||||
logUtils.logWithTime('File change detected. Starting incremental compilation...');
|
||||
// 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
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of contracts to compile.
|
||||
*/
|
||||
@ -206,6 +213,7 @@ export class Compiler {
|
||||
}
|
||||
return contractNamesToCompile;
|
||||
}
|
||||
|
||||
private _getPathsToWatch(): string[] {
|
||||
const contractNames = this.getContractNamesToCompile();
|
||||
const spyResolver = new SpyResolver(this._resolver);
|
||||
@ -220,6 +228,7 @@ export class Compiler {
|
||||
const pathsToWatch = _.uniq(spyResolver.resolvedContractSources.map(cs => cs.absolutePath));
|
||||
return pathsToWatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles contracts, and, if `shouldPersist` is true, saves artifacts to artifactsDir.
|
||||
* @param fileName Name of contract with '.sol' extension.
|
||||
@ -227,13 +236,12 @@ export class Compiler {
|
||||
*/
|
||||
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 = {};
|
||||
|
||||
const contractsByVersion: ContractsByVersion = {};
|
||||
// map contract paths to data about them for later verification and persistence
|
||||
const contractPathToData: ContractPathToData = {};
|
||||
|
||||
const solcJSReleases = await getSolcJSReleasesAsync(this._isOfflineMode);
|
||||
const resolvedContractSources = [];
|
||||
const resolvedContractSources: ContractSource[] = [];
|
||||
for (const contractName of contractNames) {
|
||||
const spyResolver = new SpyResolver(this._resolver);
|
||||
const contractSource = spyResolver.resolve(contractName);
|
||||
@ -246,161 +254,155 @@ export class Compiler {
|
||||
if (!this._shouldCompile(contractData)) {
|
||||
continue;
|
||||
}
|
||||
contractPathToData[contractSource.path] = contractData;
|
||||
const solcVersion =
|
||||
this._solcVersionIfExists === undefined
|
||||
? semver.maxSatisfying(_.keys(solcJSReleases), parseSolidityVersionRange(contractSource.source))
|
||||
: this._solcVersionIfExists;
|
||||
if (solcVersion === null) {
|
||||
contractPathToData[contractSource.absolutePath] = contractData;
|
||||
let solcVersion: string | undefined;
|
||||
if (this._solcVersionIfExists) {
|
||||
solcVersion = this._solcVersionIfExists;
|
||||
} else {
|
||||
const solidityVersion = semver.maxSatisfying(
|
||||
_.keys(solcJSReleases),
|
||||
parseSolidityVersionRange(contractSource.source),
|
||||
);
|
||||
if (solidityVersion) {
|
||||
solcVersion = normalizeSolcVersion(solcJSReleases[solidityVersion]);
|
||||
}
|
||||
}
|
||||
if (solcVersion === undefined) {
|
||||
throw new Error(
|
||||
`Couldn't find any solidity version satisfying the constraint ${parseSolidityVersionRange(
|
||||
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
|
||||
for (const resolvedContractSource of spyResolver.resolvedContractSources) {
|
||||
versionToInputs[solcVersion].standardInput.sources[resolvedContractSource.absolutePath] = {
|
||||
content: resolvedContractSource.source,
|
||||
};
|
||||
contractsByVersion[solcVersion] = contractsByVersion[solcVersion] || {};
|
||||
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[] = [];
|
||||
for (const solcVersion of _.keys(versionToInputs)) {
|
||||
const input = versionToInputs[solcVersion];
|
||||
logUtils.warn(
|
||||
`Compiling ${input.contractsToCompile.length} contracts (${
|
||||
input.contractsToCompile
|
||||
}) with Solidity v${solcVersion}...`,
|
||||
);
|
||||
let compilerOutput;
|
||||
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,
|
||||
);
|
||||
const compilationResults = await Promise.all(
|
||||
versions.map(async solcVersion => {
|
||||
const contracts = contractsByVersion[solcVersion];
|
||||
logUtils.warn(
|
||||
`Compiling ${Object.keys(contracts).length} contracts (${Object.keys(contracts).map(p =>
|
||||
path.basename(p),
|
||||
)}) with Solidity ${solcVersion}...`,
|
||||
);
|
||||
return this._getSolcWrapperForVersion(solcVersion).compileAsync(contracts, importRemappings);
|
||||
}),
|
||||
);
|
||||
|
||||
for (const contractPath of input.contractsToCompile) {
|
||||
const contractName = contractPathToData[contractPath].contractName;
|
||||
if (compilerOutput.contracts[contractPath] !== undefined) {
|
||||
const compiledContract = compilerOutput.contracts[contractPath][contractName];
|
||||
if (compiledContract === undefined) {
|
||||
throw new Error(
|
||||
`Contract ${contractName} not found in ${contractPath}. Please make sure your contract has the same name as it's file name`,
|
||||
);
|
||||
}
|
||||
if (this._shouldSaveStandardInput) {
|
||||
await fsWrapper.writeFileAsync(
|
||||
`${this._artifactsDir}/${contractName}.input.json`,
|
||||
utils.stringifyWithFormatting(input.standardInput),
|
||||
);
|
||||
}
|
||||
addHexPrefixToContractBytecode(compiledContract);
|
||||
}
|
||||
|
||||
if (shouldPersist) {
|
||||
await this._persistCompiledContractAsync(
|
||||
contractPath,
|
||||
contractPathToData[contractPath].currentArtifactIfExists,
|
||||
contractPathToData[contractPath].sourceTreeHashHex,
|
||||
contractName,
|
||||
fullSolcVersion,
|
||||
compilerOutput,
|
||||
if (shouldPersist) {
|
||||
await Promise.all(
|
||||
versions.map(async (solcVersion, i) => {
|
||||
const compilationResult = compilationResults[i];
|
||||
const contracts = contractsByVersion[solcVersion];
|
||||
// tslint:disable-next-line: forin
|
||||
await Promise.all(
|
||||
Object.keys(contracts).map(async contractPath => {
|
||||
const contractData = contractPathToData[contractPath];
|
||||
if (contractData === undefined) {
|
||||
return;
|
||||
}
|
||||
const { contractName } = contractData;
|
||||
const compiledContract = compilationResult.output.contracts[contractPath][contractName];
|
||||
if (compiledContract === undefined) {
|
||||
throw new Error(
|
||||
`Contract ${contractName} not found in ${contractPath}. Please make sure your contract has the same name as it's file name`,
|
||||
);
|
||||
}
|
||||
await this._persistCompiledContractAsync(
|
||||
contractPath,
|
||||
contractPathToData[contractPath].currentArtifactIfExists,
|
||||
contractPathToData[contractPath].sourceTreeHashHex,
|
||||
contractName,
|
||||
solcVersion,
|
||||
contracts,
|
||||
compilationResult.input,
|
||||
compilationResult.output,
|
||||
importRemappings,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
compilerOutputs.push(compilerOutput);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return compilerOutputs;
|
||||
return compilationResults.map(r => r.output);
|
||||
}
|
||||
|
||||
private _shouldCompile(contractData: ContractData): boolean {
|
||||
if (contractData.currentArtifactIfExists === undefined) {
|
||||
return true;
|
||||
} else {
|
||||
const currentArtifact = contractData.currentArtifactIfExists as ContractArtifact;
|
||||
const solc = this._getSolcWrapperForVersion(currentArtifact.compiler.version);
|
||||
const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
|
||||
const didCompilerSettingsChange = !_.isEqual(
|
||||
_.omit(currentArtifact.compiler.settings, 'remappings'),
|
||||
_.omit(this._compilerSettings, 'remappings'),
|
||||
);
|
||||
const didCompilerSettingsChange = solc.areCompilerSettingsDifferent(currentArtifact.compiler.settings);
|
||||
const didSourceChange = currentArtifact.sourceTreeHashHex !== contractData.sourceTreeHashHex;
|
||||
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(
|
||||
contractPath: string,
|
||||
currentArtifactIfExists: ContractArtifact | void,
|
||||
sourceTreeHashHex: string,
|
||||
contractName: string,
|
||||
fullSolcVersion: string,
|
||||
compilerOutput: solc.StandardOutput,
|
||||
solcVersion: string,
|
||||
sourcesByPath: ContractContentsByPath,
|
||||
compilerInput: StandardInput,
|
||||
compilerOutput: StandardOutput,
|
||||
importRemappings: ImportPrefixRemappings,
|
||||
): 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 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 } = getSourcesWithDependencies(
|
||||
this._resolver,
|
||||
contractPath,
|
||||
compilerOutput.sources,
|
||||
);
|
||||
const allSources: CompiledSources = {};
|
||||
// tslint:disable-next-line: forin
|
||||
for (const sourceContractPath in sourcesByPath) {
|
||||
const content = sourcesByPath[sourceContractPath];
|
||||
const { id } = compilerOutput.sources[sourceContractPath];
|
||||
allSources[sourceContractPath] = { id, content };
|
||||
}
|
||||
const usedSources = getSourcesWithDependencies(contractPath, allSources, importRemappings);
|
||||
|
||||
const contractVersion: ContractVersionData = {
|
||||
compilerOutput: compiledContract,
|
||||
sources,
|
||||
sourceCodes,
|
||||
sourceTreeHashHex,
|
||||
sources: _.mapValues(usedSources, ({ id }) => ({ id })),
|
||||
sourceCodes: _.mapValues(usedSources, ({ content }) => content),
|
||||
compiler: {
|
||||
name: 'solc',
|
||||
version: fullSolcVersion,
|
||||
settings: this._compilerSettings,
|
||||
version: solcVersion,
|
||||
settings: compilerInput.settings,
|
||||
},
|
||||
};
|
||||
|
||||
@ -424,5 +426,20 @@ export class Compiler {
|
||||
const currentArtifactPath = `${this._artifactsDir}/${contractName}.json`;
|
||||
await fsWrapper.writeFileAsync(currentArtifactPath, artifactString);
|
||||
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 { fetchAsync, logUtils } from '@0x/utils';
|
||||
import chalk from 'chalk';
|
||||
import { execSync } from 'child_process';
|
||||
import { exec, spawn } from 'child_process';
|
||||
import { ContractArtifact } from 'ethereum-types';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as requireFromString from 'require-from-string';
|
||||
import * as solc from 'solc';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { fsWrapper } from './fs_wrapper';
|
||||
@ -152,18 +153,39 @@ export async function compileSolcJSAsync(
|
||||
|
||||
/**
|
||||
* Compiles the contracts and prints errors/warnings
|
||||
* @param solcVersion Version of a solc compiler
|
||||
* @param solidityVersion Solidity version
|
||||
* @param standardInput Solidity standard JSON input
|
||||
*/
|
||||
export async function compileDockerAsync(
|
||||
solcVersion: string,
|
||||
solidityVersion: string,
|
||||
standardInput: solc.StandardInput,
|
||||
): Promise<solc.StandardOutput> {
|
||||
const standardInputStr = JSON.stringify(standardInput, null, 2);
|
||||
const dockerCommand = `docker run -i -a stdin -a stdout -a stderr ethereum/solc:${solcVersion} solc --standard-json`;
|
||||
const standardOutputStr = execSync(dockerCommand, { input: standardInputStr }).toString();
|
||||
const compiled: solc.StandardOutput = JSON.parse(standardOutputStr);
|
||||
return compiled;
|
||||
// prettier-ignore
|
||||
const dockerArgs = [
|
||||
'run',
|
||||
'-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
|
||||
* 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
|
||||
* `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`).
|
||||
* Mapping of absolute contract path to compilation ID and source code.
|
||||
*/
|
||||
export interface CompiledSources {
|
||||
[sourcePath: string]: { id: number; content: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
resolver: Resolver,
|
||||
contractPath: string,
|
||||
fullSources: { [sourceName: string]: { id: number } },
|
||||
): { sourceCodes: { [sourceName: string]: string }; sources: { [sourceName: string]: { id: number } } } {
|
||||
const sources = { [contractPath]: fullSources[contractPath] };
|
||||
const sourceCodes = { [contractPath]: resolver.resolve(contractPath).source };
|
||||
sourcesByAbsolutePath: CompiledSources,
|
||||
importRemappings: { [prefix: string]: string },
|
||||
): CompiledImports {
|
||||
const compiledImports = { [`./${path.basename(contractPath)}`]: sourcesByAbsolutePath[contractPath] };
|
||||
recursivelyGatherDependencySources(
|
||||
resolver,
|
||||
contractPath,
|
||||
sourceCodes[contractPath],
|
||||
fullSources,
|
||||
sources,
|
||||
sourceCodes,
|
||||
path.dirname(contractPath),
|
||||
sourcesByAbsolutePath,
|
||||
importRemappings,
|
||||
compiledImports,
|
||||
);
|
||||
return { sourceCodes, sources };
|
||||
return compiledImports;
|
||||
}
|
||||
|
||||
function recursivelyGatherDependencySources(
|
||||
resolver: Resolver,
|
||||
contractPath: string,
|
||||
contractSource: string,
|
||||
fullSources: { [sourceName: string]: { id: number } },
|
||||
sourcesToAppendTo: { [sourceName: string]: { id: number } },
|
||||
sourceCodesToAppendTo: { [sourceName: string]: string },
|
||||
rootDir: string,
|
||||
sourcesByAbsolutePath: CompiledSources,
|
||||
importRemappings: { [prefix: string]: string },
|
||||
compiledImports: CompiledImports,
|
||||
visitedAbsolutePaths: { [absPath: string]: boolean } = {},
|
||||
importRootDir?: string,
|
||||
): void {
|
||||
if (visitedAbsolutePaths[contractPath]) {
|
||||
return;
|
||||
} else {
|
||||
visitedAbsolutePaths[contractPath] = true;
|
||||
}
|
||||
const contractSource = sourcesByAbsolutePath[contractPath].content;
|
||||
const importStatementMatches = contractSource.match(/\nimport[^;]*;/g);
|
||||
if (importStatementMatches === null) {
|
||||
return;
|
||||
}
|
||||
const lastPathSeparatorPos = contractPath.lastIndexOf('/');
|
||||
const contractFolder = lastPathSeparatorPos === -1 ? '' : contractPath.slice(0, lastPathSeparatorPos + 1);
|
||||
for (const importStatementMatch of importStatementMatches) {
|
||||
const importPathMatches = importStatementMatch.match(/\"([^\"]*)\"/);
|
||||
if (importPathMatches === null || importPathMatches.length === 0) {
|
||||
@ -294,59 +333,54 @@ function recursivelyGatherDependencySources(
|
||||
}
|
||||
|
||||
let importPath = importPathMatches[1];
|
||||
// HACK(albrow): 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);
|
||||
let absPath = importPath;
|
||||
let _importRootDir = importRootDir;
|
||||
if (importPath.startsWith('.')) {
|
||||
/**
|
||||
* Some imports path are relative ("../Token.sol", "./Wallet.sol")
|
||||
* while others are absolute ("Token.sol", "@0x/contracts/Wallet.sol")
|
||||
* And we need to append the base path for relative imports.
|
||||
*/
|
||||
importPath = path.resolve(`/${contractFolder}`, importPath).replace('/', '');
|
||||
}
|
||||
|
||||
if (sourcesToAppendTo[importPath] === undefined) {
|
||||
sourcesToAppendTo[importPath] = { id: fullSources[importPath].id };
|
||||
sourceCodesToAppendTo[importPath] = resolver.resolve(importPath).source;
|
||||
|
||||
recursivelyGatherDependencySources(
|
||||
resolver,
|
||||
importPath,
|
||||
resolver.resolve(importPath).source,
|
||||
fullSources,
|
||||
sourcesToAppendTo,
|
||||
sourceCodesToAppendTo,
|
||||
);
|
||||
absPath = path.join(contractFolder, importPath);
|
||||
if (_importRootDir) {
|
||||
// If there's an `_importRootDir`, we're in a package, so express
|
||||
// the import path as within the package.
|
||||
importPath = path.join(_importRootDir, importPath);
|
||||
} else {
|
||||
// Express relative imports paths as paths from the root directory.
|
||||
importPath = path.relative(rootDir, absPath);
|
||||
if (!importPath.startsWith('.')) {
|
||||
importPath = `./${importPath}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [prefix, replacement] of Object.entries(importRemappings)) {
|
||||
if (importPath.startsWith(prefix)) {
|
||||
absPath = `${replacement}${importPath.substr(prefix.length)}`;
|
||||
_importRootDir = path.dirname(importPath);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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,
|
||||
* 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
|
||||
*/
|
||||
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 fullSolcVersion = solcJSReleases[solcVersion];
|
||||
const fullSolcVersion = solcJSReleases[solidityVersion];
|
||||
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);
|
||||
let solcjs: string;
|
||||
@ -383,7 +417,7 @@ export function getSolcJSFromPath(modulePath: string): solc.SolcInstance {
|
||||
* @param path The path to the solc module.
|
||||
*/
|
||||
export function getSolcJSVersionFromPath(modulePath: string): string {
|
||||
return require(modulePath).version();
|
||||
return normalizeSolcVersion(require(modulePath).version());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -437,3 +471,47 @@ export function getDependencyNameToPackagePath(
|
||||
});
|
||||
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 { CompilerOptions, ContractArtifact } from 'ethereum-types';
|
||||
import 'mocha';
|
||||
import { join } from 'path';
|
||||
|
||||
import { Compiler } from '../src/compiler';
|
||||
import { fsWrapper } from '../src/utils/fs_wrapper';
|
||||
|
||||
import { exchange_binary } from './fixtures/exchange_bin';
|
||||
import { v6_contract_binary } from './fixtures/v6_contract_bin';
|
||||
import { chaiSetup } from './util/chai_setup';
|
||||
import { constants } from './util/constants';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const METADATA_SIZE = 43;
|
||||
|
||||
describe('#Compiler', function(): void {
|
||||
this.timeout(constants.timeoutMs); // tslint:disable-line:no-invalid-this
|
||||
const artifactsDir = `${__dirname}/fixtures/artifacts`;
|
||||
@ -41,14 +44,12 @@ describe('#Compiler', function(): void {
|
||||
};
|
||||
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
|
||||
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
|
||||
// The last 43 bytes of the binaries are metadata which may not be equivalent
|
||||
const metadataByteLength = 43;
|
||||
const metadataHexLength = metadataByteLength * 2;
|
||||
const unlinkedBinaryWithoutMetadata = exchangeArtifact.compilerOutput.evm.bytecode.object.slice(
|
||||
2,
|
||||
-metadataHexLength,
|
||||
const unlinkedBinaryWithoutMetadata = hexUtils.slice(
|
||||
exchangeArtifact.compilerOutput.evm.bytecode.object,
|
||||
0,
|
||||
-METADATA_SIZE,
|
||||
);
|
||||
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -metadataHexLength);
|
||||
const exchangeBinaryWithoutMetadata = hexUtils.slice(exchange_binary, 0, -METADATA_SIZE);
|
||||
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
|
||||
});
|
||||
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');
|
||||
}
|
||||
});
|
||||
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,
|
||||
"version": "5.0.2",
|
||||
|
@ -105,6 +105,7 @@ declare module 'solc' {
|
||||
findImports: (importPath: string) => ImportContents,
|
||||
): CompilationResult;
|
||||
compileStandardWrapper(input: string, findImports?: (importPath: string) => ImportContents): string;
|
||||
version(): string;
|
||||
}
|
||||
export function loadRemoteVersion(
|
||||
versionName: string,
|
||||
|
Loading…
x
Reference in New Issue
Block a user