Skip to content

Add irMinimum option #907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ module.exports = {
| Option <img width=200/>| Type <img width=200/> | Default <img width=1000/> | Description <img width=1000/> |
| ------ | ---- | ------- | ----------- |
| 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] |
Expand All @@ -113,9 +114,9 @@ module.exports = {
| onCompileComplete[<sup>*</sup>][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[<sup>*</sup>][14] | *Function* | | Hook run *after* the tests complete, *before* Istanbul reports are generated. [More...][23]|
| onIstanbulComplete[<sup>*</sup>][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] ). |


[<sup>*</sup> Advanced use][14]
Expand Down
1 change: 1 addition & 0 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
20 changes: 16 additions & 4 deletions plugins/hardhat.plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const {
// Toggled true for `coverage` task only.
let measureCoverage = false;
let configureYulOptimizer = false;
let irMinimum = false;
let instrumentedSources;
let optimizerDetails;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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));
Expand Down
16 changes: 16 additions & 0 deletions test/integration/standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions test/sources/projects/irMinimum/.solcover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
silent: process.env.SILENT ? true : false,
istanbulReporter: ['json-summary', 'text'],
irMinimum: true,
}
26 changes: 26 additions & 0 deletions test/sources/projects/irMinimum/contracts/IRMinimum.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}

16 changes: 16 additions & 0 deletions test/sources/projects/irMinimum/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -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,
};
29 changes: 29 additions & 0 deletions test/sources/projects/irMinimum/test/test_irMinimum.js
Original file line number Diff line number Diff line change
@@ -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
);
})
});