Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track gas deltas #230

Merged
merged 6 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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
Loading