diff --git a/lib/artifactor.js b/lib/artifactor.js index aa4f35b..dc96bd9 100644 --- a/lib/artifactor.js +++ b/lib/artifactor.js @@ -1,23 +1,50 @@ -const SyncRequest = require("./syncRequest"); +const { parseSoliditySources } = require("./utils"); /** - * Extracts assets the reporter consumes (abi, bytecode) from supported artifact formats - * @example - * ``` - * const artifactor = new Artifactor(config); - * const contract = artifactor.require('Example'); - * - * > { - * > abi: [etc...], - * > bytecode: "0x" + contract.evm.bytecode.object, // (solc key name) - * > deployedBytecode: "0x" + contract.evm.deployedBytecode.object // (solc key name) - * > } + * Supplies contract artifact data to the reporter in a format it can use. */ class Artifactor { constructor(config) { this.config = config; - this.sync = new SyncRequest(config.url); - this.networkId = !config.provider ? this.sync.getNetworkId() : null; + } + + /** + * Returns an array of contract info objects in the format consumed by ./gasData.js. + * @return {Object[]} + * @example + * ``` + * const artifactor = new Artifactor(config); + * const contracts = artifactor.getContracts(); + * > [ + * > { + * > name: "Example", + * > artifact: { + * > abi: [etc...], + * > bytecode: "0x" + contract.evm.bytecode.object, // (solc key name) + * > deployedBytecode: "0x" + contract.evm.deployedBytecode.object // (solc key name) + * > }, + * > ... + * > ] + */ + getContracts() { + if (typeof this.config.getContracts === "function") { + return this.config.getContracts(); + } + + const contracts = []; + + for (const name of parseSoliditySources(this.config)) { + let artifact; + + try { + artifact = this._require(name); + } catch (e) { + return; + } + + contracts.push({ name: name, artifact: artifact }); + } + return contracts; } /** @@ -25,7 +52,7 @@ class Artifactor { * @param {String} contractName * @return {Object} egr artifact */ - require(contractName) { + _require(contractName) { // User defined if (typeof this.config.artifactType === "function") return this.config.artifactType(contractName); @@ -85,6 +112,7 @@ class Artifactor { } /** + * [DEPRECATED] * Buidler artifact translator. Solc info (metadata) is attached to config * at the buidler plugin * @param {String} contractName @@ -102,6 +130,7 @@ class Artifactor { } /** + * [EXPERIMENTAL] * 0x artifact translator. Untested stub. * @param {String} contractName * @return {Object} egr artifact diff --git a/lib/config.js b/lib/config.js index 5d43dce..c3ae6e2 100644 --- a/lib/config.js +++ b/lib/config.js @@ -18,6 +18,7 @@ class Config { this.showTimeSpent = options.showTimeSpent || false; this.srcPath = options.src || "contracts"; this.artifactType = options.artifactType || "truffle-v5"; + this.getContracts = options.getContracts || null; this.noColors = options.noColors; this.proxyResolver = options.proxyResolver || null; this.metadata = options.metadata || null; diff --git a/lib/gasData.js b/lib/gasData.js index 5a6071b..769ca7e 100644 --- a/lib/gasData.js +++ b/lib/gasData.js @@ -19,8 +19,6 @@ class GasData { } /** - * + Parses the .sol files in the config.srcPath directory to obtain contract names. - * + Gets abis & bytecode for those assets via Artifactor. * + Compiles pre-test gas usage (e.g. from `truffle migrate`) * + Sets up data structures to store deployments and methods gas usage * + Called in the mocha `start` hook to guarantee it's run later than pre-test deployments @@ -31,8 +29,6 @@ class GasData { this.provider = config.provider; const artifactor = new Artifactor(config); - const files = utils.listSolidityFiles(config.srcPath); - // Get the current blockLimit; // TODO: This shouldn't be here - should be on the config object & // fetched when the table is written or something. @@ -43,80 +39,71 @@ class GasData { this.blockLimit = utils.gas(block.gasLimit); } - files.forEach(file => { - utils - .getContractNames(file) - .filter(name => !config.excludeContracts.includes(name)) - .forEach(name => { - let contract; - try { - contract = artifactor.require(name); - } catch (error) { - return; - } - - const contractInfo = { - name: name, - bytecode: contract.bytecode, - deployedBytecode: contract.deployedBytecode, - gasData: [] + for (const contract of artifactor.getContracts()) { + const contractInfo = { + name: contract.name, + bytecode: contract.artifact.bytecode, + deployedBytecode: contract.artifact.deployedBytecode, + gasData: [] + }; + this.deployments.push(contractInfo); + + // Report gas used during pre-test deployments (ex: truffle migrate) + if ( + contract.artifact.deployed && + contract.artifact.deployed.transactionHash && + !this.provider + ) { + const receipt = this.sync.getTransactionReceipt( + contract.artifact.deployed.transactionHash + ); + if (receipt) { + // Sync: only runs for Truffle atm... + this.trackNameByAddress( + contract.name, + contract.artifact.deployed.address + ); + contractInfo.gasData.push(utils.gas(receipt.gasUsed)); + } + } + + // Decode, getMethodIDs + const methodIDs = {}; + + let methods; + try { + methods = new ethersABI.Interface(contract.artifact.abi).functions; + } catch (err) { + utils.warnEthers(contract.name, err); + return; + } + + // Generate sighashes and remap ethers to something similar + // to abiDecoder.getMethodIDs + Object.keys(methods).forEach(key => { + const raw = ejsUtil.keccak256(key); + const sighash = ejsUtil.bufferToHex(raw).slice(2, 10); + methodIDs[sighash] = Object.assign({ fnSig: key }, methods[key]); + }); + + // Create Method Map; + Object.keys(methodIDs).forEach(key => { + const isInterface = contract.artifact.bytecode === "0x"; + const isCall = methodIDs[key].type === "call"; + const methodHasName = methodIDs[key].name !== undefined; + + if (methodHasName && !isCall && !isInterface) { + this.methods[contract.name + "_" + key] = { + key: key, + contract: contract.name, + method: methodIDs[key].name, + fnSig: methodIDs[key].fnSig, + gasData: [], + numberOfCalls: 0 }; - this.deployments.push(contractInfo); - - // Report gas used during pre-test deployments (ex: truffle migrate) - if ( - contract.deployed && - contract.deployed.transactionHash && - !this.provider - ) { - const receipt = this.sync.getTransactionReceipt( - contract.deployed.transactionHash - ); - if (receipt) { - // Sync: only runs for Truffle atm... - this.trackNameByAddress(name, contract.deployed.address); - contractInfo.gasData.push(utils.gas(receipt.gasUsed)); - } - } - - // Decode, getMethodIDs - const methodIDs = {}; - - let methods; - try { - methods = new ethersABI.Interface(contract.abi).functions; - } catch (err) { - utils.warnEthers(name, err); - return; - } - - // Generate sighashes and remap ethers to something similar - // to abiDecoder.getMethodIDs - Object.keys(methods).forEach(key => { - const raw = ejsUtil.keccak256(key); - const sighash = ejsUtil.bufferToHex(raw).slice(2, 10); - methodIDs[sighash] = Object.assign({ fnSig: key }, methods[key]); - }); - - // Create Method Map; - Object.keys(methodIDs).forEach(key => { - const isInterface = contract.bytecode === "0x"; - const isCall = methodIDs[key].type === "call"; - const methodHasName = methodIDs[key].name !== undefined; - - if (methodHasName && !isCall && !isInterface) { - this.methods[name + "_" + key] = { - key: key, - contract: name, - method: methodIDs[key].name, - fnSig: methodIDs[key].fnSig, - gasData: [], - numberOfCalls: 0 - }; - } - }); - }); - }); + } + }); + } } /** diff --git a/lib/utils.js b/lib/utils.js index bc5f2ae..940e7bc 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -220,6 +220,23 @@ const utils = { return paths; }, + /** + * Loads and parses Solidity files, returning a filtered array of contract names. + * @return {string[]} + */ + parseSoliditySources(config) { + const names = []; + const files = utils.listSolidityFiles(config.srcPath); + files.forEach(file => { + const namesForFile = utils.getContractNames(file); + const filtered = namesForFile.filter( + name => !config.excludeContracts.includes(name) + ); + filtered.forEach(item => names.push(item)); + }); + return names; + }, + // Debugging helper pretty: function(msg, obj) { console.log(`<------ ${msg} ------>\n` + JSON.stringify(obj, null, " ")); diff --git a/mock/package.json b/mock/package.json index 06fb1d7..91d39cc 100644 --- a/mock/package.json +++ b/mock/package.json @@ -1,6 +1,6 @@ { "name": "eth-gas-reporter", - "version": "0.2.17", + "version": "0.2.18", "description": "Mocha reporter which shows gas used per unit test.", "main": "index.js", "scripts": {