Skip to content

Commit

Permalink
Add Base chain support
Browse files Browse the repository at this point in the history
  • Loading branch information
cgewecke committed Mar 30, 2024
1 parent 5c17386 commit e4792e9
Show file tree
Hide file tree
Showing 15 changed files with 234 additions and 25 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ const config: HardhatUserConfig = {
| blobBaseFeeApi | _string_ | - | URL to fetch live *execution* network blob base fee from. (By default, this is auto-configured based on the `L1` or `L2` setting) |
| gasPriceApi | _string_ | - | URL to fetch live *execution* network gas price from. (By default, this is auto-configured based on the `L1` or `L2` setting) |
| getBlockApi | _string_ | - | URL to fetch L1 block header from when simulating L2. (By default, this is auto-configured based on the `L2` setting) |
| opStackBaseFeeScalar | _number_ | - | Scalar applied to L1 base fee when calculating L1 data cost (see [Advanced Usage][12]) |
| opStackBlobBaseFeeScalar | _number_ | - | Scalar applied to L1 blob base fee when calculating L1 data cost (see [Advanced Usage][12]) |
| token | _string_ | - | Network token gas fees are denominated in (ex:"ETH"). (By default, this is auto-configured based on the `L1` or `L2` setting) |
| tokenPrice | _string_ | - | Network token price per nation state currency unit. (To denominate costs *in network token* set this to `"1"`) |

Expand All @@ -132,11 +134,12 @@ npx hardhat hhgas:merge "gasReporterOutput-*.json"

## Supported Networks

API keys for the networks this plugin auto-configures via the `L1` and `L2` options are available from the links below. These aren't strictly required - you only need to set them if you start seeing rate-limit warnings.
API keys for the networks this plugin auto-configures via the `L1` and `L2` options are available from the links below. In many cases these aren't equired - you'll only need to set them if you start seeing rate-limit warnings.

**L2**

+ [optimism][109]
+ [base][110] (live `blobBaseFee` prices require an API key)
+ [optimism][109] (live `blobBaseFee` prices require an API key)

**L1**

Expand Down Expand Up @@ -165,6 +168,7 @@ You can support hardhat-gas-reporter via [DRIPS][11], a public goods protocol th
[9]: https://github.com/cgewecke/hardhat-gas-reporter/blob/master/docs/advanced.md#json-output
[10]: https://github.com/cgewecke/hardhat-gas-reporter/blob/master/docs/advanced.md#markdown-format-example
[11]: https://www.drips.network/app/projects/github/cgewecke/hardhat-gas-reporter
[12]: https://github.com/cgewecke/hardhat-gas-reporter/blob/master/docs/advanced.md#op-stack-l1-data-costs
[100]: https://optimistic.etherscan.io
[101]: https://docs.etherscan.io/getting-started/viewing-api-usage-statistics
[102]: https://docs.polygonscan.com/getting-started/viewing-api-usage-statistics
Expand All @@ -175,3 +179,4 @@ You can support hardhat-gas-reporter via [DRIPS][11], a public goods protocol th
[107]: https://docs.gnosisscan.io/getting-started/viewing-api-usage-statistics
[108]: https://snowtrace.io/
[109]: https://docs.optimism.etherscan.io/getting-started/viewing-api-usage-statistics
[110]: https://docs.basescan.org/getting-started/viewing-api-usage-statistics
24 changes: 24 additions & 0 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* [Proxy Resolvers](#proxy-resolvers)
* [Remote Contracts](#remote-contracts)
* [Intrinsic Gas](#intrinsic-gas)
* [OP Stack L1 Data Costs](#op-stack-l1-data-costs)
* [JSON output](#json-output)
* [Markdown Format Example](#markdown-format-example)

## Config Examples

Expand Down Expand Up @@ -205,6 +207,25 @@ Gas Calculation:
:warning: The execution costs minus intrinsic gas overhead reported by the plugin are **only lower bounds** of what the actual cost will be. (Read [wolfio/evm-opcodes/gas][8] for more info about the complex accounting applied to any given method invocation in reality)
## OP Stack L1 Data Costs
Optimism and Base networks L1 data costs are calculated using [the formulae specified in their docs][9]. If you've configured the reporter to fetch market data, the `baseFee` and `blobBaseFee` components of these equations are pulled from the live network. However, the scalar constants applied to these fees are hardcoded by default (because they're set by the operator & don't fluctuate in a demand auction).
You can verify that the scalar values correctly reflect the current network by checking them at:
+ [Base GasPriceOracle][10]
+ [Optimism GasPriceOracle][11]
You can override the plugin's defaults by setting the relevant options:
```ts
const config: HardhatUserConfig = {
gasReporter: {
opStackBaseFeeScalar: 1101,
opStackBlobBaseFeeScalar: 659851
}
}
```
## JSON Output
The JSON output includes the full gas reporter option state (API keys are redacted) and all collected gas data for methods and deployments. The object has the following interface:
Expand Down Expand Up @@ -313,3 +334,6 @@ Example of a report produced with `reportFormat: "markdown"`:
[4]: https://github.com/cgewecke/hardhat-gas-reporter/blob/master/src/types.ts
[5]: https://github.com/cgewecke/hardhat-gas-reporter/issues/195
[8]: https://github.com/wolflo/evm-opcodes/blob/main/gas.md#a0-0-intrinsic-gas
[9]: https://docs.optimism.io/stack/transactions/fees#ecotone
[10]: https://basescan.org/address/0x420000000000000000000000000000000000000F#readProxyContract
[11]: https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000015#readProxyContract
14 changes: 14 additions & 0 deletions scripts/gen-options-md.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,20 @@ advancedSubtitle,
"URL to fetch L1 block header from when simulating L2. (By default, this is auto-configured " +
"based on the `L2` setting)"
],
// opStackBaseFeeScalar
[
"opStackBaseFeeScalar",
"_number_",
"-",
"Scalar applied to L1 base fee when calculating L1 data cost (see [Advanced Usage][12])"
],
// opStackBlobBaseFeeScalar
[
"opStackBlobBaseFeeScalar",
"_number_",
"-",
"Scalar applied to L1 blob base fee when calculating L1 data cost (see [Advanced Usage][12])"
],
// token
[
"token",
Expand Down
9 changes: 6 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,18 @@ export const OPTIMISM_BEDROCK_FIXED_OVERHEAD = 188;
export const OPTIMISM_BEDROCK_DYNAMIC_OVERHEAD = 0.684;

// These params are configured by node operators and may vary
// Values are suggested default values from:
// https://docs.optimism.io/builders/chain-operators/management/blobs
// Values were read from the GasPriceOracle contract at:
// https://optimistic.etherscan.io/address/0x420000000000000000000000000000000000000F
export const OPTIMISM_ECOTONE_BASE_FEE_SCALAR = 1368
export const OPTIMISM_ECOTONE_BLOB_BASE_FEE_SCALAR = 810949

// https://basescan.org/address/0x420000000000000000000000000000000000000F
export const BASE_ECOTONE_BASE_FEE_SCALAR = 1101;
export const BASE_ECOTONE_BLOB_BASE_FEE_SCALAR = 659851;

export const UNICODE_CIRCLE = "◯";
export const UNICODE_TRIANGLE = "△"

export const OPTIMISM_GAS_ORACLE_ADDRESS = "0xb528d11cc114e026f138fe568744c6d45ce6da7a";
export const OPTIMISM_GAS_ORACLE_ABI_PARTIAL = [
{
constant: true,
Expand Down
31 changes: 29 additions & 2 deletions src/lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
DEFAULT_CURRENCY_DISPLAY_PRECISION,
DEFAULT_JSON_OUTPUT_FILE,
DEFAULT_OPTIMISM_HARDFORK,
BASE_ECOTONE_BASE_FEE_SCALAR,
BASE_ECOTONE_BLOB_BASE_FEE_SCALAR,
OPTIMISM_ECOTONE_BASE_FEE_SCALAR,
OPTIMISM_ECOTONE_BLOB_BASE_FEE_SCALAR,
TABLE_NAME_TERMINAL
} from "../constants";

Expand Down Expand Up @@ -40,13 +44,34 @@ function isOptimismHardfork(hardfork: string | undefined) {
export function getDefaultOptions(userConfig: Readonly<HardhatUserConfig>): GasReporterOptions {
// let arbitrumHardfork: ArbitrumHardfork;
let optimismHardfork: OptimismHardfork;
let opStackBaseFeeScalar: number = 0;
let opStackBlobBaseFeeScalar: number = 0;

const userOptions = userConfig.gasReporter;

// NB: silently coercing to default if there's a misspelling or option not avail
if (userOptions) {
if (userOptions.L2 === "optimism" && !isOptimismHardfork(userOptions.optimismHardfork)){
optimismHardfork = DEFAULT_OPTIMISM_HARDFORK;
if (userOptions.L2 === "optimism" || userOptions.L2 === "base")
if (!isOptimismHardfork(userOptions.optimismHardfork)){
optimismHardfork = DEFAULT_OPTIMISM_HARDFORK;
}

if (userOptions.L2 === "optimism") {
if (!userOptions.opStackBaseFeeScalar) {
opStackBaseFeeScalar = OPTIMISM_ECOTONE_BASE_FEE_SCALAR;
}
if (!userOptions.opStackBlobBaseFeeScalar) {
opStackBlobBaseFeeScalar = OPTIMISM_ECOTONE_BLOB_BASE_FEE_SCALAR
}
}

if (userOptions.L2 === "base") {
if (!userOptions.opStackBaseFeeScalar) {
opStackBaseFeeScalar = BASE_ECOTONE_BASE_FEE_SCALAR;
}
if (!userOptions.opStackBlobBaseFeeScalar) {
opStackBlobBaseFeeScalar = BASE_ECOTONE_BLOB_BASE_FEE_SCALAR
}
}

// TODO: enable when arbitrum support added
Expand All @@ -69,6 +94,8 @@ export function getDefaultOptions(userConfig: Readonly<HardhatUserConfig>): GasR
L1: "ethereum",
noColors: false,
offline: false,
opStackBaseFeeScalar,
opStackBlobBaseFeeScalar,
optimismHardfork,
outputJSON: false,
outputJSONFile: DEFAULT_JSON_OUTPUT_FILE,
Expand Down
25 changes: 25 additions & 0 deletions src/lib/render/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ export function generateTerminalTextTable(
// NETWORK CONFIG
// ==============
let networkConfig: HorizontalTableRow = [];
let opStackConfig: HorizontalTableRow = [];

Check failure on line 276 in src/lib/render/terminal.ts

View workflow job for this annotation

GitHub Actions / lint

'opStackConfig' is never reassigned. Use 'const' instead

if (options.tokenPrice && options.gasPrice) {
const {
Expand Down Expand Up @@ -313,6 +314,25 @@ export function generateTerminalTextTable(
colSpan: 2,
content: chalk.magenta(`${rate} ${currency}/${token}`)
});

if (options.L2 === "optimism" || options.L2 === "base") {
opStackConfig.push({ colSpan: 2, content: " " })
opStackConfig.push({
hAlign: "left",
colSpan: 2,
content: chalk.cyan(`L1: ${options.blobBaseFee} gwei (blobBaseFee)`)

Check failure on line 323 in src/lib/render/terminal.ts

View workflow job for this annotation

GitHub Actions / lint

Invalid type "number | undefined" of template literal expression
});
opStackConfig.push({
hAlign: "left",
colSpan: 2,
content: chalk.cyan(`Base Fee Scalar: ${options.opStackBaseFeeScalar}`)

Check failure on line 328 in src/lib/render/terminal.ts

View workflow job for this annotation

GitHub Actions / lint

Invalid type "number | undefined" of template literal expression
});
opStackConfig.push({
hAlign: "left",
colSpan: 2,
content: chalk.cyan(`Blob Fee Scalar: ${options.opStackBlobBaseFeeScalar}`)

Check failure on line 333 in src/lib/render/terminal.ts

View workflow job for this annotation

GitHub Actions / lint

Invalid type "number | undefined" of template literal expression
});
}
} else {
networkConfig = [
{ hAlign: "left", colSpan: numberOfCols, content: chalk.green.bold("Methods") }
Expand Down Expand Up @@ -362,6 +382,11 @@ export function generateTerminalTextTable(
table.push(title);
table.push(solcConfig);
table.push(networkConfig);

if (opStackConfig.length > 0) {
table.push(opStackConfig);
}

table.push(methodsHeader);

methodRows.sort((a, b) => {
Expand Down
8 changes: 7 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export interface GasReporterOptions {

// TODO: Enable arbitrum when support added
/** @property L2 Network to calculate execution costs for */
L2?: "optimism" // | "arbitrum"
L2?: "optimism" | "base" // | "arbitrum"

/** @property Etherscan API key for L1 networks */
L1Etherscan?: string;
Expand All @@ -94,6 +94,12 @@ export interface GasReporterOptions {
/** @property Optimism client version to emulate gas costs for. Only applied when L2 is "optimism" */
optimismHardfork?: OptimismHardfork,

/** @property Scalar applied to L1 base fee (see Optimism gas fee documentation for details) */
opStackBaseFeeScalar?: number;

/** @property Scalar applied to L1 blob base fee (see Optimism gas fee documentation for details) */
opStackBlobBaseFeeScalar?: number;

/** @property Relative path to a file to output terminal table to (instead of stdout) */
outputFile?: string;

Expand Down
5 changes: 5 additions & 0 deletions src/utils/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ export const L2 = {
gasPriceOracle: "0x420000000000000000000000000000000000000F",
token: "ETH"
},
base: {
baseUrl: "https://api.basescan.org/api?module=proxy&",
gasPriceOracle: "0x420000000000000000000000000000000000000F",
token: "ETH"
},
arbitrum: {
baseUrl: "https://api.arbiscan.io/api?module=proxy&",
token: "ETH"
Expand Down
26 changes: 16 additions & 10 deletions src/utils/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { serializeTransaction, Hex } from 'viem';
import {
EVM_BASE_TX_COST,
OPTIMISM_BEDROCK_DYNAMIC_OVERHEAD,
OPTIMISM_BEDROCK_FIXED_OVERHEAD,
OPTIMISM_ECOTONE_BASE_FEE_SCALAR,
OPTIMISM_ECOTONE_BLOB_BASE_FEE_SCALAR
OPTIMISM_BEDROCK_FIXED_OVERHEAD
} from "../constants";

import { GasReporterOptions, JsonRpcTx } from "../types";
Expand Down Expand Up @@ -107,13 +105,15 @@ export function getOptimismEcotoneL1Gas(tx: JsonRpcTx) {
* return fee / (16 * 10 ** DECIMALS);
* }
*/
export function getOptimismEcotoneL1Cost(
export function getOPStackEcotoneL1Cost(
txSerialized: number,
baseFee: number,
blobBaseFee: number
blobBaseFee: number,
opStackBaseFeeScalar: number,
opStackBlobBaseFeeScalar: number
): number {
const weightedBaseFee = 16 * OPTIMISM_ECOTONE_BASE_FEE_SCALAR * baseFee;
const weightedBlobBaseFee = OPTIMISM_ECOTONE_BLOB_BASE_FEE_SCALAR * blobBaseFee;
const weightedBaseFee = 16 * opStackBaseFeeScalar * baseFee;
const weightedBlobBaseFee = opStackBlobBaseFeeScalar * blobBaseFee;
return (txSerialized * (weightedBaseFee + weightedBlobBaseFee)) / 16000000;
}

Expand Down Expand Up @@ -245,7 +245,7 @@ export function getCalldataGasForNetwork(
options: GasReporterOptions,
tx: JsonRpcTx
) : number {
if (options.L2 === "optimism") {
if (options.L2 === "optimism" || options.L2 === "base") {
switch (options.optimismHardfork){
case "bedrock": return getOptimismBedrockL1Gas(tx);
case "ecotone": return getOptimismEcotoneL1Gas(tx);
Expand Down Expand Up @@ -277,10 +277,16 @@ export function getCalldataCostForNetwork(
options: GasReporterOptions,
gas: number,
) : number {
if (options.L2 === "optimism") {
if (options.L2 === "optimism" || options.L2 === "base") {
switch (options.optimismHardfork){
case "bedrock": return getOptimismBedrockL1Cost(gas, options.baseFee!);
case "ecotone": return getOptimismEcotoneL1Cost(gas, options.baseFee!, options.blobBaseFee!);
case "ecotone": return getOPStackEcotoneL1Cost(
gas,
options.baseFee!,
options.blobBaseFee!,
options.opStackBaseFeeScalar!,
options.opStackBlobBaseFeeScalar!
);
default: return 0; /** This shouldn't happen */
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/prices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise<

// blobBaseFee data: etherscan eth_call to OP Stack gas oracle on L2
if (
options.L2 === "optimism" &&
(options.L2 === "optimism" || options.L2 === "base") &&
options.optimismHardfork === "ecotone" &&
!options.blobBaseFee
) {
Expand Down
4 changes: 2 additions & 2 deletions src/utils/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,9 @@ export function getCommonTableVals(options: GasReporterOptions) {
const usingL1 = options.L2 === undefined;

let token = "";
let l1gwei: string | number = (usingL1) ? options.gasPrice!: options.blobBaseFee!;
let l1gwei: string | number = (usingL1) ? options.gasPrice!: options.baseFee!;
let l2gwei: string | number = (usingL1) ? "" : options.gasPrice!;
const l1gweiNote: string = (usingL1) ? "" : "(blobBaseFee)";
const l1gweiNote: string = (usingL1) ? "" : "(baseFee)";
const l2gweiNote: string = (usingL1) ? "" : "(gasPrice)";
const network = (usingL1) ? options.L1!.toUpperCase() : options.L2!.toUpperCase();

Expand Down
8 changes: 5 additions & 3 deletions test/integration/options.e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ import { useEnvironment, findMethod, findDeployment } from "../helpers";
* OPTIONS ARE SET UP TO TEST:
* + Default Terminal Format
* + L2: Optimism
* + Autoconfiguration without etherscan api-key
* + reportPureAndViewMethods
* + excludeAutoGeneratedGetters
*/
describe("Options E", function () {
describe("Options E (OPStack:optimism with live pricing & `reportPureAndViewMethods`)", function () {
let output: GasReporterOutput;
let options: GasReporterOptions;
let methods: MethodData;
Expand Down Expand Up @@ -50,9 +49,12 @@ describe("Options E", function () {
after(() => execSync(`rm ${outputPath}`));

it("auto-configures options correctly", function () {
assert.equal(options.L2, "optimism");
assert.equal(options.optimismHardfork, "ecotone");
assert.isDefined(options.gasPrice)
assert.isDefined(options.gasPrice);
assert.isDefined(options.blobBaseFee);
assert.isBelow(options.gasPrice!, 1);
assert.isAbove(options.blobBaseFee!, 1);

assert.isDefined(options.tokenPrice);
assert.isAbove(parseFloat(options.tokenPrice!), 1000); // Eth-ish
Expand Down
Loading

0 comments on commit e4792e9

Please sign in to comment.