Skip to content

Commit 387ce69

Browse files
Amxxarr00
andauthored
Add script to automatically minimize pragma (#5740)
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
1 parent 56fe41c commit 387ce69

File tree

4 files changed

+143
-4
lines changed

4 files changed

+143
-4
lines changed

contracts/mocks/DummyImplementation.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: MIT
22

3-
pragma solidity ^0.8.22;
3+
pragma solidity ^0.8.21;
44

55
import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol";
66
import {StorageSlot} from "../utils/StorageSlot.sol";

contracts/mocks/account/AccountMock.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: MIT
22

3-
pragma solidity ^0.8.27;
3+
pragma solidity ^0.8.26;
44

55
import {Account} from "../../account/Account.sol";
66
import {AccountERC7579} from "../../account/extensions/draft-AccountERC7579.sol";

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@
2424
"clean": "hardhat clean && rimraf build contracts/build",
2525
"prepack": "scripts/prepack.sh",
2626
"generate": "scripts/generate/run.js",
27+
"pragma": "npm run compile && scripts/minimize-pragma.js artifacts/build-info/*",
2728
"version": "scripts/release/version.sh",
2829
"test": ". scripts/set-max-old-space-size.sh && hardhat test",
2930
"test:generation": "scripts/checks/generation.sh",
30-
"test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*",
31-
"test:pragma": "scripts/checks/pragma-validity.js artifacts/build-info/*",
31+
"test:inheritance": "npm run compile && scripts/checks/inheritance-ordering.js artifacts/build-info/*",
32+
"test:pragma": "npm run compile && scripts/checks/pragma-validity.js artifacts/build-info/*",
3233
"gas-report": "env ENABLE_GAS_REPORT=true npm run test",
3334
"slither": "npm run clean && slither ."
3435
},

scripts/minimize-pragma.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require('fs');
4+
const graphlib = require('graphlib');
5+
const semver = require('semver');
6+
const pLimit = require('p-limit').default;
7+
const { hideBin } = require('yargs/helpers');
8+
const yargs = require('yargs/yargs');
9+
10+
const getContractsMetadata = require('./get-contracts-metadata');
11+
const { versions: allSolcVersions, compile } = require('./solc-versions');
12+
13+
const {
14+
argv: { pattern, skipPatterns, minVersionForContracts, minVersionForInterfaces, concurrency, _: artifacts },
15+
} = yargs(hideBin(process.argv))
16+
.env('')
17+
.options({
18+
pattern: { alias: 'p', type: 'string', default: 'contracts/**/*.sol' },
19+
skipPatterns: { alias: 's', type: 'string', default: 'contracts/mocks/**/*.sol' },
20+
minVersionForContracts: { type: 'string', default: '0.8.20' },
21+
minVersionForInterfaces: { type: 'string', default: '0.0.0' },
22+
concurrency: { alias: 'c', type: 'number', default: 8 },
23+
});
24+
25+
// limit concurrency
26+
const limit = pLimit(concurrency);
27+
28+
/********************************************************************************************************************
29+
* HELPERS *
30+
********************************************************************************************************************/
31+
32+
/**
33+
* Updates the pragma in the given file to the newPragma version.
34+
* @param {*} file Absolute path to the file to update.
35+
* @param {*} pragma New pragma version to set. (ex: '>=0.8.4')
36+
*/
37+
const updatePragma = (file, pragma) =>
38+
fs.writeFileSync(
39+
file,
40+
fs.readFileSync(file, 'utf8').replace(/pragma solidity [><=^]*[0-9]+.[0-9]+.[0-9]+;/, `pragma solidity ${pragma};`),
41+
'utf8',
42+
);
43+
44+
/**
45+
* Get the applicable pragmas for a given file by compiling it with all solc versions.
46+
* @param {*} file Absolute path to the file to compile.
47+
* @param {*} candidates List of solc version to test. (ex: ['0.8.4','0.8.5'])
48+
* @returns {Promise<string[]>} List of applicable pragmas.
49+
*/
50+
const getApplicablePragmas = (file, candidates = allSolcVersions) =>
51+
Promise.all(
52+
candidates.map(version =>
53+
limit(() =>
54+
compile(file, version).then(
55+
() => version,
56+
() => null,
57+
),
58+
),
59+
),
60+
).then(versions => versions.filter(Boolean));
61+
62+
/**
63+
* Get the minimum applicable pragmas for a given file.
64+
* @param {*} file Absolute path to the file to compile.
65+
* @param {*} candidates List of solc version to test. (ex: ['0.8.4','0.8.5'])
66+
* @returns {Promise<string>} Smallest applicable pragma out of the list.
67+
*/
68+
const getMinimalApplicablePragma = (file, candidates = allSolcVersions) =>
69+
getApplicablePragmas(file, candidates).then(valid => {
70+
if (valid.length == 0) {
71+
throw new Error(`No valid pragma found for ${file}`);
72+
} else {
73+
return valid.sort(semver.compare).at(0);
74+
}
75+
});
76+
77+
/**
78+
* Get the minimum applicable pragmas for a given file, and update the file to use it.
79+
* @param {*} file Absolute path to the file to compile.
80+
* @param {*} candidates List of solc version to test. (ex: ['0.8.4','0.8.5'])
81+
* @param {*} prefix Prefix to use when building the pragma (ex: '^')
82+
* @returns {Promise<string>} Version that was used and set in the file
83+
*/
84+
const setMinimalApplicablePragma = (file, candidates = allSolcVersions, prefix = '>=') =>
85+
getMinimalApplicablePragma(file, candidates)
86+
.then(version => `${prefix}${version}`)
87+
.then(pragma => {
88+
updatePragma(file, pragma);
89+
return pragma;
90+
});
91+
92+
/********************************************************************************************************************
93+
* MAIN *
94+
********************************************************************************************************************/
95+
96+
// Build metadata from artifact files (hardhat compilation)
97+
const metadata = getContractsMetadata(pattern, skipPatterns, artifacts);
98+
99+
// Build dependency graph
100+
const graph = new graphlib.Graph({ directed: true });
101+
Object.keys(metadata).forEach(file => {
102+
graph.setNode(file);
103+
metadata[file].sources.forEach(dep => graph.setEdge(dep, file));
104+
});
105+
106+
// Weaken all pragma to allow exploration
107+
Object.keys(metadata).forEach(file => updatePragma(file, '>=0.0.0'));
108+
109+
// Do a topological traversal of the dependency graph, minimizing pragma for each file we encounter
110+
(async () => {
111+
const queue = graph.sources();
112+
const pragmas = {};
113+
while (queue.length) {
114+
const file = queue.shift();
115+
if (!Object.hasOwn(pragmas, file)) {
116+
if (Object.hasOwn(metadata, file)) {
117+
const minVersion = metadata[file].interface ? minVersionForInterfaces : minVersionForContracts;
118+
const parentsPragmas = graph
119+
.predecessors(file)
120+
.map(file => pragmas[file])
121+
.filter(Boolean);
122+
const candidates = allSolcVersions.filter(
123+
v => semver.gte(v, minVersion) && parentsPragmas.every(p => semver.satisfies(v, p)),
124+
);
125+
const pragmaPrefix = metadata[file].interface ? '>=' : '^';
126+
127+
process.stdout.write(
128+
`[${Object.keys(pragmas).length + 1}/${Object.keys(metadata).length}] Searching minimal version for ${file} ... `,
129+
);
130+
const pragma = await setMinimalApplicablePragma(file, candidates, pragmaPrefix);
131+
console.log(pragma);
132+
133+
pragmas[file] = pragma;
134+
}
135+
queue.push(...graph.successors(file));
136+
}
137+
}
138+
})();

0 commit comments

Comments
 (0)