From 1ca6b4297eeff2e05c0793763ed52ea560b7e5f5 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Mon, 17 Jan 2022 07:41:19 -0800 Subject: [PATCH] Add solcOptimizerDetails option --- README.md | 30 ++------------ docs/faq.md | 41 ++++++++++++++++--- lib/api.js | 2 + plugins/hardhat.plugin.js | 20 ++++++--- test/integration/projects/solc-8/.solcover.js | 12 +++++- 5 files changed, 66 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 0f6d4128..57600a51 100644 --- a/README.md +++ b/README.md @@ -55,30 +55,6 @@ npx hardhat coverage [command-options] (Additional Hardhat-specific info can be found [here][37]) -### Buidler [Deprecated] - -**Add** the plugin in `buidler.config.js` ([Buidler docs][26]) -```javascript -usePlugin('solidity-coverage') - -module.exports = { - networks: { - coverage: { - url: 'http://localhost:8555' - } - }, -} -``` -**Run** -``` -npx buidler coverage --network coverage [command-options] -``` - -**Buidler Project Examples:** - -+ Simple: [buidler-metacoin][32] -+ More complex: [MolochDao/moloch][33] - ### @openzeppelin/test-environment OpenZeppelin have written their own coverage generation scripts for `test-environment` using the solidity-coverage API. @@ -132,6 +108,7 @@ module.exports = { | onTestsComplete[*][14] | *Function* | | Hook run *after* the tests complete, *before* Istanbul reports are generated. [More...][23]| | onIstanbulComplete[*][14] | *Function* | | Hook run *after* the Istanbul reports are generated, *before* the ganache server is shut down. Useful if you need to clean resources up. [More...][23]| | configureYulOptimizer | *Boolean* | false | (Experimental) Setting to `true` should resolve "stack too deep" compiler errors in large projects using ABIEncoderV2 | +| solcOptimizerDetails | *Object* | (Experimental) Must be used in combination with `configureYulOptimizer`Allows you define the [solc optimizer details][1001]. Useful if the default remedy for stack-too-deep errors doesn't work in your case (See FAQ below). | [* Advanced use][14] @@ -152,9 +129,8 @@ Common problems & questions: + [Running in CI][7] + [Running out of gas][13] + [Running out of time][6] ++ [Running out of stack] [1002] (Stack too deep) + [Running out of memory][5] -+ [Why are `require` statements highlighted as branch points?][8] - ## Example reports + [metacoin][9] (Istanbul HTML) @@ -239,4 +215,6 @@ $ yarn [36]: https://hardhat.org/ [37]: https://github.com/sc-forks/solidity-coverage/blob/master/HARDHAT_README.md [38]: https://github.com/sindresorhus/globby#globbing-patterns +[1001]: https://docs.soliditylang.org/en/v0.8.0/using-the-compiler.html#input-description +[1002]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md#running-out-of-stack diff --git a/docs/faq.md b/docs/faq.md index 9b73046b..a4ea6238 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -4,6 +4,7 @@ * [Continuous Integration](#continuous-integration) * [Running out of memory](#running-out-of-memory) * [Running out of time](#running-out-of-time) + * [Running out of stack](#running-out-of-stack) * [Notes on gas distortion](#notes-on-gas-distortion) * [Notes on branch coverage](#notes-on-branch-coverage) @@ -68,12 +69,12 @@ We use [Codecov.io][2] here as a coverage provider for our JS tests - they're gr If your target contains dozens (and dozens) of large contracts, you may run up against Node's memory cap during the contract compilation step. This can be addressed by setting the size of the memory space allocated to the command -when you run it. +when you run it. ``` // Truffle $ node --max-old-space-size=4096 ./node_modules/.bin/truffle run coverage [options] -// Buidler +// Buidler $ node --max-old-space-size=4096 ./node_modules/.bin/buidler coverage [options] ``` @@ -85,7 +86,7 @@ RuntimeError: memory access out of bounds at wasm-function[833]:1152 at wasm-function[147]:18 at wasm-function[21880]:5 - + // solc 0.5.x Downloading compiler version 0.5.16 * Line 1, Column 1 @@ -94,7 +95,7 @@ Downloading compiler version 0.5.16 Extra non-whitespace after JSON value. ``` -...try setting the `measureStatementCoverage` option to `false` in `.solcoverjs`. This will reduce the footprint of +...try setting the `measureStatementCoverage` option to `false` in `.solcoverjs`. This will reduce the footprint of the instrumentation solidity-coverage adds to your files. You'll still get line, branch and function coverage but the data Istanbul collects for statements will be omitted. @@ -121,13 +122,41 @@ module.exports = { } ``` +## Running out of stack + +If your project is large, complex and uses ABI encoder V2 or Solidity >= V8, you may see "stack too deep" compiler errors when using solidity-coverage. This happens because: + ++ solidity-coverage turns the solc optimizer off in order trace code execution correctly ++ some projects cannot compile unless the optimizer is turned on. + +Work-arounds for this problem are tracked below. (These are only available in hardhat. If you're using hardhat and none of them work for you, please open an issue.) + +**Work-around #1** ++ Set the `.solcoverjs` option `configureYulOptimizer` to `true`. + +**Work-around #2** ++ Set the `.solcoverjs` option: `configureYulOptimizer` to `true`. ++ Set the `.solcoverjs` option: `solcOptimizerDetails` to: + + ```js + { + peephole: false, + inliner: false, + jumpdestRemover: false, + orderLiterals: true, // <-- TRUE! Stack too deep when false + deduplicate: false, + cse: false, + constantOptimizer: false, + yul: false + } + ``` + ## Notes on gas distortion Solidity-coverage instruments by injecting statements into your code, increasing its execution costs. + If you are running gas usage simulations, they will **not be accurate**. + If you have hardcoded gas costs into your tests, some of them may **error**. -+ If your solidity logic constrains gas usage within narrow bounds, it may **fail**. ++ If your solidity logic constrains gas usage within narrow bounds, it may **fail**. + Solidity's `.send` and `.transfer` methods usually work fine though. Using `estimateGas` to calculate your gas costs or allowing your transactions to use the default gas @@ -153,6 +182,6 @@ Clearly, the coverage should be the same in these situations, as the code is (fu If an `assert` or `require` is marked with an `I` in the coverage report, then during your tests the conditional is never true. If it is marked with an `E`, then it is never false. [1]: https://coveralls.io/builds/25886294 -[2]: https://codecov.io/ +[2]: https://codecov.io/ [3]: https://user-images.githubusercontent.com/7332026/28502310-6851f79c-6fa4-11e7-8c80-c8fd80808092.png [4]: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-170.md diff --git a/lib/api.js b/lib/api.js index 13ad991f..33ce2b59 100644 --- a/lib/api.js +++ b/lib/api.js @@ -58,6 +58,8 @@ class API { this.istanbulFolder = config.istanbulFolder || false; this.istanbulReporter = config.istanbulReporter || ['html', 'lcov', 'text', 'json']; + this.solcOptimizerDetails = config.solcOptimizerDetails; + this.setLoggingLevel(config.silent); this.ui = new AppUI(this.log); } diff --git a/plugins/hardhat.plugin.js b/plugins/hardhat.plugin.js index d91509d4..0649acd8 100644 --- a/plugins/hardhat.plugin.js +++ b/plugins/hardhat.plugin.js @@ -19,7 +19,8 @@ const { // Toggled true for `coverage` task only. let measureCoverage = false; let configureYulOptimizer = false; -let instrumentedSources +let instrumentedSources; +let optimizerDetails; // UI for the task flags... const ui = new PluginUI(); @@ -63,11 +64,16 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_, // This is fixes a stack too deep bug in ABIEncoderV2 // Experimental because not sure this works as expected across versions.... if (configureYulOptimizer) { - settings.optimizer.details = { - yul: true, - yulDetails: { - stackAllocation: true, - }, + if (optimizerDetails === undefined) { + settings.optimizer.details = { + yul: true, + yulDetails: { + stackAllocation: true, + }, + } + // Other configurations may work as well. This loads custom details from .solcoverjs + } else { + settings.optimizer.details = optimizerDetails; } } } @@ -101,6 +107,8 @@ task("coverage", "Generates a code coverage report for tests") ui = new PluginUI(config.logger.log); api = new API(utils.loadSolcoverJS(config)); + optimizerDetails = api.solcOptimizerDetails; + // Catch interrupt signals process.on("SIGINT", nomiclabsUtils.finish.bind(null, config, api, true)); diff --git a/test/integration/projects/solc-8/.solcover.js b/test/integration/projects/solc-8/.solcover.js index fc5a56be..08268965 100644 --- a/test/integration/projects/solc-8/.solcover.js +++ b/test/integration/projects/solc-8/.solcover.js @@ -2,5 +2,15 @@ module.exports = { silent: process.env.SILENT ? true : false, skipFiles: ['skipped-folder'], istanbulReporter: ['json-summary', 'text'], - configureYulOptimizer: true + configureYulOptimizer: true, + solcOptimizerDetails: { + peephole: false, + inliner: false, + jumpdestRemover: false, + orderLiterals: true, // <-- TRUE! Stack too deep when false + deduplicate: false, + cse: false, + constantOptimizer: false, + yul: false + } }