Add script to automatically minimize pragma (#5740)
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.22;
|
||||
pragma solidity ^0.8.21;
|
||||
|
||||
import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol";
|
||||
import {StorageSlot} from "../utils/StorageSlot.sol";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.27;
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import {Account} from "../../account/Account.sol";
|
||||
import {AccountERC7579} from "../../account/extensions/draft-AccountERC7579.sol";
|
||||
|
||||
@ -24,11 +24,12 @@
|
||||
"clean": "hardhat clean && rimraf build contracts/build",
|
||||
"prepack": "scripts/prepack.sh",
|
||||
"generate": "scripts/generate/run.js",
|
||||
"pragma": "npm run compile && scripts/minimize-pragma.js artifacts/build-info/*",
|
||||
"version": "scripts/release/version.sh",
|
||||
"test": ". scripts/set-max-old-space-size.sh && hardhat test",
|
||||
"test:generation": "scripts/checks/generation.sh",
|
||||
"test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*",
|
||||
"test:pragma": "scripts/checks/pragma-validity.js artifacts/build-info/*",
|
||||
"test:inheritance": "npm run compile && scripts/checks/inheritance-ordering.js artifacts/build-info/*",
|
||||
"test:pragma": "npm run compile && scripts/checks/pragma-validity.js artifacts/build-info/*",
|
||||
"gas-report": "env ENABLE_GAS_REPORT=true npm run test",
|
||||
"slither": "npm run clean && slither ."
|
||||
},
|
||||
|
||||
138
scripts/minimize-pragma.js
Executable file
138
scripts/minimize-pragma.js
Executable file
@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const graphlib = require('graphlib');
|
||||
const semver = require('semver');
|
||||
const pLimit = require('p-limit').default;
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
const yargs = require('yargs/yargs');
|
||||
|
||||
const getContractsMetadata = require('./get-contracts-metadata');
|
||||
const { versions: allSolcVersions, compile } = require('./solc-versions');
|
||||
|
||||
const {
|
||||
argv: { pattern, skipPatterns, minVersionForContracts, minVersionForInterfaces, concurrency, _: artifacts },
|
||||
} = yargs(hideBin(process.argv))
|
||||
.env('')
|
||||
.options({
|
||||
pattern: { alias: 'p', type: 'string', default: 'contracts/**/*.sol' },
|
||||
skipPatterns: { alias: 's', type: 'string', default: 'contracts/mocks/**/*.sol' },
|
||||
minVersionForContracts: { type: 'string', default: '0.8.20' },
|
||||
minVersionForInterfaces: { type: 'string', default: '0.0.0' },
|
||||
concurrency: { alias: 'c', type: 'number', default: 8 },
|
||||
});
|
||||
|
||||
// limit concurrency
|
||||
const limit = pLimit(concurrency);
|
||||
|
||||
/********************************************************************************************************************
|
||||
* HELPERS *
|
||||
********************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* Updates the pragma in the given file to the newPragma version.
|
||||
* @param {*} file Absolute path to the file to update.
|
||||
* @param {*} pragma New pragma version to set. (ex: '>=0.8.4')
|
||||
*/
|
||||
const updatePragma = (file, pragma) =>
|
||||
fs.writeFileSync(
|
||||
file,
|
||||
fs.readFileSync(file, 'utf8').replace(/pragma solidity [><=^]*[0-9]+.[0-9]+.[0-9]+;/, `pragma solidity ${pragma};`),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the applicable pragmas for a given file by compiling it with all solc versions.
|
||||
* @param {*} file Absolute path to the file to compile.
|
||||
* @param {*} candidates List of solc version to test. (ex: ['0.8.4','0.8.5'])
|
||||
* @returns {Promise<string[]>} List of applicable pragmas.
|
||||
*/
|
||||
const getApplicablePragmas = (file, candidates = allSolcVersions) =>
|
||||
Promise.all(
|
||||
candidates.map(version =>
|
||||
limit(() =>
|
||||
compile(file, version).then(
|
||||
() => version,
|
||||
() => null,
|
||||
),
|
||||
),
|
||||
),
|
||||
).then(versions => versions.filter(Boolean));
|
||||
|
||||
/**
|
||||
* Get the minimum applicable pragmas for a given file.
|
||||
* @param {*} file Absolute path to the file to compile.
|
||||
* @param {*} candidates List of solc version to test. (ex: ['0.8.4','0.8.5'])
|
||||
* @returns {Promise<string>} Smallest applicable pragma out of the list.
|
||||
*/
|
||||
const getMinimalApplicablePragma = (file, candidates = allSolcVersions) =>
|
||||
getApplicablePragmas(file, candidates).then(valid => {
|
||||
if (valid.length == 0) {
|
||||
throw new Error(`No valid pragma found for ${file}`);
|
||||
} else {
|
||||
return valid.sort(semver.compare).at(0);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the minimum applicable pragmas for a given file, and update the file to use it.
|
||||
* @param {*} file Absolute path to the file to compile.
|
||||
* @param {*} candidates List of solc version to test. (ex: ['0.8.4','0.8.5'])
|
||||
* @param {*} prefix Prefix to use when building the pragma (ex: '^')
|
||||
* @returns {Promise<string>} Version that was used and set in the file
|
||||
*/
|
||||
const setMinimalApplicablePragma = (file, candidates = allSolcVersions, prefix = '>=') =>
|
||||
getMinimalApplicablePragma(file, candidates)
|
||||
.then(version => `${prefix}${version}`)
|
||||
.then(pragma => {
|
||||
updatePragma(file, pragma);
|
||||
return pragma;
|
||||
});
|
||||
|
||||
/********************************************************************************************************************
|
||||
* MAIN *
|
||||
********************************************************************************************************************/
|
||||
|
||||
// Build metadata from artifact files (hardhat compilation)
|
||||
const metadata = getContractsMetadata(pattern, skipPatterns, artifacts);
|
||||
|
||||
// Build dependency graph
|
||||
const graph = new graphlib.Graph({ directed: true });
|
||||
Object.keys(metadata).forEach(file => {
|
||||
graph.setNode(file);
|
||||
metadata[file].sources.forEach(dep => graph.setEdge(dep, file));
|
||||
});
|
||||
|
||||
// Weaken all pragma to allow exploration
|
||||
Object.keys(metadata).forEach(file => updatePragma(file, '>=0.0.0'));
|
||||
|
||||
// Do a topological traversal of the dependency graph, minimizing pragma for each file we encounter
|
||||
(async () => {
|
||||
const queue = graph.sources();
|
||||
const pragmas = {};
|
||||
while (queue.length) {
|
||||
const file = queue.shift();
|
||||
if (!Object.hasOwn(pragmas, file)) {
|
||||
if (Object.hasOwn(metadata, file)) {
|
||||
const minVersion = metadata[file].interface ? minVersionForInterfaces : minVersionForContracts;
|
||||
const parentsPragmas = graph
|
||||
.predecessors(file)
|
||||
.map(file => pragmas[file])
|
||||
.filter(Boolean);
|
||||
const candidates = allSolcVersions.filter(
|
||||
v => semver.gte(v, minVersion) && parentsPragmas.every(p => semver.satisfies(v, p)),
|
||||
);
|
||||
const pragmaPrefix = metadata[file].interface ? '>=' : '^';
|
||||
|
||||
process.stdout.write(
|
||||
`[${Object.keys(pragmas).length + 1}/${Object.keys(metadata).length}] Searching minimal version for ${file} ... `,
|
||||
);
|
||||
const pragma = await setMinimalApplicablePragma(file, candidates, pragmaPrefix);
|
||||
console.log(pragma);
|
||||
|
||||
pragmas[file] = pragma;
|
||||
}
|
||||
queue.push(...graph.successors(file));
|
||||
}
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user