Skip to content

Commit f08f6cb

Browse files
authored
Merge pull request #8108 from NomicFoundation/gas-stats-runtime-size
feat: show contract runtime bytecode size in the gas stats table and JSON output
2 parents 6a946f4 + 5404ac8 commit f08f6cb

5 files changed

Lines changed: 76 additions & 29 deletions

File tree

.changeset/cold-beers-knock.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"hardhat": patch
3+
---
4+
5+
Display contract runtime bytecode size in the gas stats table and JSON output

packages/hardhat/src/internal/builtin-plugins/gas-analytics/gas-analytics-manager.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
ContractGasStatsJson,
3+
DeploymentGasStatsJsonEntry,
34
GasAnalyticsManager,
45
GasMeasurement,
56
GasStatsJson,
@@ -10,7 +11,10 @@ import type { TableItem } from "@nomicfoundation/hardhat-utils/format";
1011
import crypto from "node:crypto";
1112
import path from "node:path";
1213

13-
import { HardhatError } from "@nomicfoundation/hardhat-errors";
14+
import {
15+
HardhatError,
16+
assertHardhatInvariant,
17+
} from "@nomicfoundation/hardhat-errors";
1418
import { formatTable } from "@nomicfoundation/hardhat-utils/format";
1519
import {
1620
ensureDir,
@@ -31,8 +35,12 @@ const gasStatsLog = debug(
3135
"hardhat:core:gas-analytics:gas-analytics-manager:gas-stats",
3236
);
3337

38+
interface DeploymentGasStats extends GasStats {
39+
runtimeSize: number;
40+
}
41+
3442
interface ContractGasStats {
35-
deployment?: GasStats;
43+
deployment?: DeploymentGasStats;
3644
functions: Map<
3745
string, // function name or signature (if overloaded)
3846
GasStats
@@ -53,6 +61,7 @@ type GasMeasurementsByContract = Map<string, ContractGasMeasurements>;
5361

5462
interface ContractGasMeasurements {
5563
deployments: number[];
64+
deploymentRuntimeSize?: number;
5665
functions: Map<
5766
string, // functionSig
5867
number[]
@@ -176,12 +185,18 @@ export class GasAnalyticsManagerImplementation implements GasAnalyticsManager {
176185
};
177186

178187
if (measurements.deployments.length > 0) {
188+
assertHardhatInvariant(
189+
measurements.deploymentRuntimeSize !== undefined,
190+
"deploymentRuntimeSize must be set when deployments exist",
191+
);
192+
179193
contractGasStats.deployment = {
180194
min: Math.min(...measurements.deployments),
181195
max: Math.max(...measurements.deployments),
182196
avg: Math.round(avg(measurements.deployments)),
183197
median: Math.round(median(measurements.deployments)),
184198
count: measurements.deployments.length,
199+
runtimeSize: measurements.deploymentRuntimeSize,
185200
};
186201
}
187202

@@ -236,6 +251,10 @@ export class GasAnalyticsManagerImplementation implements GasAnalyticsManager {
236251

237252
if (currentMeasurement.type === "deployment") {
238253
contractMeasurements.deployments.push(currentMeasurement.gas);
254+
if (contractMeasurements.deploymentRuntimeSize === undefined) {
255+
contractMeasurements.deploymentRuntimeSize =
256+
currentMeasurement.runtimeSize;
257+
}
239258
} else {
240259
let measurements = contractMeasurements.functions.get(
241260
currentMeasurement.functionSig,
@@ -337,6 +356,13 @@ export class GasAnalyticsManagerImplementation implements GasAnalyticsManager {
337356
`${contractGasStats.deployment.count}`,
338357
],
339358
});
359+
rows.push({
360+
type: "header",
361+
cells: [
362+
chalk.yellow("Bytecode size"),
363+
`${contractGasStats.deployment.runtimeSize}`,
364+
],
365+
});
340366
}
341367
}
342368

@@ -361,7 +387,7 @@ export class GasAnalyticsManagerImplementation implements GasAnalyticsManager {
361387
for (const { userFqn, stats } of sortedContracts) {
362388
const { sourceName, contractName } = parseFullyQualifiedName(userFqn);
363389

364-
const deployment: GasStatsJsonEntry | null =
390+
const deployment: DeploymentGasStatsJsonEntry | null =
365391
stats.deployment !== undefined ? { ...stats.deployment } : null;
366392

367393
let functions: Record<string, GasStatsJsonEntry> | null = null;

packages/hardhat/src/internal/builtin-plugins/gas-analytics/types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ export interface GasStatsJsonEntry {
99
count: number;
1010
}
1111

12+
/**
13+
* Gas statistics for a deployment, including bytecode size.
14+
*/
15+
export interface DeploymentGasStatsJsonEntry extends GasStatsJsonEntry {
16+
runtimeSize: number;
17+
}
18+
1219
/**
1320
* Gas statistics for a single contract in the JSON output.
1421
* `deployment` is null when the contract was never deployed during the test run
@@ -18,7 +25,7 @@ export interface GasStatsJsonEntry {
1825
export interface ContractGasStatsJson {
1926
sourceName: string;
2027
contractName: string;
21-
deployment: GasStatsJsonEntry | null;
28+
deployment: DeploymentGasStatsJsonEntry | null;
2229
functions: Record<string, GasStatsJsonEntry> | null;
2330
}
2431

@@ -43,7 +50,7 @@ interface FunctionGasMeasurement extends BaseGasMeasurement {
4350

4451
interface DeploymentGasMeasurement extends BaseGasMeasurement {
4552
type: "deployment";
46-
size: number;
53+
runtimeSize: number;
4754
}
4855

4956
export type GasMeasurement = FunctionGasMeasurement | DeploymentGasMeasurement;

packages/hardhat/src/internal/builtin-plugins/network-manager/edr/utils/convert-to-edr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ export function edrGasReportToHardhatGasMeasurements(
404404
contractFqn,
405405
type: "deployment",
406406
gas: Number(deployment.gas),
407-
size: Number(deployment.size),
407+
runtimeSize: Number(deployment.runtimeSize),
408408
});
409409
}
410410
}

packages/hardhat/test/internal/builtin-plugins/gas-analytics/gas-analytics-manager.ts

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ describe("gas-analytics-manager", () => {
6767
type: "deployment",
6868
contractFqn: "project/contracts/MyContract.sol:MyContract",
6969
gas: 500000,
70-
size: 2048,
70+
runtimeSize: 2048,
7171
};
7272

7373
manager.addGasMeasurement(deploymentMeasurement);
@@ -90,7 +90,7 @@ describe("gas-analytics-manager", () => {
9090
type: "deployment",
9191
contractFqn: "project/contracts/MyContract.sol:MyContract",
9292
gas: 500000,
93-
size: 2048,
93+
runtimeSize: 2048,
9494
};
9595

9696
manager.addGasMeasurement(measurement1);
@@ -115,7 +115,7 @@ describe("gas-analytics-manager", () => {
115115
type: "deployment",
116116
contractFqn: "project/contracts/MyContract.sol:MyContract",
117117
gas: 500000,
118-
size: 2048,
118+
runtimeSize: 2048,
119119
};
120120

121121
manager.addGasMeasurement(measurement1);
@@ -154,7 +154,7 @@ describe("gas-analytics-manager", () => {
154154
type: "deployment",
155155
contractFqn: "project/contracts/MyContract.sol:MyContract",
156156
gas: 500000,
157-
size: 2048,
157+
runtimeSize: 2048,
158158
};
159159

160160
manager.addGasMeasurement(measurement1);
@@ -179,7 +179,7 @@ describe("gas-analytics-manager", () => {
179179
type: "deployment",
180180
contractFqn: "project/contracts/MyContract.sol:MyContract",
181181
gas: 500000,
182-
size: 2048,
182+
runtimeSize: 2048,
183183
};
184184

185185
manager.addGasMeasurement(measurement1);
@@ -217,7 +217,7 @@ describe("gas-analytics-manager", () => {
217217
type: "deployment",
218218
contractFqn: "project/contracts/MyContract.sol:MyContract",
219219
gas: 500000,
220-
size: 2048,
220+
runtimeSize: 2048,
221221
};
222222
manager.addGasMeasurement(measurement1);
223223
manager.addGasMeasurement(measurement2);
@@ -274,7 +274,7 @@ describe("gas-analytics-manager", () => {
274274
type: "deployment",
275275
contractFqn: "project/contracts/MyContract.sol:MyContract",
276276
gas: 500000,
277-
size: 2048,
277+
runtimeSize: 2048,
278278
};
279279
manager.addGasMeasurement(measurement1);
280280
manager.addGasMeasurement(measurement2);
@@ -430,7 +430,7 @@ describe("gas-analytics-manager", () => {
430430
type: "deployment",
431431
contractFqn: "project/contracts/MyContract.sol:MyContract",
432432
gas: 500000,
433-
size: 2048,
433+
runtimeSize: 2048,
434434
});
435435

436436
const result = manager._aggregateGasMeasurements();
@@ -444,6 +444,7 @@ describe("gas-analytics-manager", () => {
444444
"Contract measurements should be defined",
445445
);
446446
assert.deepEqual(contractMeasurements.deployments, [500000]);
447+
assert.equal(contractMeasurements.deploymentRuntimeSize, 2048);
447448
assert.equal(contractMeasurements.functions.size, 0);
448449
});
449450

@@ -453,7 +454,7 @@ describe("gas-analytics-manager", () => {
453454
type: "deployment",
454455
contractFqn: "project/contracts/MyContract.sol:MyContract",
455456
gas: 500000,
456-
size: 2048,
457+
runtimeSize: 2048,
457458
});
458459
manager.addGasMeasurement({
459460
type: "function",
@@ -480,6 +481,7 @@ describe("gas-analytics-manager", () => {
480481
);
481482

482483
assert.deepEqual(contractMeasurements.deployments, [500000]);
484+
assert.equal(contractMeasurements.deploymentRuntimeSize, 2048);
483485

484486
assert.equal(contractMeasurements.functions.size, 2);
485487
const transferMeasurements = contractMeasurements.functions.get(
@@ -512,7 +514,7 @@ describe("gas-analytics-manager", () => {
512514
type: "deployment",
513515
contractFqn: "project/contracts/TokenB.sol:TokenB",
514516
gas: 600000,
515-
size: 3072,
517+
runtimeSize: 3072,
516518
});
517519
manager.addGasMeasurement({
518520
type: "function",
@@ -550,6 +552,7 @@ describe("gas-analytics-manager", () => {
550552
"TokenB measurements should be defined",
551553
);
552554
assert.deepEqual(tokenBMeasurements.deployments, [600000]);
555+
assert.equal(tokenBMeasurements.deploymentRuntimeSize, 3072);
553556
assert.equal(tokenBMeasurements.functions.size, 1);
554557
const burnMeasurements =
555558
tokenBMeasurements.functions.get("burn(uint256)");
@@ -655,13 +658,13 @@ describe("gas-analytics-manager", () => {
655658
type: "deployment",
656659
contractFqn: "project/contracts/MyContract.sol:MyContract",
657660
gas: 500000,
658-
size: 2048,
661+
runtimeSize: 2048,
659662
});
660663
manager.addGasMeasurement({
661664
type: "deployment",
662665
contractFqn: "project/contracts/MyContract.sol:MyContract",
663666
gas: 600000,
664-
size: 3072,
667+
runtimeSize: 3072,
665668
});
666669

667670
const result = manager._aggregateGasMeasurements();
@@ -675,6 +678,7 @@ describe("gas-analytics-manager", () => {
675678
"Contract measurements should be defined",
676679
);
677680
assert.deepEqual(contractMeasurements.deployments, [500000, 600000]);
681+
assert.equal(contractMeasurements.deploymentRuntimeSize, 2048);
678682
});
679683
});
680684

@@ -730,19 +734,19 @@ describe("gas-analytics-manager", () => {
730734
type: "deployment",
731735
contractFqn: "project/contracts/MyContract.sol:MyContract",
732736
gas: 400000,
733-
size: 2048,
737+
runtimeSize: 2048,
734738
});
735739
manager.addGasMeasurement({
736740
type: "deployment",
737741
contractFqn: "project/contracts/MyContract.sol:MyContract",
738742
gas: 500000,
739-
size: 2048,
743+
runtimeSize: 2048,
740744
});
741745
manager.addGasMeasurement({
742746
type: "deployment",
743747
contractFqn: "project/contracts/MyContract.sol:MyContract",
744748
gas: 600000,
745-
size: 3072,
749+
runtimeSize: 3072,
746750
});
747751

748752
const gasStats = manager._calculateGasStats();
@@ -764,6 +768,7 @@ describe("gas-analytics-manager", () => {
764768
assert.equal(contractStats.deployment.avg, 500000);
765769
assert.equal(contractStats.deployment.median, 500000);
766770
assert.equal(contractStats.deployment.count, 3);
771+
assert.equal(contractStats.deployment.runtimeSize, 2048);
767772
});
768773

769774
it("should calculate stats for multiple contracts", () => {
@@ -954,6 +959,7 @@ describe("gas-analytics-manager", () => {
954959
avg: 500000,
955960
median: 500000,
956961
count: 3,
962+
runtimeSize: 2048,
957963
},
958964
functions: new Map([
959965
// Functions are added in non-alphabetical order to test sorting
@@ -998,7 +1004,9 @@ describe("gas-analytics-manager", () => {
9981004
${chalk.yellow("Deployment")}${chalk.yellow("Min")}${chalk.yellow("Average")}${chalk.yellow("Median")}${chalk.yellow("Max")}${chalk.yellow("#deployments")}
9991005
╟─────────────────────────────────┼────────┼─────────┼────────┼────────┼──────────────╢
10001006
║ │ 400000 │ 500000 │ 500000 │ 600000 │ 3 ║
1001-
╚═════════════════════════════════╧════════╧═════════╧════════╧════════╧══════════════╝
1007+
╟─────────────────────────────────┼────────┼─────────┴────────┴────────┴──────────────╢
1008+
${chalk.yellow("Bytecode size")} │ 2048 │ ║
1009+
╚═════════════════════════════════╧════════╧══════════════════════════════════════════╝
10021010
╔═════════════════════════════════════════════════════════════════════════════════════╗
10031011
${chalk.cyan.bold("contracts/TokenA.sol:TokenA")}
10041012
╟─────────────────────────────────┬────────┬─────────┬────────┬────────┬──────────────╢
@@ -1116,7 +1124,7 @@ describe("gas-analytics-manager", () => {
11161124
type: "deployment",
11171125
contractFqn: "project/contracts/MyContract.sol:MyContract",
11181126
gas: 500000,
1119-
size: 2048,
1127+
runtimeSize: 2048,
11201128
});
11211129
manager.addGasMeasurement({
11221130
type: "function",
@@ -1138,6 +1146,7 @@ describe("gas-analytics-manager", () => {
11381146
avg: 500000,
11391147
median: 500000,
11401148
count: 1,
1149+
runtimeSize: 2048,
11411150
});
11421151
assert.ok(contract.functions !== null, "functions should not be null");
11431152
assert.deepEqual(contract.functions.transfer, {
@@ -1172,7 +1181,7 @@ describe("gas-analytics-manager", () => {
11721181
type: "deployment",
11731182
contractFqn: "project/contracts/Factory.sol:Factory",
11741183
gas: 300000,
1175-
size: 1024,
1184+
runtimeSize: 1024,
11761185
});
11771186
const stats = manager._calculateGasStats();
11781187
const result = manager._generateGasStatsJson(stats);
@@ -1192,13 +1201,13 @@ describe("gas-analytics-manager", () => {
11921201
type: "deployment",
11931202
contractFqn: "project/contracts/ZContract.sol:ZContract",
11941203
gas: 100000,
1195-
size: 512,
1204+
runtimeSize: 512,
11961205
});
11971206
manager.addGasMeasurement({
11981207
type: "deployment",
11991208
contractFqn: "project/contracts/AContract.sol:AContract",
12001209
gas: 200000,
1201-
size: 512,
1210+
runtimeSize: 512,
12021211
});
12031212
const stats = manager._calculateGasStats();
12041213
const result = manager._generateGasStatsJson(stats);
@@ -1276,7 +1285,7 @@ describe("gas-analytics-manager", () => {
12761285
type: "deployment",
12771286
contractFqn: "project/contracts/MyContract.sol:MyContract",
12781287
gas: 100000,
1279-
size: 512,
1288+
runtimeSize: 512,
12801289
});
12811290
const stats = manager._calculateGasStats();
12821291
const result = manager._generateGasStatsJson(stats);
@@ -1324,7 +1333,7 @@ describe("gas-analytics-manager", () => {
13241333
type: "deployment",
13251334
contractFqn: internalFqn,
13261335
gas: 250000,
1327-
size: 1024,
1336+
runtimeSize: 1024,
13281337
});
13291338
const stats = manager._calculateGasStats();
13301339
const result = manager._generateGasStatsJson(stats);
@@ -1398,7 +1407,7 @@ describe("gas-analytics-manager", () => {
13981407
type: "deployment",
13991408
contractFqn: "project/contracts/MyContract.sol:MyContract",
14001409
gas: 500000,
1401-
size: 2048,
1410+
runtimeSize: 2048,
14021411
});
14031412
await manager.saveGasMeasurements("test-id");
14041413

0 commit comments

Comments
 (0)