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
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.22;
|
pragma solidity ^0.8.21;
|
||||||
|
|
||||||
import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol";
|
import {ERC1967Utils} from "../proxy/ERC1967/ERC1967Utils.sol";
|
||||||
import {StorageSlot} from "../utils/StorageSlot.sol";
|
import {StorageSlot} from "../utils/StorageSlot.sol";
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
pragma solidity ^0.8.27;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import {Account} from "../../account/Account.sol";
|
import {Account} from "../../account/Account.sol";
|
||||||
import {AccountERC7579} from "../../account/extensions/draft-AccountERC7579.sol";
|
import {AccountERC7579} from "../../account/extensions/draft-AccountERC7579.sol";
|
||||||
|
|||||||
@ -24,11 +24,12 @@
|
|||||||
"clean": "hardhat clean && rimraf build contracts/build",
|
"clean": "hardhat clean && rimraf build contracts/build",
|
||||||
"prepack": "scripts/prepack.sh",
|
"prepack": "scripts/prepack.sh",
|
||||||
"generate": "scripts/generate/run.js",
|
"generate": "scripts/generate/run.js",
|
||||||
|
"pragma": "npm run compile && scripts/minimize-pragma.js artifacts/build-info/*",
|
||||||
"version": "scripts/release/version.sh",
|
"version": "scripts/release/version.sh",
|
||||||
"test": ". scripts/set-max-old-space-size.sh && hardhat test",
|
"test": ". scripts/set-max-old-space-size.sh && hardhat test",
|
||||||
"test:generation": "scripts/checks/generation.sh",
|
"test:generation": "scripts/checks/generation.sh",
|
||||||
"test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*",
|
"test:inheritance": "npm run compile && scripts/checks/inheritance-ordering.js artifacts/build-info/*",
|
||||||
"test:pragma": "scripts/checks/pragma-validity.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",
|
"gas-report": "env ENABLE_GAS_REPORT=true npm run test",
|
||||||
"slither": "npm run clean && slither ."
|
"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