123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- #!/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));
- }
- }
- })();
|