diff --git a/README.md b/README.md index 284fa570..db48517c 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ module.exports = { | Option | Type | Default | Description | | ------ | ---- | ------- | ----------- | | skipFiles | *Array* | `[]` | Array of contracts or folders (with paths expressed relative to the `contracts` directory) that should be skipped when doing instrumentation.(ex: `[ "Routers", "Networks/Polygon.sol"]`) :warning: **RUN THE HARDHAT CLEAN COMMAND AFTER UPDATING THIS** | +| irMinimum | *Boolean* | `[]` | Speeds up test execution times when solc is run in `viaIR` mode. If your project successfully compiles while generating coverage with this option turned on (it may not!) it's worth using | | modifierWhitelist | *String[]* | `[]` | List of modifier names (ex: `onlyOwner`) to exclude from branch measurement. (Useful for modifiers which prepare something instead of acting as a gate.)) | | mocha | *Object* | `{ }` | [Mocha options][3] to merge into existing mocha config. `grep` and `invert` are useful for skipping certain tests under coverage using tags in the test descriptions. [More...][24]| | measureStatementCoverage | *boolean* | `true` | Computes statement (in addition to line) coverage. [More...][34] | @@ -113,9 +114,9 @@ module.exports = { | onCompileComplete[*][14] | *Function* | | Hook run *after* compilation completes, *before* tests are run. Useful if you have secondary compilation steps or need to modify built artifacts. [More...][23]| | 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 coverage task completes. Useful if you need to clean resources up. [More...][23]| -| **:warning: DEPRECATED** | | | | -| configureYulOptimizer | *Boolean* | false | **(Deprecated since 0.8.7)** Setting to `true` should resolve "stack too deep" compiler errors in large projects using ABIEncoderV2 | -| solcOptimizerDetails | *Object* | `undefined` |**(Deprecated since 0.8.7))** Must be used in combination with `configureYulOptimizer`. Allows you to configure solc's [optimizer details][1001]. Useful if the default remedy for stack-too-deep errors doesn't work in your case (See [FAQ: Running out of stack][1002] ). | +| **:warning: LOW LEVEL** | | | | +| configureYulOptimizer | *Boolean* | false | Setting to `true` lets you specify optimizer details (see next option). If no details are defined it defaults to turning on the yul optimizer and enabling stack allocation | +| solcOptimizerDetails | *Object* | `undefined` |Must be used in combination with `configureYulOptimizer`. Allows you to configure solc's [optimizer details][1001]. (See [FAQ: Running out of stack][1002] ). | [* Advanced use][14] diff --git a/lib/api.js b/lib/api.js index 79ea611a..368312d3 100644 --- a/lib/api.js +++ b/lib/api.js @@ -56,6 +56,7 @@ class API { this.viaIR = config.viaIR; this.usingSolcV4 = config.usingSolcV4; + this.irMinimum = config.irMinimum; this.solcOptimizerDetails = config.solcOptimizerDetails; this.setLoggingLevel(config.silent); diff --git a/plugins/hardhat.plugin.js b/plugins/hardhat.plugin.js index 9bb18fb4..d612eefa 100644 --- a/plugins/hardhat.plugin.js +++ b/plugins/hardhat.plugin.js @@ -15,6 +15,7 @@ const { // Toggled true for `coverage` task only. let measureCoverage = false; let configureYulOptimizer = false; +let irMinimum = false; let instrumentedSources; let optimizerDetails; @@ -70,12 +71,22 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_, // Beginning with v0.8.7, we let the optimizer run if viaIR is true and // instrument using `abi.encode(bytes8 covHash)`. Otherwise turn the optimizer off. - if (!settings.viaIR) settings.optimizer.enabled = false; - - // This sometimes fixed a stack-too-deep bug in ABIEncoderV2 for coverage plugin versions up to 0.8.6 + if (!settings.viaIR || irMinimum) settings.optimizer.enabled = false; + + // Almost identical to foundry's irMinimum option - may improve performance for projects compiling with viaIR + // Original discussion at: https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 + if (irMinimum) { + settings.optimizer.details = { + yul: true, + yulDetails: { + stackAllocation: true, + optimizerSteps: "", + }, + } + // LEGACY: This sometimes fixed a stack-too-deep bug in ABIEncoderV2 for coverage plugin versions up to 0.8.6 // Although issue should be fixed in 0.8.7, am leaving this option in because it may still be necessary // to configure optimizer details in some cases. - if (configureYulOptimizer) { + } else if (configureYulOptimizer) { if (optimizerDetails === undefined) { settings.optimizer.details = { yul: true, @@ -142,6 +153,7 @@ task("coverage", "Generates a code coverage report for tests") api = new API(utils.loadSolcoverJS(config)); optimizerDetails = api.solcOptimizerDetails; + irMinimum = api.irMinimum; // Catch interrupt signals process.on("SIGINT", nomiclabsUtils.finish.bind(null, config, api, true)); diff --git a/test/integration/standard.js b/test/integration/standard.js index a23b34e5..05a84945 100644 --- a/test/integration/standard.js +++ b/test/integration/standard.js @@ -380,6 +380,22 @@ describe('Hardhat Plugin: standard use cases', function() { verify.lineCoverage(expected); }) + it('compiles with irMinimum setting', async function(){ + mock.installFullProject('irMinimum'); + mock.hardhatSetupEnv(this); + + await this.env.run("coverage"); + + const expected = [ + { + file: mock.pathToContract(hardhatConfig, 'IRMinimum.sol'), + pct: 100 + } + ]; + + verify.lineCoverage(expected); + }) + it('locates .coverage_contracts correctly when dir is subfolder', async function(){ mock.installFullProject('contract-subfolders'); mock.hardhatSetupEnv(this); diff --git a/test/sources/projects/irMinimum/.solcover.js b/test/sources/projects/irMinimum/.solcover.js new file mode 100644 index 00000000..886d7c2f --- /dev/null +++ b/test/sources/projects/irMinimum/.solcover.js @@ -0,0 +1,5 @@ +module.exports = { + silent: process.env.SILENT ? true : false, + istanbulReporter: ['json-summary', 'text'], + irMinimum: true, +} diff --git a/test/sources/projects/irMinimum/contracts/IRMinimum.sol b/test/sources/projects/irMinimum/contracts/IRMinimum.sol new file mode 100644 index 00000000..8da38223 --- /dev/null +++ b/test/sources/projects/irMinimum/contracts/IRMinimum.sol @@ -0,0 +1,26 @@ +pragma solidity >=0.8.0 <0.9.0; + +contract ContractA { + // 15 fn args + 1 local variable assignment + // will trigger stack too deep error when optimizer is off. + function stackTooDeep( + uint _a, + uint _b, + uint _c, + uint _d, + uint _e, + uint _f, + uint _g, + uint _h, + uint _i, + uint _j, + uint _k, + uint _l, + uint _m, + uint _n, + uint _o + ) public { + uint x = _a; + } +} + diff --git a/test/sources/projects/irMinimum/hardhat.config.js b/test/sources/projects/irMinimum/hardhat.config.js new file mode 100644 index 00000000..d570af45 --- /dev/null +++ b/test/sources/projects/irMinimum/hardhat.config.js @@ -0,0 +1,16 @@ +require("@nomiclabs/hardhat-truffle5"); +require(__dirname + "/../plugins/nomiclabs.plugin"); + +module.exports = { + solidity: { + version: "0.8.28", + settings: { + optimizer: { + enabled: true + }, + evmVersion: "cancun", + viaIR: true + } + }, + logger: process.env.SILENT ? { log: () => {} } : console, +}; diff --git a/test/sources/projects/irMinimum/test/test_irMinimum.js b/test/sources/projects/irMinimum/test/test_irMinimum.js new file mode 100644 index 00000000..18e5860e --- /dev/null +++ b/test/sources/projects/irMinimum/test/test_irMinimum.js @@ -0,0 +1,29 @@ +const ContractA = artifacts.require("ContractA"); + +contract("contracta", function(accounts) { + let a,b; + + before(async () => { + a = await ContractA.new(); + }) + + it('a:stackTooDeep', async function(){ + await a.stackTooDeep( + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ); + }) +});