You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
110 lines
3.0 KiB
110 lines
3.0 KiB
2 years ago
|
#!/usr/bin/env node
|
||
|
|
||
|
// USAGE:
|
||
|
// node certora/run.js [[CONTRACT_NAME:]SPEC_NAME] [OPTIONS...]
|
||
|
// EXAMPLES:
|
||
|
// node certora/run.js AccessControl
|
||
|
// node certora/run.js AccessControlHarness:AccessControl
|
||
|
|
||
|
const MAX_PARALLEL = 4;
|
||
|
|
||
|
const specs = require(__dirname + '/specs.json');
|
||
|
|
||
|
const proc = require('child_process');
|
||
|
const { PassThrough } = require('stream');
|
||
|
const events = require('events');
|
||
|
const limit = require('p-limit')(MAX_PARALLEL);
|
||
|
|
||
|
let [, , request = '', ...extraOptions] = process.argv;
|
||
|
if (request.startsWith('-')) {
|
||
|
extraOptions.unshift(request);
|
||
|
request = '';
|
||
|
}
|
||
|
const [reqSpec, reqContract] = request.split(':').reverse();
|
||
|
|
||
|
for (const { spec, contract, files, options = [] } of Object.values(specs)) {
|
||
|
if ((!reqSpec || reqSpec === spec) && (!reqContract || reqContract === contract)) {
|
||
|
limit(runCertora, spec, contract, files, [...options, ...extraOptions]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Run certora, aggregate the output and print it at the end
|
||
|
async function runCertora(spec, contract, files, options = []) {
|
||
|
const args = [...files, '--verify', `${contract}:certora/specs/${spec}.spec`, ...options];
|
||
|
const child = proc.spawn('certoraRun', args);
|
||
|
|
||
|
const stream = new PassThrough();
|
||
|
const output = collect(stream);
|
||
|
|
||
|
child.stdout.pipe(stream, { end: false });
|
||
|
child.stderr.pipe(stream, { end: false });
|
||
|
|
||
|
// as soon as we have a jobStatus link, print it
|
||
|
stream.on('data', function logStatusUrl(data) {
|
||
|
const urls = data.toString('utf8').match(/https?:\S*/g);
|
||
|
for (const url of urls ?? []) {
|
||
|
if (url.includes('/jobStatus/')) {
|
||
|
console.error(`[${spec}] ${url}`);
|
||
|
stream.off('data', logStatusUrl);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// wait for process end
|
||
|
const [code, signal] = await events.once(child, 'exit');
|
||
|
|
||
|
// error
|
||
|
if (code || signal) {
|
||
|
console.error(`[${spec}] Exited with code ${code || signal}`);
|
||
|
process.exitCode = 1;
|
||
|
}
|
||
|
|
||
|
// get all output
|
||
|
stream.end();
|
||
|
|
||
|
// write results in markdown format
|
||
|
writeEntry(spec, contract, code || signal, (await output).match(/https:\S*/)[0]);
|
||
|
|
||
|
// write all details
|
||
|
console.error(`+ certoraRun ${args.join(' ')}\n` + (await output));
|
||
|
}
|
||
|
|
||
|
// Collects stream data into a string
|
||
|
async function collect(stream) {
|
||
|
const buffers = [];
|
||
|
for await (const data of stream) {
|
||
|
const buf = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
||
|
buffers.push(buf);
|
||
|
}
|
||
|
return Buffer.concat(buffers).toString('utf8');
|
||
|
}
|
||
|
|
||
|
// Formatting
|
||
|
let hasHeader = false;
|
||
|
|
||
|
function formatRow(...array) {
|
||
|
return ['', ...array, ''].join(' | ');
|
||
|
}
|
||
|
|
||
|
function writeHeader() {
|
||
|
console.log(formatRow('spec', 'contract', 'result', 'status', 'output'));
|
||
|
console.log(formatRow('-', '-', '-', '-', '-'));
|
||
|
}
|
||
|
|
||
|
function writeEntry(spec, contract, success, url) {
|
||
|
if (!hasHeader) {
|
||
|
hasHeader = true;
|
||
|
writeHeader();
|
||
|
}
|
||
|
console.log(
|
||
|
formatRow(
|
||
|
spec,
|
||
|
contract,
|
||
|
success ? ':x:' : ':heavy_check_mark:',
|
||
|
`[link](${url})`,
|
||
|
`[link](${url.replace('/jobStatus/', '/output/')})`,
|
||
|
),
|
||
|
);
|
||
|
}
|