Skip to content

Commit 1da9510

Browse files
committed
First draft
1 parent d3a5b37 commit 1da9510

File tree

40 files changed

+282
-53
lines changed

40 files changed

+282
-53
lines changed

.circleci/config.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,13 @@ jobs:
3232
command: |
3333
yarn
3434
- run:
35-
name: Run tests
35+
name: Tests ( optimizer.enabled=false )
3636
command: |
3737
npm run test:ci
38+
- run:
39+
name: Tests ( viaIR=true )
40+
command: |
41+
npm run test:ci:viaIR
3842
- run:
3943
name: Upload coverage
4044
command: |

lib/api.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class API {
5454
this.istanbulFolder = config.istanbulFolder || false;
5555
this.istanbulReporter = config.istanbulReporter || ['html', 'lcov', 'text', 'json'];
5656

57+
this.viaIR = config.viaIR;
5758
this.solcOptimizerDetails = config.solcOptimizerDetails;
5859

5960
this.setLoggingLevel(config.silent);
@@ -92,6 +93,8 @@ class API {
9293
target.canonicalPath
9394
);
9495

96+
//console.log('instrumented.contract --> ' + instrumented.contract);
97+
9598
this.coverage.addContract(instrumented, target.canonicalPath);
9699

97100
outputs.push({
@@ -176,7 +179,7 @@ class API {
176179
// Hardhat
177180
async attachToHardhatVM(provider){
178181
const self = this;
179-
this.collector = new DataCollector(this.instrumenter.instrumentationData);
182+
this.collector = new DataCollector(this.instrumenter.instrumentationData, this.viaIR);
180183

181184
if ('init' in provider) {
182185
// Newer versions of Hardhat initialize the provider lazily, so we need to

lib/collector.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,30 @@
33
* coverage map constructed by the Instrumenter.
44
*/
55
class DataCollector {
6-
constructor(instrumentationData={}){
6+
constructor(instrumentationData={}, viaIR){
77
this.instrumentationData = instrumentationData;
88

99
this.validOpcodes = {
1010
"PUSH1": true,
11+
"DUP1": viaIR,
12+
"DUP2": viaIR,
13+
"DUP3": viaIR,
14+
"DUP4": viaIR,
15+
"DUP5": viaIR,
16+
"DUP6": viaIR,
17+
"DUP7": viaIR,
18+
"DUP8": viaIR,
19+
"DUP9": viaIR,
20+
"DUP10": viaIR,
21+
"DUP11": viaIR,
22+
"DUP12": viaIR,
23+
"DUP13": viaIR,
24+
"DUP14": viaIR,
25+
"DUP15": viaIR,
26+
"DUP16": viaIR,
1127
}
28+
29+
this.lastHash = null;
1230
}
1331

1432
/**
@@ -22,6 +40,8 @@ class DataCollector {
2240
const idx = info.stack.length - 1;
2341
let hash = '0x' + info.stack[idx].toString(16);
2442
this._registerHash(hash)
43+
44+
// console.log('hash: ' + info.opcode.name + " " + hash)
2545
}
2646
} catch (err) { /*Ignore*/ };
2747
}
@@ -45,8 +65,15 @@ class DataCollector {
4565
_registerHash(hash){
4666
hash = this._normalizeHash(hash);
4767

68+
//console.log('normalized: ' + hash);
69+
4870
if(this.instrumentationData[hash]){
49-
this.instrumentationData[hash].hits++;
71+
72+
// abi.encode (used to circumvent viaIR) puts the hash on the stack twice
73+
if (this.lastHash !== hash) {
74+
this.lastHash = hash;
75+
this.instrumentationData[hash].hits++
76+
}
5077
}
5178
}
5279

lib/injector.js

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const web3Utils = require("web3-utils");
22

33
class Injector {
4-
constructor(){
4+
constructor(viaIR){
5+
this.viaIR = viaIR;
56
this.hashCounter = 0;
67
this.modifierCounter = 0;
78
this.modifiers = {};
@@ -23,7 +24,9 @@ class Injector {
2324
case 'modifier':
2425
return ` ${this._getModifierIdentifier(id)} `;
2526
default:
26-
return `${this._getDefaultMethodIdentifier(id)}(${hash}); /* ${type} */ \n`;
27+
return (this.viaIR)
28+
? `${this._getAbiEncodeStatementHash(hash)} /* ${type} */ \n`
29+
: `${this._getDefaultMethodIdentifier(id)}(${hash}); /* ${type} */ \n`;
2730
}
2831
}
2932

@@ -51,6 +54,17 @@ class Injector {
5154
return `c_mod${web3Utils.keccak256(id).slice(2,10)}`
5255
}
5356

57+
// Way to get hash on the stack with viaIR (which seems to ignore abi.encode (v0.8.24))
58+
// If you visiting solidity-coverage & understand what's happening here, please do not mention
59+
// this obscure and irrelevant optimization opportunity in the issues at ethereum/solidity
60+
_getAbiEncodeStatementHash(hash){
61+
return `abi.encode(${hash}); `
62+
}
63+
64+
_getAbiEncodeStatementVar(hash){
65+
return `abi.encode(c__${hash}); `
66+
}
67+
5468
_getInjectionComponents(contract, injectionPoint, id, type){
5569
const { start, end } = this._split(contract, injectionPoint);
5670
const hash = this._getHash(id)
@@ -73,7 +87,10 @@ class Injector {
7387
_getDefaultMethodDefinition(id){
7488
const hash = web3Utils.keccak256(id).slice(2,10);
7589
const method = this._getDefaultMethodIdentifier(id);
76-
return `\nfunction ${method}(bytes8 c__${hash}) internal pure {}\n`;
90+
91+
return (this.viaIR)
92+
? ``
93+
: `\nfunction ${method}(bytes8 c__${hash}) internal pure {}\n`;
7794
}
7895

7996
/**
@@ -85,7 +102,11 @@ class Injector {
85102
_getFileScopedHashMethodDefinition(id, contract){
86103
const hash = web3Utils.keccak256(id).slice(2,10);
87104
const method = this._getDefaultMethodIdentifier(id);
88-
return `\nfunction ${method}(bytes8 c__${hash}) pure {}\n`;
105+
const abi = this._getAbiEncodeStatementVar(hash);
106+
107+
return (this.viaIR)
108+
? `\nfunction ${method}(bytes8 c__${hash}) pure { ${abi} }\n`
109+
: `\nfunction ${method}(bytes8 c__${hash}) pure {}\n`;
89110
}
90111

91112
/**
@@ -97,7 +118,11 @@ class Injector {
97118
_getTrueMethodDefinition(id){
98119
const hash = web3Utils.keccak256(id).slice(2,10);
99120
const method = this._getTrueMethodIdentifier(id);
100-
return `function ${method}(bytes8 c__${hash}) internal pure returns (bool){ return true; }\n`;
121+
const abi = this._getAbiEncodeStatementVar(hash);
122+
123+
return (this.viaIR)
124+
? `function ${method}(bytes8 c__${hash}) internal pure returns (bool){ ${abi} return true; }\n`
125+
: `function ${method}(bytes8 c__${hash}) internal pure returns (bool){ return true; }\n`;
101126
}
102127

103128
/**
@@ -110,7 +135,11 @@ class Injector {
110135
_getFileScopeTrueMethodDefinition(id){
111136
const hash = web3Utils.keccak256(id).slice(2,10);
112137
const method = this._getTrueMethodIdentifier(id);
113-
return `function ${method}(bytes8 c__${hash}) pure returns (bool){ return true; }\n`;
138+
const abi = this._getAbiEncodeStatementVar(hash);
139+
140+
return (this.viaIR)
141+
? `function ${method}(bytes8 c__${hash}) pure returns (bool){ ${abi} return true; }\n`
142+
: `function ${method}(bytes8 c__${hash}) pure returns (bool){ return true; }\n`;
114143
}
115144

116145
/**
@@ -122,7 +151,11 @@ class Injector {
122151
_getFalseMethodDefinition(id){
123152
const hash = web3Utils.keccak256(id).slice(2,10);
124153
const method = this._getFalseMethodIdentifier(id);
125-
return `function ${method}(bytes8 c__${hash}) internal pure returns (bool){ return false; }\n`;
154+
const abi = this._getAbiEncodeStatementVar(hash);
155+
156+
return (this.viaIR)
157+
? `function ${method}(bytes8 c__${hash}) internal pure returns (bool){ ${abi} return false; }\n`
158+
: `function ${method}(bytes8 c__${hash}) internal pure returns (bool){ return false; }\n`;
126159
}
127160

128161
/**
@@ -135,7 +168,11 @@ class Injector {
135168
_getFileScopedFalseMethodDefinition(id){
136169
const hash = web3Utils.keccak256(id).slice(2,10);
137170
const method = this._getFalseMethodIdentifier(id);
138-
return `function ${method}(bytes8 c__${hash}) pure returns (bool){ return false; }\n`;
171+
const abi = this._getAbiEncodeStatementVar(hash);
172+
173+
return (this.viaIR)
174+
? `function ${method}(bytes8 c__${hash}) pure returns (bool){ ${abi} return false; }\n`
175+
: `function ${method}(bytes8 c__${hash}) pure returns (bool){ return false; }\n`;
139176
}
140177

141178
_getModifierDefinitions(contractId, instrumentation){

lib/instrumenter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Instrumenter {
1414

1515
constructor(config={}){
1616
this.instrumentationData = {};
17-
this.injector = new Injector();
17+
this.injector = new Injector(config.viaIR);
1818
this.modifierWhitelist = config.modifierWhitelist || [];
1919
this.enabled = {
2020
statements: (config.measureStatementCoverage === false) ? false : true,

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"name": "solidity-coverage",
33
"version": "0.8.6",
44
"description": "Code coverage for Solidity testing",
@@ -12,7 +12,10 @@
1212
"scripts": {
1313
"test:unit": "./scripts/unit.sh",
1414
"test:integration": "./scripts/integration.sh",
15-
"test:ci": "./scripts/ci.sh"
15+
"test:ci": "./scripts/ci.sh",
16+
"test:uint:viaIR": "VIA_IR=true ./scripts/unit.sh",
17+
"test:integration:viaIR": "VIA_IR=true ./scripts/integration.sh",
18+
"test:ci:viaIR": "VIA_IR=true ./scripts/ci.sh"
1619
},
1720
"homepage": "https://github.com/sc-forks/solidity-coverage",
1821
"repository": {

plugins/hardhat.plugin.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_,
5252
if (settings.optimizer === undefined) {
5353
settings.optimizer = {};
5454
}
55+
5556
// Unset useLiteralContent due to solc metadata size restriction
5657
settings.metadata.useLiteralContent = false;
57-
// Override optimizer settings for all compilers
58-
settings.optimizer.enabled = false;
58+
59+
// Beginning with v0.8.7, we let the optimizer run if viaIR is true and
60+
// instrument using `verbatim` YUL keyword. Otherwise turn the optimizer off.
61+
if (!settings.viaIR) settings.optimizer.enabled = false;
5962

6063
// This is fixes a stack too deep bug in ABIEncoderV2
6164
// Experimental because not sure this works as expected across versions....

plugins/resources/nomiclabs.utils.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ function normalizeConfig(config, args={}){
3636
? sources = path.join(config.paths.sources, args.sources)
3737
: sources = config.paths.sources;
3838

39+
//const { inspect } = require('util');
40+
//console.log('config --> ' + inspect(config.solidity.compilers));
41+
42+
if (config.solidity && config.solidity.compilers.length) {
43+
config.viaIR = isUsingViaIR(config.solidity);
44+
}
45+
3946
config.workingDir = config.paths.root;
4047
config.contractsDir = sources;
4148
config.testDir = config.paths.tests;
@@ -55,6 +62,17 @@ function normalizeConfig(config, args={}){
5562
return config;
5663
}
5764

65+
function isUsingViaIR(solidity) {
66+
let viaIR = false;
67+
for (compiler of solidity.compilers) {
68+
if (compiler.settings && compiler.settings.viaIR) {
69+
viaIR = true;
70+
}
71+
}
72+
// Also handle overrides...
73+
return viaIR;
74+
}
75+
5876
async function setupHardhatNetwork(env, api, ui){
5977
const hardhatPackage = require('hardhat/package.json');
6078
const { createProvider } = require("hardhat/internal/core/providers/construction");

plugins/resources/plugin.utils.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,9 @@ function loadSolcoverJS(config={}){
227227
coverageConfig = {};
228228
}
229229

230-
// Truffle writes to coverage config
230+
// viaIR is eval'd in `nomiclab.utils.normalizeConfig`
231+
coverageConfig.viaIR = config.viaIR;
232+
231233
coverageConfig.log = log;
232234
coverageConfig.cwd = config.workingDir;
233235
coverageConfig.originalContractsDir = config.contractsDir;

scripts/ci.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
#!/usr/bin/env bash
22

3-
SILENT=true node --max-old-space-size=4096 \
3+
# Toggles optimizer on/off
4+
VIAR_IR=$VIA_IR
5+
6+
# Minimize integration test output
7+
SILENT=true
8+
9+
node --max-old-space-size=4096 \
410
./node_modules/.bin/nyc \
511
--reporter=lcov \
612
--exclude '**/sc_temp/**' \

scripts/integration.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/usr/bin/env bash
22

3+
# Toggles optimizer on/off
4+
VIA_IR=$VIA_IR
5+
36
node --max-old-space-size=4096 \
47
./node_modules/.bin/nyc \
58
--exclude '**/sc_temp/**' \

scripts/unit.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#!/usr/bin/env bash
22

3+
# Toggles optimizer on/off
4+
VIAR_IR=$VIA_IR
5+
36
node --max-old-space-size=4096 \
47
./node_modules/.bin/nyc \
58
--exclude '**/sc_temp/**' \

test/integration/standard.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ describe('Hardhat Plugin: standard use cases', function() {
364364
verify.lineCoverage(expected);
365365
})
366366

367-
it('logicalOR & ternary conditionals', async function(){
367+
it.only('logicalOR & ternary conditionals', async function(){
368368
mock.installFullProject('ternary-and-logical-or');
369369
mock.hardhatSetupEnv(this);
370370

test/sources/projects/contract-subfolders/hardhat.config.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ module.exports={
88
}
99
},
1010
solidity: {
11-
version: "0.8.17"
11+
version: "0.8.17",
12+
settings: {
13+
optimizer: {
14+
enabled: true
15+
},
16+
viaIR: process.env.VIA_IR === "true"
17+
}
1218
},
1319
paths: {
1420
sources: './contracts/A'

test/sources/projects/hardhat-compile-config/contracts/ContractA1.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pragma solidity ^0.5.5;
1+
pragma solidity >=0.8.0 <0.9.0;
22

33

44
contract ContractA {

test/sources/projects/hardhat-compile-config/contracts/ContractB1.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pragma solidity ^0.5.0;
1+
pragma solidity >=0.8.0 <0.9.0;
22

33

44
contract ContractB {

test/sources/projects/hardhat-compile-config/contracts/ContractC1.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pragma solidity ^0.6.0;
1+
pragma solidity >=0.8.0 <0.9.0;
22

33

44
contract ContractC {

0 commit comments

Comments
 (0)