Skip to content

Commit 78c8da8

Browse files
Amxxarr00
andauthored
Update pragma check: validate by actually running the compiler (#5730)
Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com>
1 parent e11cac4 commit 78c8da8

File tree

6 files changed

+118
-52
lines changed

6 files changed

+118
-52
lines changed

.github/workflows/checks.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ jobs:
4141
run: npm run test
4242
- name: Check linearisation of the inheritance graph
4343
run: npm run test:inheritance
44-
- name: Check pragma consistency between files
45-
run: npm run test:pragma
44+
- name: Check pragma validity
45+
run: npm run test:pragma -- --concurrency 1
4646
- name: Check procedurally generated contracts are up-to-date
4747
run: npm run test:generation
4848
- name: Compare gas costs

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"test": ". scripts/set-max-old-space-size.sh && hardhat test",
2929
"test:generation": "scripts/checks/generation.sh",
3030
"test:inheritance": "scripts/checks/inheritance-ordering.js artifacts/build-info/*",
31-
"test:pragma": "scripts/checks/pragma-consistency.js artifacts/build-info/*",
31+
"test:pragma": "scripts/checks/pragma-validity.js artifacts/build-info/*",
3232
"gas-report": "env ENABLE_GAS_REPORT=true npm run test",
3333
"slither": "npm run clean && slither ."
3434
},

scripts/checks/pragma-consistency.js

Lines changed: 0 additions & 49 deletions
This file was deleted.

scripts/checks/pragma-validity.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env node
2+
3+
const semver = require('semver');
4+
const pLimit = require('p-limit').default;
5+
6+
const { hideBin } = require('yargs/helpers');
7+
const yargs = require('yargs/yargs');
8+
9+
const getContractsMetadata = require('../get-contracts-metadata');
10+
const { compile } = require('../solc-versions');
11+
12+
const {
13+
argv: { pattern, skipPatterns, verbose, concurrency, _: artifacts },
14+
} = yargs(hideBin(process.argv))
15+
.env('')
16+
.options({
17+
pattern: { alias: 'p', type: 'string', default: 'contracts/**/*.sol' },
18+
skipPatterns: { alias: 's', type: 'string', default: 'contracts/mocks/**/*.sol' },
19+
concurrency: { alias: 'c', type: 'number', default: 8 },
20+
verbose: { alias: 'v', type: 'count' },
21+
});
22+
23+
const limit = pLimit(concurrency);
24+
Promise.all(
25+
Object.entries(getContractsMetadata(pattern, skipPatterns, artifacts)).map(([source, { pragma }]) =>
26+
limit(
27+
(file, version) =>
28+
compile(file, version).then(
29+
() => {
30+
verbose && console.log(`Compile ${file} using solc ${version}: ok`);
31+
},
32+
error => {
33+
console.error(`Failed to compile ${file} using solc ${version}\n${error}`);
34+
process.exitCode = 1;
35+
},
36+
),
37+
source,
38+
semver.minVersion(pragma),
39+
),
40+
),
41+
).finally(() => {
42+
if (!process.exitCode) {
43+
console.log('All files can be compiled with the specified pragma.');
44+
}
45+
});

scripts/get-contracts-metadata.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const fs = require('fs');
2+
const glob = require('glob');
3+
const match = require('micromatch');
4+
const path = require('path');
5+
const { findAll } = require('solidity-ast/utils');
6+
7+
module.exports = function (
8+
pattern = 'contracts/**/*.sol',
9+
skipPatterns = ['contracts/mocks/**/*.sol'],
10+
artifacts = [],
11+
) {
12+
// Use available hardhat artifacts. They reliably identify pragmas and the contracts, libraries and interfaces
13+
// definitions with minimal IO operations.
14+
const metadata = Object.fromEntries(
15+
artifacts.flatMap(artifact => {
16+
const { output: solcOutput } = require(path.resolve(__dirname, '..', artifact));
17+
return Object.keys(solcOutput.contracts)
18+
.filter(source => match.all(source, pattern) && !match.any(source, skipPatterns))
19+
.map(source => [
20+
source,
21+
{
22+
pragma: Array.from(findAll('PragmaDirective', solcOutput.sources[source].ast))
23+
.find(({ literals }) => literals.at(0) == 'solidity')
24+
.literals.slice(1)
25+
.join(''),
26+
sources: Array.from(findAll('ImportDirective', solcOutput.sources[source].ast)).map(
27+
({ absolutePath }) => absolutePath,
28+
),
29+
interface: Array.from(findAll('ContractDefinition', solcOutput.sources[source].ast)).every(
30+
({ contractKind }) => contractKind === 'interface',
31+
),
32+
},
33+
]);
34+
}),
35+
);
36+
37+
// Artifacts are missing files that only include imports. We have a few of these in contracts/interfaces
38+
// We add the missing metadata entries using the foundry artifacts
39+
glob
40+
.sync(pattern)
41+
.filter(file => !match.any(file, skipPatterns) && !Object.hasOwn(metadata, file))
42+
.forEach(file => {
43+
const entries = glob.sync(`out/${path.basename(file)}/*`);
44+
metadata[file] = {
45+
pragma: fs.readFileSync(file, 'utf-8').match(/pragma solidity (?<pragma>[<>=^]*[0-9]+\.[0-9]+\.[0-9]+);/)
46+
?.groups.pragma,
47+
sources: entries
48+
.flatMap(entry => Object.keys(JSON.parse(fs.readFileSync(entry)).metadata.sources))
49+
.filter(source => source !== file && match.all(source, pattern) && !match.any(source, skipPatterns)),
50+
interface: entries.every(entry => path.basename(entry).match(/^I[A-Z]/)),
51+
};
52+
});
53+
54+
return metadata;
55+
};

scripts/solc-versions.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const { exec } = require('child_process');
2+
const semver = require('semver');
3+
const { range } = require('./helpers');
4+
5+
module.exports = {
6+
versions: ['0.4.26', '0.5.16', '0.6.12', '0.7.6', '0.8.30']
7+
.map(semver.parse)
8+
.flatMap(({ major, minor, patch }) => range(patch + 1).map(p => `${major}.${minor}.${p}`)),
9+
compile: (source, version) =>
10+
new Promise((resolve, reject) =>
11+
exec(`forge build ${source} --use ${version} --out out/solc-${version}`, error =>
12+
error ? reject(error) : resolve(),
13+
),
14+
),
15+
};

0 commit comments

Comments
 (0)