Code coverage for Solidity smart-contracts
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.
 
 
 
solidity-coverage/test/util/util.js

148 lines
4.0 KiB

/**
* Setup and reporting helpers for the suites which test instrumentation
* and coverage correctness. (Integration test helpers are elsewhere)
*/
const fs = require('fs');
const path = require('path');
const solc = require('solc');
const ethers = require('ethers');
const Instrumenter = require('./../../lib/instrumenter');
const DataCollector = require('./../../lib/collector')
// ====================
// Paths / Files
// ====================
const filePath = path.resolve('./test.sol');
const pathPrefix = './';
// ====================
// Contract deployments
// ====================
function getABI(solcOutput, testFile="test.sol", testName="Test"){
return solcOutput.contracts[testFile][testName].abi;
}
function getBytecode(solcOutput, testFile="test.sol", testName="Test"){
return `0x${solcOutput.contracts[testFile][testName].evm.bytecode.object}`;
}
async function getDeployedContractInstance(info, provider){
const ethersProvider = new ethers.providers.Web3Provider(provider);
const signer = ethersProvider.getSigner();
const factory = new ethers.ContractFactory(
getABI(info.solcOutput),
getBytecode(info.solcOutput),
signer
)
const contract = await factory.deploy();
await contract.deployTransaction.wait();
return contract;
}
// ============
// Compilation
// ============
function getCode(_path) {
const pathToSources = `./../sources/solidity/contracts/${_path}`;
return fs.readFileSync(path.join(__dirname, pathToSources), 'utf8');
};
function compile(source){
const compilerInput = codeToCompilerInput(source);
return JSON.parse(solc.compile(compilerInput));
}
function codeToCompilerInput(code) {
return JSON.stringify({
language: 'Solidity',
sources: { 'test.sol': { content: code } },
settings: {
outputSelection: {'*': { '*': [ '*' ] }},
evmVersion: "paris",
viaIR: process.env.VIA_IR === "true"
}
});
}
// ===========
// Diff tests
// ===========
function getDiffABIs(sourceName, testFile="test.sol", original="Old", current="New"){
const contract = getCode(`${sourceName}.sol`)
const solcOutput = compile(contract)
return {
original: {
contractName: "Test",
sha: "d8b26d8",
abi: solcOutput.contracts[testFile][original].abi,
},
current: {
contractName: "Test",
sha: "e77e29d",
abi: solcOutput.contracts[testFile][current].abi,
}
}
}
// ============================
// Instrumentation Correctness
// ============================
function instrumentAndCompile(sourceName, api={ config: {} }) {
api.config.viaIR = process.env.VIA_IR === "true";
const contract = getCode(`${sourceName}.sol`)
const instrumenter = new Instrumenter(api.config);
const instrumented = instrumenter.instrument(contract, filePath);
//console.log('instrumented: --> ' + instrumented.contract);
return {
contract: contract,
instrumented: instrumented,
solcOutput: compile(instrumented.contract),
data: instrumenter.instrumentationData
}
}
function report(output=[]) {
output.forEach(item => {
if (item.severity === 'error') {
const errors = JSON.stringify(output, null, ' ');
throw new Error(`Instrumentation fault: ${errors}`);
}
});
}
// =====================
// Coverage Correctness
// =====================
async function bootstrapCoverage(file, api, provider){
const info = instrumentAndCompile(file, api);
const {inspect} = require('util');
//console.log('info --> ' + inspect(info.solcOutput));
// Need to define a gasLimit for contract calls because otherwise ethers will estimateGas
// and cause duplicate hits for everything
info.gas = { gasLimit: 2_000_000 }
info.instance = await getDeployedContractInstance(info, provider);
// Have to do this after the deployment call because provider initializes on send
await api.attachToHardhatVM(provider);
api.collector._setInstrumentationData(info.data);
return info;
}
module.exports = {
getCode,
pathPrefix,
filePath,
report,
instrumentAndCompile,
bootstrapCoverage,
getDiffABIs
}