139 lines
5.4 KiB
JavaScript
Executable File
139 lines
5.4 KiB
JavaScript
Executable File
#!/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));
|
|
}
|
|
}
|
|
})();
|