UNPKG

@g4g/eth-gas-reporter

Version:

Mocha reporter which shows gas used per unit test.

231 lines (206 loc) 6.88 kB
const fs = require("fs"); const parser = require("@solidity-parser/parser"); const request = require("request-promise-native"); const path = require("path"); const read = require("fs-readdir-recursive"); const colors = require("colors/safe"); const log = console.log; const utils = { /** * Expresses gas usage as a nation-state currency price * @param {Number} gas gas used * @param {Number} ethPrice e.g chf/eth * @param {Number} gasPrice in wei e.g 5000000000 (5 gwei) * @return {Number} cost of gas used (0.00) */ gasToCost: function(gas, ethPrice, gasPrice) { ethPrice = parseFloat(ethPrice); gasPrice = parseInt(gasPrice); return ((gasPrice / 1e9) * gas * ethPrice).toFixed(2); }, /** * Expresses gas usage as a % of the block gasLimit. Source: NeuFund (see issues) * @param {Number} gasUsed gas value * @param {Number} blockLimit gas limit of a block * @return {Number} percent (0.0) */ gasToPercentOfLimit: function(gasUsed, blockLimit) { return Math.round((1000 * gasUsed) / blockLimit) / 10; }, /** * Generates id for a GasData.methods entry from the input of a web3.eth.getTransaction * and a contract name * @param {String} code hex data * @return {String} id */ getMethodID: function(contractName, code) { return contractName + "_" + code.slice(2, 10); }, /** * Extracts solc settings and version info from solidity metadata * @param {Object} metadata solidity metadata * @return {Object} {version, optimizer, runs} */ getSolcInfo: function(metadata) { const missing = "----"; const info = { version: missing, optimizer: missing, runs: missing }; if (metadata) { info.version = metadata.compiler.version; info.optimizer = metadata.settings.optimizer.enabled; info.runs = metadata.settings.optimizer.runs; } return info; }, /** * Return true if transaction input and bytecode are same, ignoring library link code. * @param {String} code * @return {Bool} */ matchBinaries: function(input, bytecode) { const regExp = utils.bytecodeToBytecodeRegex(bytecode); return input.match(regExp) !== null; }, /** * Generate a regular expression string which is library link agnostic so we can match * linked bytecode deployment transaction inputs to the evm.bytecode solc output. * @param {String} bytecode * @return {String} */ bytecodeToBytecodeRegex: function(bytecode = "") { const bytecodeRegex = bytecode .replace(/__.{38}/g, ".{40}") .replace(/73f{40}/g, ".{42}"); // HACK: Node regexes can't be longer that 32767 characters. // Contracts bytecode can. We just truncate the regexes. It's safe in practice. const MAX_REGEX_LENGTH = 32767; const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH); return truncatedBytecodeRegex; }, /** * Parses files for contract names * @param {String} filePath path to file * @return {String[]} contract names */ getContractNames: function(filePath) { const names = []; const code = fs.readFileSync(filePath, "utf-8"); let ast; try { ast = parser.parse(code, { tolerant: true }); } catch (err) { utils.warnParser(filePath, err); return names; } parser.visit(ast, { ContractDefinition: function(node) { names.push(node.name); } }); return names; }, /** * Message for un-parseable files * @param {String} filePath * @param {Error} err * @return {void} */ warnParser: function(filePath, err) { log(); log(colors.red(`>>>>> WARNING <<<<<<`)); log( `Failed to parse file: "${filePath}". No data will collected for its contract(s).` ); log( `NB: some Solidity 0.6.x syntax is not supported by the JS parser yet.` ); log( `Please report the error below to github.com/consensys/solidity-parser-antlr` ); log(colors.red(`>>>>>>>>>>>>>>>>>>>>`)); log(colors.red(`${err}`)); log(); }, /** * Message for un-parseable ABI (ethers) * @param {String} name contract name * @param {Error} err * @return {void} */ warnEthers: function(name, err) { log(); log(colors.red(`>>>>> WARNING <<<<<<`)); log( `Failed to parse ABI for contract: "${name}". (Its method data will not be collected).` ); log( `NB: Some Solidity 0.6.x syntax is not supported by Ethers.js V5 AbiCoder yet.` ); log(`Please report the error below to github.com/ethers-io/ethers.js`); log(colors.red(`>>>>>>>>>>>>>>>>>>>>`)); log(colors.red(`${err}`)); log(); }, /** * Converts hex gas to decimal * @param {Number} val hex gas returned by RPC * @return {Number} decimal gas consumed by human eyes. */ gas: function(val) { return parseInt(val, 16); }, /** * Fetches gasPrices from ethgasstation (defaults to the lowest safe gas price) * and current market value of eth in currency specified by the config from * coinmarketcap (defaults to euros). Sets config.ethPrice, config.gasPrice unless these * are already set as constants in the reporter options * @param {Object} config */ setGasAndPriceRates: async function(config) { const ethgasstation = `https://ethgasstation.info/json/ethgasAPI.json`; const coinmarketcap = `https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/` + `latest?symbol=ETH&CMC_PRO_API_KEY=${config.coinmarketcap}&convert=`; const currencyKey = config.currency.toUpperCase(); const currencyPath = `${coinmarketcap}${currencyKey}`; // Currency market data: coinmarketcap if (!config.ethPrice) { try { let response = await request.get(currencyPath); response = JSON.parse(response); config.ethPrice = response.data.ETH.quote[currencyKey].price.toFixed(2); } catch (error) { config.ethPrice = null; } } // Gas price data: ethgasstation if (!config.gasPrice) { try { let response = await request.get(ethgasstation); response = JSON.parse(response); config.gasPrice = Math.round(response.safeLow / 10); } catch (error) { config.gasPrice = config.defaultGasPrice; } } }, listSolidityFiles(srcPath) { let base = `./${srcPath}/`; if (process.platform === "win32") { base = base.replace(/\\/g, "/"); } const paths = read(base) .filter(file => path.extname(file) === ".sol") .map(file => base + file); return paths; }, // Debugging helper pretty: function(msg, obj) { console.log(`<------ ${msg} ------>\n` + JSON.stringify(obj, null, " ")); console.log(`<------- END -------->\n`); } }; module.exports = utils;