Skip to content

Commit

Permalink
Add option to track method gas deltas between runs (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xVolosnikov committed May 22, 2024
1 parent ac88595 commit 63c172a
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const config: HardhatUserConfig = {

| Options | Type | Default | Description |
| :------------------------------ | :--------: | :--------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| checkGasDeltas | _bool_ | `false` | Compare gas values to previous results |
| currency | _string_ | `USD` | National currency to represent gas costs in. Exchange rates are loaded at runtime from the `coinmarketcap` api. Available currency codes can be found [here][5] |
| coinmarketcap | _string_ | - | [API key][3] to use when fetching live token price data |
| enabled | _bool_ | `true` | Produce gas reports with `hardhat test` |
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const DEFAULT_ARBITRUM_HARDFORK = "arbOS11";
export const TOOLCHAIN_HARDHAT = "hardhat";
export const TOOLCHAIN_FOUNDRY = "foundry";

export const CACHE_FILE_NAME = ".hardhat_gas_reporter_output.json";

// EVM
export const EVM_BASE_TX_COST = 21000;
export const DEFAULT_BLOB_BASE_FEE = 10; // gwei
Expand Down
29 changes: 29 additions & 0 deletions src/lib/gasData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,35 @@ export class GasData {
}
}

/**
* Calculate gas deltas compared to previous data, if applicable
* @param {GasData} previousData previous gas data
*/
public addDeltas(previousData: GasData) {
Object.keys(this.methods).forEach(key => {
if (!previousData.methods[key]) return;

const currentMethod = this.methods[key];
const prevMethod = previousData.methods[key];

if (currentMethod.min !== undefined && prevMethod.min !== undefined) {
currentMethod.minDelta = currentMethod.min! - prevMethod.min!;
}

if (currentMethod.max !== undefined && prevMethod.max !== undefined) {
currentMethod.maxDelta = currentMethod.max! - prevMethod.max!;
}

if (currentMethod.executionGasAverage !== undefined && prevMethod.executionGasAverage !== undefined) {
currentMethod.executionGasAverageDelta = currentMethod.executionGasAverage! - prevMethod.executionGasAverage!;
}

if (currentMethod.calldataGasAverage !== undefined && prevMethod.calldataGasAverage !== undefined) {
currentMethod.calldataGasAverageDelta = currentMethod.calldataGasAverage! - prevMethod.calldataGasAverage!;
}
})
}

/**
* Map a contract name to pre-generated hash of the code stored at an address
* @param {String} name contract name
Expand Down
23 changes: 21 additions & 2 deletions src/lib/render/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import type { GasData } from "../gasData";
import { writeFileSync } from "fs";
import path from "path";

import { HardhatRuntimeEnvironment as HRE } from "hardhat/types";
import {
TABLE_NAME_LEGACY,
TABLE_NAME_MARKDOWN,
TABLE_NAME_TERMINAL
TABLE_NAME_TERMINAL,
CACHE_FILE_NAME
} from "../../constants";
import { getSolcInfo } from "../../utils/sources";
import { warnReportFormat } from "../../utils/ui";
import { GasReporterOptions } from "../../types";
import { generateTerminalTextTable } from "./terminal";
import { generateLegacyTextTable } from "./legacy";
import { generateMarkdownTable} from "./markdown";
import { generateJSONData } from "./json";
import { generateJSONData, loadJSONCache } from "./json";


/**
Expand Down Expand Up @@ -55,6 +57,18 @@ export function render(
options.blockGasLimit = hre.__hhgrec.blockGasLimit;
options.solcInfo = getSolcInfo(hre.config.solidity.compilers[0]);

if (options.checkGasDeltas) {
options.cachePath = options.cachePath || path.resolve(
hre.config.paths.cache,
CACHE_FILE_NAME
);

try {
const previousData = loadJSONCache(options);
data.addDeltas(previousData.data!);
} catch {};
}


// Get table
let table = getTableForFormat(hre, data, options, toolchain);
Expand Down Expand Up @@ -106,6 +120,11 @@ export function render(
generateJSONData(data, options, toolchain);
}

if (options.checkGasDeltas) {
options.outputJSONFile = options.cachePath!;
generateJSONData(data, options, toolchain);
}

// Write warnings
for (const warning of warnings) console.log(warning);
}
13 changes: 12 additions & 1 deletion src/lib/render/json.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { writeFileSync } from "fs";
import { writeFileSync, readFileSync } from "fs";
import { GasData } from "../gasData";
import { GasReporterOptions, GasReporterOutput } from "../../types";

Expand Down Expand Up @@ -28,6 +28,17 @@ export function generateJSONData(
writeFileSync(options.outputJSONFile!, JSON.stringify(output, null, ' '));
}

/**
* Reads previous acccumulated data and options from cache so it can be used to calculate deltas
* @param {GasReporterOptions} options
* @returns {GasReporterOptions} previous data and options
*/
export function loadJSONCache(
options: GasReporterOptions
): GasReporterOutput {
return JSON.parse(readFileSync(options.cachePath!).toString());
}

/**
* Removes extraneous data and attached methods
* @param {GasData} data
Expand Down
18 changes: 15 additions & 3 deletions src/lib/render/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
entitleMarkdown,
getCommonTableVals,
costIsBelowPrecision,
markdownBold
markdownBold,
renderWithGasDelta
} from "../../utils/ui";

import { GasReporterOptions, MethodDataItem } from "../../types";
Expand Down Expand Up @@ -135,6 +136,11 @@ export function generateMarkdownTable(
? "-"
: commify(method.calldataGasAverage);
};

if (options.checkGasDeltas) {
stats.executionGasAverage = renderWithGasDelta(stats.executionGasAverage, method.executionGasAverageDelta || 0);
stats.calldataGasAverage = renderWithGasDelta(stats.calldataGasAverage, method.calldataGasAverageDelta || 0);
}
} else {
stats.executionGasAverage = "-";
stats.cost = "-";
Expand All @@ -146,8 +152,14 @@ export function generateMarkdownTable(

if (method.min && method.max) {
const uniform = (method.min === method.max);
stats.min = uniform ? "-" : commify(method.min!);
stats.max = uniform ? "-" : commify(method.max!);
let min = commify(method.min!);
let max = commify(method.max!)
if (options.checkGasDeltas) {
min = renderWithGasDelta(min, method.minDelta || 0);
max = renderWithGasDelta(max, method.maxDelta || 0);
}
stats.min = uniform ? "-" : min;
stats.max = uniform ? "-" : max;
}

stats.numberOfCalls = method.numberOfCalls.toString();
Expand Down
17 changes: 14 additions & 3 deletions src/lib/render/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
getCommonTableVals,
indentText,
indentTextWithSymbol,
costIsBelowPrecision
costIsBelowPrecision,
renderWithGasDelta
} from "../../utils/ui";

import { GasReporterOptions, MethodDataItem } from "../../types";
Expand Down Expand Up @@ -106,6 +107,10 @@ export function generateTerminalTextTable(
? commify(method.calldataGasAverage)
: chalk.grey("-");

if (options.checkGasDeltas) {
stats.executionGasAverage = renderWithGasDelta(stats.executionGasAverage, method.executionGasAverageDelta || 0, true);
stats.calldataGasAverage = renderWithGasDelta(stats.calldataGasAverage, method.calldataGasAverageDelta || 0, true);
}
} else {
stats.executionGasAverage = chalk.grey("-");
stats.cost = chalk.grey("-");
Expand All @@ -117,8 +122,14 @@ export function generateTerminalTextTable(

if (method.min && method.max) {
const uniform = (method.min === method.max);
stats.min = uniform ? chalk.grey("-") : chalk.cyan(commify(method.min!));
stats.max = uniform ? chalk.grey("-") : chalk.red(commify(method.max!));
let min = chalk.cyan(commify(method.min!));
let max = chalk.red(commify(method.max!));
if (options.checkGasDeltas) {
min = renderWithGasDelta(min, method.minDelta || 0, true);
max = renderWithGasDelta(max, method.maxDelta || 0, true);
}
stats.min = uniform ? chalk.grey("-") : min;
stats.max = uniform ? chalk.grey("-") : max;
}

const fnName = options.showMethodSig ? method.fnSig : method.method;
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export interface GasReporterOptions {
/** @property Etherscan-like url to fetch blobBasefee from */
blobBaseFeeApi?: string;

/** @property Compare gas values to previous results */
checkGasDeltas?: boolean;

/** @property API key to access token/currency market price data with */
coinmarketcap?: string;

Expand Down Expand Up @@ -155,6 +158,9 @@ export interface GasReporterOptions {

/** @ignore */
blockGasLimit?: number;

/** @ignore */
cachePath?: string;
}

export interface GasReporterExecutionContext {
Expand Down Expand Up @@ -214,6 +220,10 @@ export interface MethodDataItem {
executionGasAverage?: number,
calldataGasAverage?: number,
cost?: string,
minDelta?: number,
maxDelta?: number,
executionGasAverageDelta? :number
calldataGasAverageDelta?: number,
}

export interface MethodData {[key: string]: MethodDataItem }
Expand Down
13 changes: 13 additions & 0 deletions src/utils/ui.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chalk from "chalk";
import { EOL } from "os";
import { commify } from "@ethersproject/units";

import {
DEFAULT_GAS_PRICE_PRECISION,
Expand Down Expand Up @@ -39,6 +40,18 @@ export function markdownItalic(val: string) {
return `*${val}*`
}

export function renderWithGasDelta(val: string, delta: number, withColor?: boolean) {
if (delta == 0) return val;

Check failure on line 44 in src/utils/ui.ts

View workflow job for this annotation

GitHub Actions / lint

Expected '===' and instead saw '=='

let deltaString = commify(delta);
if (withColor) {
deltaString = delta > 0 ? chalk.redBright(`+${deltaString}`) : chalk.green(`${deltaString}`);
} else {
deltaString = delta > 0 ? `+${deltaString}` : `${deltaString}`;
}
return `${val} ${deltaString}`;
}

export function getSmallestPrecisionVal(precision: number): number {
let start = "."
for (let i = 0; i < precision - 1; i++ ) {
Expand Down
22 changes: 22 additions & 0 deletions test/integration/options.a.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useEnvironment, findMethod, findDeployment } from "../helpers";
* + Display full method signature
* + Dark mode
* + RST titles
* + Gas deltas
*/
describe("Options A", function () {
let output: GasReporterOutput;
Expand Down Expand Up @@ -87,4 +88,25 @@ describe("Options A", function () {
assert.equal(methodA?.numberOfCalls, 1);
assert.equal(methodB?.numberOfCalls, 1);
});

it("calculates gas deltas for method calls", async function(){
process.env.GAS_DELTA = "true";
await this.env.run(TASK_TEST, { testFiles: [] });
process.env.GAS_DELTA = "";

const _output = JSON.parse(readFileSync(options.cachePath!, 'utf-8'));
const _methods = _output.data!.methods;
const _options = _output.options;

const method = findMethod(_methods, "VariableCosts", "addToMap");

if (_options.cachePath) {
try {
execSync(`rm ${_options.cachePath}`)
} catch {}
}

assert.isNumber(method!.executionGasAverageDelta!);
assert.notEqual(method!.executionGasAverageDelta!, 0);
});
});
3 changes: 2 additions & 1 deletion test/projects/options/hardhat.options.a.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ const config: HardhatUserConfig = {
enabled: true,
darkMode: true,
proxyResolver: new EtherRouterResolver(),
includeBytecodeInJSON: true
includeBytecodeInJSON: true,
checkGasDeltas: true
}
};

Expand Down
2 changes: 1 addition & 1 deletion test/projects/options/test/variablecosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Contract } from "ethers";

describe("VariableCosts", function() {
const one = [1];
const three = [2, 3, 4];
const three = process.env.GAS_DELTA === "true" ? [2, 3, 4, 5, 6, 7, 8] : [2, 3, 4]; // changing gas values if required
const five = [5, 6, 7, 8, 9];
let instance: Contract;
let walletB: any;
Expand Down

0 comments on commit 63c172a

Please sign in to comment.