From a1855523541445ccbdc13bf01a6f61d001efff90 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Mon, 14 Apr 2025 15:26:20 -0700 Subject: [PATCH 1/7] Add irMinimum option --- plugins/hardhat.plugin.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/plugins/hardhat.plugin.js b/plugins/hardhat.plugin.js index 9bb18fb4..348b8805 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; @@ -72,10 +73,27 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_, // 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 + // + // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 + if (irMinimum) { + settings.optimizer.details = { + peephole: false, + inliner: false, + jumpdest_remover: false, + order_literals: false, + deduplicate: false, + cse: false, + constant_optimizer: false, + yul: true, + yul_details: { + stack_allocation: true, + optimizer_steps: "", + }, + } + // 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 +160,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)); From e94a1f189533ec57841e143cfe84602fe7d5e72e Mon Sep 17 00:00:00 2001 From: cgewecke Date: Mon, 14 Apr 2025 17:58:58 -0700 Subject: [PATCH 2/7] Add test, fix solc config --- lib/api.js | 1 + plugins/hardhat.plugin.js | 12 ++++---- test/integration/standard.js | 16 ++++++++++ test/sources/projects/irMinimum/.solcover.js | 5 ++++ .../irMinimum/contracts/IRMinimum.sol | 26 +++++++++++++++++ .../projects/irMinimum/hardhat.config.js | 16 ++++++++++ .../projects/irMinimum/test/test_irMinimum.js | 29 +++++++++++++++++++ 7 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 test/sources/projects/irMinimum/.solcover.js create mode 100644 test/sources/projects/irMinimum/contracts/IRMinimum.sol create mode 100644 test/sources/projects/irMinimum/hardhat.config.js create mode 100644 test/sources/projects/irMinimum/test/test_irMinimum.js 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 348b8805..ca5cd599 100644 --- a/plugins/hardhat.plugin.js +++ b/plugins/hardhat.plugin.js @@ -79,15 +79,15 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_, settings.optimizer.details = { peephole: false, inliner: false, - jumpdest_remover: false, - order_literals: false, + jumpdestRemover: false, + orderLiterals: false, deduplicate: false, cse: false, - constant_optimizer: false, + constantOptimizer: false, yul: true, - yul_details: { - stack_allocation: true, - optimizer_steps: "", + yulDetails: { + stackAllocation: false, + //optimizerSteps: "", }, } // LEGACY: This sometimes fixed a stack-too-deep bug in ABIEncoderV2 for coverage plugin versions up to 0.8.6 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 + ); + }) +}); From 47af49777bc4f0e5f8443858d3fc21cc430b3e75 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Mon, 14 Apr 2025 18:03:15 -0700 Subject: [PATCH 3/7] Fix config again, clarify comment --- plugins/hardhat.plugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/hardhat.plugin.js b/plugins/hardhat.plugin.js index ca5cd599..1613ef0c 100644 --- a/plugins/hardhat.plugin.js +++ b/plugins/hardhat.plugin.js @@ -73,8 +73,8 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_, // instrument using `abi.encode(bytes8 covHash)`. Otherwise turn the optimizer off. if (!settings.viaIR) settings.optimizer.enabled = false; - // - // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 + // 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 = { peephole: false, @@ -86,8 +86,8 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_, constantOptimizer: false, yul: true, yulDetails: { - stackAllocation: false, - //optimizerSteps: "", + stackAllocation: true, + optimizerSteps: "", }, } // LEGACY: This sometimes fixed a stack-too-deep bug in ABIEncoderV2 for coverage plugin versions up to 0.8.6 From 9937f3be44298e4a60ab3e660a3c4d7f2792633d Mon Sep 17 00:00:00 2001 From: cgewecke Date: Mon, 14 Apr 2025 18:25:30 -0700 Subject: [PATCH 4/7] Update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 284fa570..e825d5f6 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* | `[]` | May speed up test execution times when using `viaIR` compilation setting. If your project will compile with this solc config (it may not!) it's worth a try | | 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` may resolve "stack too deep" compiler errors when **NOT** using `viaIR` | +| 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] From d6cd3ae6d38b60e2f5ece8d4059a52cc06423650 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Tue, 15 Apr 2025 16:11:13 -0700 Subject: [PATCH 5/7] Keep optimizer off and minimize optimizer details --- README.md | 2 +- plugins/hardhat.plugin.js | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e825d5f6..7f0d21aa 100644 --- a/README.md +++ b/README.md @@ -95,7 +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* | `[]` | May speed up test execution times when using `viaIR` compilation setting. If your project will compile with this solc config (it may not!) it's worth a try | +| irMinimum | *Boolean* | `[]` | Speeds up test execution times when using `viaIR` compilation setting. If your project will compile with this solc config (it may not!) it's worth a try | | 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] | diff --git a/plugins/hardhat.plugin.js b/plugins/hardhat.plugin.js index 1613ef0c..d612eefa 100644 --- a/plugins/hardhat.plugin.js +++ b/plugins/hardhat.plugin.js @@ -71,19 +71,12 @@ 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; + 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 = { - peephole: false, - inliner: false, - jumpdestRemover: false, - orderLiterals: false, - deduplicate: false, - cse: false, - constantOptimizer: false, yul: true, yulDetails: { stackAllocation: true, From e63b9b774b4f3be23f51705df8b607b7c91e6073 Mon Sep 17 00:00:00 2001 From: cgewecke Date: Tue, 15 Apr 2025 17:14:50 -0700 Subject: [PATCH 6/7] README edits --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f0d21aa..30f39a40 100644 --- a/README.md +++ b/README.md @@ -95,7 +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 using `viaIR` compilation setting. If your project will compile with this solc config (it may not!) it's worth a try | +| irMinimum | *Boolean* | `[]` | Speeds up test execution times when using `viaIR` compilation setting. If your project will compile with this solc config (it may not!) this is worth a try | | 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] | @@ -115,7 +115,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 coverage task completes. Useful if you need to clean resources up. [More...][23]| | **:warning: LOW LEVEL** | | | | -| configureYulOptimizer | *Boolean* | false | Setting to `true` may resolve "stack too deep" compiler errors when **NOT** using `viaIR` | +| 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] ). | From 1c14ea46ba5f9a13e5767bf44f6564d968add14c Mon Sep 17 00:00:00 2001 From: cgewecke Date: Tue, 15 Apr 2025 17:20:51 -0700 Subject: [PATCH 7/7] More README edits --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 30f39a40..db48517c 100644 --- a/README.md +++ b/README.md @@ -95,7 +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 using `viaIR` compilation setting. If your project will compile with this solc config (it may not!) this is worth a try | +| 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] |