123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- #!/usr/bin/env node
- // USAGE:
- // node certora/run.js [[CONTRACT_NAME:]SPEC_NAME]* [--all] [--options OPTIONS...] [--specs PATH]
- // EXAMPLES:
- // node certora/run.js --all
- // node certora/run.js AccessControl
- // node certora/run.js AccessControlHarness:AccessControl
- const proc = require('child_process');
- const { PassThrough } = require('stream');
- const events = require('events');
- const argv = require('yargs')
- .env('')
- .options({
- all: {
- alias: 'a',
- type: 'boolean',
- },
- spec: {
- alias: 's',
- type: 'string',
- default: __dirname + '/specs.json',
- },
- parallel: {
- alias: 'p',
- type: 'number',
- default: 4,
- },
- options: {
- alias: 'o',
- type: 'array',
- default: [],
- },
- }).argv;
- function match(entry, request) {
- const [reqSpec, reqContract] = request.split(':').reverse();
- return entry.spec == reqSpec && (!reqContract || entry.contract == reqContract);
- }
- const specs = require(argv.spec).filter(s => argv.all || argv._.some(r => match(s, r)));
- const limit = require('p-limit')(argv.parallel);
- if (argv._.length == 0 && !argv.all) {
- console.error(`Warning: No specs requested. Did you forgot to toggle '--all'?`);
- }
- for (const r of argv._) {
- if (!specs.some(s => match(s, r))) {
- console.error(`Error: Requested spec '${r}' not found in ${argv.spec}`);
- process.exitCode = 1;
- }
- }
- if (process.exitCode) {
- process.exit(process.exitCode);
- }
- for (const { spec, contract, files, options = [] } of specs) {
- limit(runCertora, spec, contract, files, [...options.flatMap(opt => opt.split(' ')), ...argv.options]);
- }
- // 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 job id, print the output link
- stream.on('data', function logStatusUrl(data) {
- const { '-DjobId': jobId, '-DuserId': userId } = Object.fromEntries(
- data
- .toString('utf8')
- .match(/-D\S+=\S+/g)
- ?.map(s => s.split('=')) || [],
- );
- if (jobId && userId) {
- console.error(`[${spec}] https://prover.certora.com/output/${userId}/${jobId}/`);
- stream.off('data', logStatusUrl);
- }
- });
- // 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:\/\/prover.certora.com\/output\/\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:',
- url ? `[link](${url?.replace('/output/', '/jobStatus/')})` : 'error',
- url ? `[link](${url})` : 'error',
- ),
- );
- }
|