From 2689cf4fb363855fdf1e0443dca7a8483239a476 Mon Sep 17 00:00:00 2001 From: Dimitar Bounov Date: Mon, 20 Sep 2021 16:22:37 +0300 Subject: [PATCH] Add support for user-passed compiler settings --- src/bin/compile.ts | 45 +++++- src/compile/utils.ts | 146 +++++++++++++----- test/integration/sol-ast-compile/common.ts | 1 + .../compiler-settings/invalid.spec.ts | 42 +++++ .../compiler-settings/valid.spec.ts | 65 ++++++++ 5 files changed, 250 insertions(+), 49 deletions(-) create mode 100644 test/integration/sol-ast-compile/compiler-settings/invalid.spec.ts create mode 100644 test/integration/sol-ast-compile/compiler-settings/valid.spec.ts diff --git a/src/bin/compile.ts b/src/bin/compile.ts index 4d275274..1308ded9 100644 --- a/src/bin/compile.ts +++ b/src/bin/compile.ts @@ -41,7 +41,7 @@ const cli = { "source" ], number: ["depth"], - string: ["mode", "compiler-version", "path-remapping", "xpath"], + string: ["mode", "compiler-version", "path-remapping", "xpath", "compiler-settings"], default: { depth: Number.MAX_SAFE_INTEGER, mode: modes[0], @@ -87,6 +87,9 @@ OPTIONS: - auto (try to detect suitable compiler version) Default value: ${cli.default["compiler-version"]} --path-remapping Path remapping input for Solc. + --compiler-settings Additional settings passed to the solc compiler in the form of a + JSON string (e.g. '{"optimizer": {"enabled": true, "runs": 200}}'). + Note the double quotes. For more details see https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description. --raw Print raw Solc compilation output. --with-sources When used with "raw", adds "source" property with source files content to the compiler artifact. @@ -119,6 +122,17 @@ OPTIONS: } const pathRemapping: string[] = args["path-remapping"] ? args["path-remapping"].split(";") : []; + let compilerSettings: any = undefined; + + if (args["compiler-settings"]) { + try { + compilerSettings = JSON.parse(args["compiler-settings"]); + } catch (e) { + throw new Error( + `Invalid compiler settings '${args["compiler-settings"]}'. Compiler settings must be a valid JSON object.(${e})` + ); + } + } let fileName = args._[0]; let result: CompileResult; @@ -137,8 +151,20 @@ OPTIONS: result = mode === "json" - ? compileJsonData(fileName, JSON.parse(content), compilerVersion, pathRemapping) - : compileSourceString(fileName, content, compilerVersion, pathRemapping); + ? compileJsonData( + fileName, + JSON.parse(content), + compilerVersion, + pathRemapping, + compilerSettings + ) + : compileSourceString( + fileName, + content, + compilerVersion, + pathRemapping, + compilerSettings + ); } else { fileName = path.resolve(process.cwd(), args._[0]); @@ -146,17 +172,22 @@ OPTIONS: const iFileName = fileName.toLowerCase(); if (iFileName.endsWith(".sol")) { - result = compileSol(fileName, compilerVersion, pathRemapping); + result = compileSol(fileName, compilerVersion, pathRemapping, compilerSettings); } else if (iFileName.endsWith(".json")) { - result = compileJson(fileName, compilerVersion, pathRemapping); + result = compileJson( + fileName, + compilerVersion, + pathRemapping, + compilerSettings + ); } else { throw new Error("Unable to auto-detect mode for the file name: " + fileName); } } else { result = mode === "json" - ? compileJson(fileName, compilerVersion, pathRemapping) - : compileSol(fileName, compilerVersion, pathRemapping); + ? compileJson(fileName, compilerVersion, pathRemapping, compilerSettings) + : compileSol(fileName, compilerVersion, pathRemapping, compilerSettings); } } } catch (e) { diff --git a/src/compile/utils.ts b/src/compile/utils.ts index 2e5891d9..47a347e0 100644 --- a/src/compile/utils.ts +++ b/src/compile/utils.ts @@ -55,41 +55,90 @@ export function getCompilerForVersion(version: string): any { ); } -type CompilerInputCreator = (fileName: string, content: string, remappings?: string[]) => any; - -const createCompiler04Input: CompilerInputCreator = (fileName, content, remappings?) => ({ - language: "Solidity", - sources: { - [fileName]: content - }, - settings: { - remappings, - outputSelection: { - "*": { - "*": ["*"], - "": ["*"] +type Solc04Input = { + language: "Solidity"; + sources: { [fileName: string]: string }; + settings: { remappings: string[]; outputSelection: any; [otherKeys: string]: any }; +}; + +type Solc05Input = { + language: "Solidity"; + sources: { [fileName: string]: { content: string } }; + settings: { remappings: string[]; outputSelection: any; [otherKeys: string]: any }; +}; + +function mergeCompilerSettings(input: T, settings: any): T { + if (settings !== undefined) { + for (const key in settings) { + if (key === "remappings" || key === "outputSelection") { + continue; } + + input.settings[key] = settings[key]; } } -}); -const createCompiler05Input: CompilerInputCreator = (fileName, content, remappings?) => ({ - language: "Solidity", - sources: { - [fileName]: { - content - } - }, - settings: { - remappings, - outputSelection: { - "*": { - "*": ["*"], - "": ["*"] + return input; +} + +type CompilerInputCreator = ( + fileName: string, + content: string, + remappings: string[], + compilerSettings: any +) => Solc04Input | Solc05Input; + +const createCompiler04Input: CompilerInputCreator = ( + fileName, + content, + remappings, + compilerSettings +) => + mergeCompilerSettings( + { + language: "Solidity", + sources: { + [fileName]: content + }, + settings: { + remappings, + outputSelection: { + "*": { + "*": ["*"], + "": ["*"] + } + } } - } - } -}); + }, + compilerSettings + ); + +const createCompiler05Input: CompilerInputCreator = ( + fileName, + content, + remappings, + compilerSettings +) => + mergeCompilerSettings( + { + language: "Solidity", + sources: { + [fileName]: { + content + } + }, + settings: { + remappings, + outputSelection: { + "*": { + "*": ["*"], + "": ["*"] + } + } + } + }, + compilerSettings + ); function consistentlyContainsOneOf( sources: { [key: string]: any }, @@ -234,26 +283,27 @@ export function compile( content: string, version: string, finder: ImportFinder, - remapping: string[] + remapping: string[], + compilerSettings?: any ): any { const compiler = getCompilerForVersion(version); if (satisfies(version, "0.4")) { - const input = createCompiler04Input(fileName, content, remapping); + const input = createCompiler04Input(fileName, content, remapping, compilerSettings); const output = compiler.compile(input, 1, finder); return output; } if (satisfies(version, "0.5")) { - const input = createCompiler05Input(fileName, content, remapping); + const input = createCompiler05Input(fileName, content, remapping, compilerSettings); const output = compiler.compile(JSON.stringify(input), finder); return JSON.parse(output); } const callbacks = { import: finder }; - const input = createCompiler05Input(fileName, content, remapping); + const input = createCompiler05Input(fileName, content, remapping, compilerSettings); const output = compiler.compile(JSON.stringify(input), callbacks); return JSON.parse(output); @@ -291,7 +341,8 @@ export function compileSourceString( fileName: string, sourceCode: string, version: string | CompilerVersionSelectionStrategy, - remapping: string[] + remapping: string[], + compilerSettings?: any ): CompileResult { const compilerVersionStrategy = getCompilerVersionStrategy(sourceCode, version); const files = new Map([[fileName, sourceCode]]); @@ -304,7 +355,14 @@ export function compileSourceString( satisfies(compilerVersion, "0.4") ? parsePathRemapping(remapping) : [] ); - const data = compile(fileName, sourceCode, compilerVersion, finder, remapping); + const data = compile( + fileName, + sourceCode, + compilerVersion, + finder, + remapping, + compilerSettings + ); const errors = detectCompileErrors(data); if (errors.length === 0) { @@ -320,18 +378,20 @@ export function compileSourceString( export function compileSol( fileName: string, version: string | CompilerVersionSelectionStrategy, - remapping: string[] + remapping: string[], + compilerSettings?: any ): CompileResult { const source = fse.readFileSync(fileName, { encoding: "utf-8" }); - return compileSourceString(fileName, source, version, remapping); + return compileSourceString(fileName, source, version, remapping, compilerSettings); } export function compileJsonData( fileName: string, data: any, version: string | CompilerVersionSelectionStrategy, - remapping: string[] + remapping: string[], + compilerSettings?: any ): CompileResult { const files = new Map(); @@ -377,7 +437,8 @@ export function compileJsonData( sourceCode, compilerVersion, finder, - remapping + remapping, + compilerSettings ); const errors = detectCompileErrors(compileData); @@ -400,9 +461,10 @@ export function compileJsonData( export function compileJson( fileName: string, version: string | CompilerVersionSelectionStrategy, - remapping: string[] + remapping: string[], + compilerSettings?: any ): CompileResult { const data = fse.readJSONSync(fileName); - return compileJsonData(fileName, data, version, remapping); + return compileJsonData(fileName, data, version, remapping, compilerSettings); } diff --git a/test/integration/sol-ast-compile/common.ts b/test/integration/sol-ast-compile/common.ts index 837123de..a6e3e602 100644 --- a/test/integration/sol-ast-compile/common.ts +++ b/test/integration/sol-ast-compile/common.ts @@ -24,6 +24,7 @@ export const options = [ "mode", "compiler-version", "path-remapping", + "compiler-settings", "raw", "with-sources", "tree", diff --git a/test/integration/sol-ast-compile/compiler-settings/invalid.spec.ts b/test/integration/sol-ast-compile/compiler-settings/invalid.spec.ts new file mode 100644 index 00000000..7454237a --- /dev/null +++ b/test/integration/sol-ast-compile/compiler-settings/invalid.spec.ts @@ -0,0 +1,42 @@ +import expect from "expect"; +import { SolAstCompileCommand, SolAstCompileExec } from "../common"; + +const sample = "test/samples/solidity/missing_pragma.sol"; +const badArgsSamples = [ + [ + [sample, "--compiler-settings", "{blahblah}"], + `Error: Invalid compiler settings '{blahblah}'. Compiler settings must be a valid JSON object.` + ] +]; + +for (const [args, expectedError] of badArgsSamples) { + const command = SolAstCompileCommand(...args); + + describe(command, () => { + let exitCode: number | null; + let outData = ""; + let errData = ""; + + before((done) => { + const result = SolAstCompileExec(...args); + + outData = result.stdout; + errData = result.stderr; + exitCode = result.status; + + done(); + }); + + it("Exit code is valid", () => { + expect(exitCode).toEqual(1); + }); + + it("STDERR is correct", () => { + expect(errData).toContain(expectedError); + }); + + it("STDOUT is empty", () => { + expect(outData).toEqual(""); + }); + }); +} diff --git a/test/integration/sol-ast-compile/compiler-settings/valid.spec.ts b/test/integration/sol-ast-compile/compiler-settings/valid.spec.ts new file mode 100644 index 00000000..ee9b0e7e --- /dev/null +++ b/test/integration/sol-ast-compile/compiler-settings/valid.spec.ts @@ -0,0 +1,65 @@ +import expect from "expect"; +import { SolAstCompileCommand, SolAstCompileExec } from "../common"; + +const sample = "test/samples/solidity/missing_pragma.sol"; + +const values = [ + "SourceUnit #5", + 'src: "0:38:0"', + + "ContractDefinition #4", + 'src: "0:37:0"', + 'name: "Test"', + + "VariableDeclaration #3", + 'src: "20:14:0"', + 'name: "some"', + 'typeString: "uint8"', + + "ElementaryTypeName #1", + 'src: "20:5:0"', + 'name: "uint8"', + + "Literal #2", + 'src: "33:1:0"', + 'value: "1"' +]; + +const args = [ + sample, + "--compiler-settings", + `{"optimizer": {"enabled": true, "runs": 1}}`, + "--compiler-version", + "0.6.0" +]; +const command = SolAstCompileCommand(...args); + +describe(command, () => { + let exitCode: number | null; + let outData: string; + let errData: string; + + before((done) => { + const result = SolAstCompileExec(...args); + + outData = result.stdout; + errData = result.stderr; + exitCode = result.status; + + done(); + }); + + it("Exit code is valid", () => { + expect(exitCode).toEqual(0); + }); + + it("STDERR is empty", () => { + expect(errData).toEqual(""); + }); + + it("STDOUT is correct", () => { + for (const value of values) { + expect(outData).toContain(value); + } + }); +});