diff --git a/src/constants.ts b/src/constants.ts index 00a9a00..dab7887 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -12,7 +12,7 @@ export const DEFAULT_CURRENCY_DISPLAY_PRECISION = 2; export const DEFAULT_JSON_OUTPUT_FILE = "./gasReporterOutput.json"; export const DEFAULT_GAS_PRICE_PRECISION = 5; -export const DEFAULT_GET_BLOCK_API_URL = "https://api.etherscan.io/api?module=proxy&action=eth_getBlockByNumber&tag=latest&boolean=true" +export const DEFAULT_GET_BLOCK_API_URL = "https://api.etherscan.io/api?module=proxy&action=eth_getBlockByNumber&tag=latest&boolean=false" export const DEFAULT_GAS_PRICE_API_URL = "https://api.etherscan.io/api?module=proxy&action=eth_gasPrice" export const DEFAULT_COINMARKET_BASE_URL = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/" diff --git a/src/lib/render/index.ts b/src/lib/render/index.ts index ae1f7c1..5afb9f2 100644 --- a/src/lib/render/index.ts +++ b/src/lib/render/index.ts @@ -31,7 +31,8 @@ export function getTableForFormat(hre: HardhatRuntimeEnvironment, data: GasData, * @param {GasData} data * @param {GasReporterOptions} options */ -export function render(hre: HardhatRuntimeEnvironment, data: GasData, options: GasReporterOptions) { +export function render(hre: HardhatRuntimeEnvironment, options: GasReporterOptions, warnings: string[]) { + const data = hre.__hhgrec.collector!.data; // Get table let table = getTableForFormat(hre, data, options); @@ -82,4 +83,7 @@ export function render(hre: HardhatRuntimeEnvironment, data: GasData, options: G if (options.outputJSON || process.env.CI) { generateJSONData(data, options); } + + // Write warnings + for (const warning of warnings) console.log(warning); } diff --git a/src/tasks/gasReporter.ts b/src/tasks/gasReporter.ts index bc7b764..77754c2 100644 --- a/src/tasks/gasReporter.ts +++ b/src/tasks/gasReporter.ts @@ -52,9 +52,10 @@ subtask(TASK_GAS_REPORTER_STOP).setAction( const options = hre.config.gasReporter; if (options.enabled === true && args.parallel !== true) { - await setGasAndPriceRates(options); + const warnings = await setGasAndPriceRates(options); + await hre.__hhgrec.collector?.data.runAnalysis(hre, options); - render(hre, hre.__hhgrec.collector!.data, options); + render(hre, options, warnings); } } ); diff --git a/src/utils/gas.ts b/src/utils/gas.ts index fef7cf0..8964aa6 100644 --- a/src/utils/gas.ts +++ b/src/utils/gas.ts @@ -305,7 +305,11 @@ export function hexToDecimal(val: string): number { * @return {BigInt} bigint */ export function hexToBigInt(val: string): bigint { - return BigInt(parseInt(val, 16)); + return BigInt(val); +} + +export function hexWeiToIntGwei(val: string): number { + return hexToDecimal(val) / Math.pow(10, 9); } export function normalizeTxType(_type: string) { diff --git a/src/utils/prices.ts b/src/utils/prices.ts index 70dcc8c..38e47d8 100644 --- a/src/utils/prices.ts +++ b/src/utils/prices.ts @@ -1,6 +1,13 @@ import axios from "axios"; import { DEFAULT_COINMARKET_BASE_URL } from "../constants"; import { GasReporterOptions } from "../types"; +import { + warnCMCRemoteCallFailed, + warnGasPriceRemoteCallFailed, + warnBaseFeeRemoteCallFailed, +} from "./ui"; + +import { hexWeiToIntGwei } from "./gas"; /** * Fetches gas, base, & blob fee rates from etherscan as well as current market value of @@ -13,17 +20,21 @@ import { GasReporterOptions } from "../types"; * + options.blobBaseFee * * ... unless these are already set as constants in the reporter options + * + * Returns a list of warnings generated if remote calls fail * @param {GasReporterOptions} options */ -export async function setGasAndPriceRates(options: GasReporterOptions): Promise { +export async function setGasAndPriceRates(options: GasReporterOptions): Promise { if ( (options.offline) || !options.coinmarketcap || (!options.L2 && options.tokenPrice && options.gasPrice) || (options.L2 && options.tokenPrice && options.gasPrice && options.baseFee && options.blobBaseFee) - ) return; + ) return []; let block; + const warnings: string[] = []; + const token = options.token!.toUpperCase(); const getBlockApi = options.getBlockApi; const gasPriceApi = options.gasPriceApi; @@ -47,7 +58,7 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise< currencyKey ].price.toFixed(2); } catch (error) { - /* ignore */ + warnings.push(warnCMCRemoteCallFailed(error, DEFAULT_COINMARKET_BASE_URL + currencyPath)); } } @@ -55,10 +66,12 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise< if (!options.gasPrice) { try { const response = await axiosInstance.get(gasPriceApi!); - const gasPrice = parseInt(response.data.result, 16) / Math.pow(10, 9); + checkForEtherscanRateLimitError(response.data.result); + const gasPrice = hexWeiToIntGwei(response.data.result); options.gasPrice = (gasPrice >= 1 ) ? Math.round(gasPrice) : gasPrice;; } catch (error) { options.gasPrice = 0; + warnings.push(warnGasPriceRemoteCallFailed(error, gasPriceApi!)); } } @@ -66,11 +79,11 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise< if (options.L2 && !options.baseFee) { try { block = await axiosInstance.get(getBlockApi!); - options.baseFee = Math.round( - parseInt(block.data.result.baseFeePerGas, 16) / Math.pow(10, 9) - ); + checkForEtherscanRateLimitError(block.data.result); + options.baseFee = Math.round(hexWeiToIntGwei(block.data.result.baseFeePerGas)) } catch (error) { options.baseFee = 0; + warnings.push(warnBaseFeeRemoteCallFailed(error, getBlockApi!)); } } @@ -82,8 +95,10 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise< /* if (block === undefined) { try { block = await axiosInstance.get(getBlockApi!); + checkForEtherscanRateLimitError(block.data.result); } catch (error) { options.blobBaseFee = 0; + warnings.push(warnBlobBaseFeeRemoteCallFailed(error, getBlockApi)); return; } } @@ -91,4 +106,12 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise< parseInt(block.data.result.blobBaseFeePerGas, 16) / Math.pow(10, 9) );*/ } + + return warnings; +} + +function checkForEtherscanRateLimitError(res: string) { + if (typeof res === "string" && res.includes("rate limit reached")){ + throw new Error(res); + } } diff --git a/src/utils/ui.ts b/src/utils/ui.ts index 2af2712..544f577 100644 --- a/src/utils/ui.ts +++ b/src/utils/ui.ts @@ -1,4 +1,6 @@ import chalk from "chalk"; +import { EOL } from "os"; + import { DEFAULT_GAS_PRICE_PRECISION, TABLE_NAME_LEGACY, @@ -30,6 +32,46 @@ export function getSmallestPrecisionVal(precision: number): number { return parseFloat(start); } +const startWarning = chalk.red (`>>>>> WARNING (hardhat-gas-reporter plugin) <<<<<<`) + +function remoteCallEndMessage(err: any) : string { + return `${ + chalk.bold(`Error was: `) + }${chalk.red (err.message) }${EOL + }${chalk.bold(`Price data will not be reported`) }${EOL + }${chalk.blue(`* Being rate limited? See the "Gas Price API" section in the docs.`) }${EOL + }${chalk.blue(`* Set the "offline" option to "true" to suppress these warnings`) }${EOL + }${chalk.red (`>>>>>>>>>>>>>>>>>>>>`) }${EOL}`; +}; + +export function warnCMCRemoteCallFailed(err: any, url: string): string { + return `${ + startWarning }${EOL + }${chalk.bold(`Failed to get token price from ${url}`) }${EOL + }${remoteCallEndMessage(err)}`; +} + +export function warnGasPriceRemoteCallFailed(err: any, url: string): string { + return `${ + startWarning }${EOL + }${chalk.bold(`Failed to get gas price from ${url}`) }${EOL + }${remoteCallEndMessage(err)}`; +} + +export function warnBaseFeeRemoteCallFailed(err: any, url: string): string { + return `${ + startWarning }${EOL + }${chalk.bold(`Failed to get L1 base fee from ${url}`) }${EOL + }${remoteCallEndMessage(err)}`; +} + +export function warnBlobBaseFeeRemoteCallFailed(err: any, url: string): string { + return `${ + startWarning }${EOL + }${chalk.bold(`Failed to get L1 blob base fee from ${url}`) }${EOL + }${remoteCallEndMessage(err)}`; +} + /** * Message for un-parseable ABI (ethers) * @param {string} name contract name @@ -37,15 +79,17 @@ export function getSmallestPrecisionVal(precision: number): number { * @return {void} */ export function warnEthers(name: string, err: any) { - log(); - log(chalk.red(`>>>>> WARNING <<<<<<`)); - log( - `Failed to parse ABI for contract: "${name}". (Its method data will not be collected).` - ); - log(`Please report the error below with the source that caused it to github.com/ethers-io/ethers.js`); - log(chalk.red(`>>>>>>>>>>>>>>>>>>>>`)); - log(chalk.red(`${err}`)); - log(); + const msg = `${ + startWarning }${EOL + }${chalk.bold( + `Failed to parse ABI for contract: "${name}". (Its method data will not be collected).` + ) }${EOL + }Please report the error below with the source that caused it to ` + + `github.com/cgewecke/hardhat-gas-reporter${ EOL + }${chalk.red(`>>>>>>>>>>>>>>>>>>>>`)}${EOL}${ + chalk.red(`${err}`)}`; + + log(msg); } /** @@ -54,16 +98,17 @@ export function warnEthers(name: string, err: any) { * @return {void} */ export function warnReportFormat(name: string | undefined) { - log(); - log(chalk.red(`>>>>> WARNING <<<<<<`)); - log( - `Failed to generate gas report for format: "${name!}". The available formats are: ` - ); - log(`> "${TABLE_NAME_TERMINAL}"`); - log(`> "${TABLE_NAME_MARKDOWN}"`); - log(`> "${TABLE_NAME_LEGACY}"`); - log(chalk.red(`>>>>>>>>>>>>>>>>>>>>`)); - log(); + const msg = `${ + startWarning }${EOL + }${chalk.bold( + `Failed to generate gas report for format: "${name!}". The available formats are: ` + ) }${EOL + }${chalk.green(`> "${TABLE_NAME_TERMINAL}"`) }${EOL + }${chalk.green(`> "${TABLE_NAME_MARKDOWN}"`) }${EOL + }${chalk.green(`> "${TABLE_NAME_LEGACY}"`) }${EOL + }${chalk.red(`>>>>>>>>>>>>>>>>>>>>`) }${EOL}`; + + log(msg); } /** @@ -71,14 +116,15 @@ export function warnReportFormat(name: string | undefined) { * @return {void} */ export function warnParallel() { - log(); - log(chalk.red(`>>>>> WARNING <<<<<<`)); - log( - "Gas reporting has been skipped because plugin `hardhat-gas-reporter` " + - "does not support the --parallel flag." - ); - log(chalk.red(`>>>>>>>>>>>>>>>>>>>>`)); - log(); + const msg = `${ + startWarning }${EOL + }${chalk.bold( + "Gas reporting has been skipped because plugin `hardhat-gas-reporter` " + + "does not support the --parallel flag." + ) }${EOL + }${chalk.red(`>>>>>>>>>>>>>>>>>>>>`) }${EOL}`; + + log(msg); } /** diff --git a/test/projects/options/hardhat.options.c.config.ts b/test/projects/options/hardhat.options.c.config.ts index c89c858..9c67aab 100644 --- a/test/projects/options/hardhat.options.c.config.ts +++ b/test/projects/options/hardhat.options.c.config.ts @@ -21,7 +21,8 @@ const config: HardhatUserConfig = { token: "ETH", coinmarketcap: process.env.CMC_API_KEY, L2: "optimism", - gasPriceApi: "https://api-optimistic.etherscan.io/api?module=proxy&action=eth_gasPrice", + gasPrice: 0.098775564, + baseFee: 79, reportFormat: "markdown", enabled: true, }