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

Add Arbitrum support #225

Merged
merged 6 commits into from
Apr 3, 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 .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ env:
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
OPTIMISTIC_API_KEY: ${{ secrets.OPTIMISTIC_API_KEY }}
BASE_API_KEY: ${{ secrets.BASE_API_KEY }}
ARBITRUM_API_KEY: ${{ secrets.ARBITRUM_API_KEY }}

jobs:
lint:
Expand Down
100 changes: 54 additions & 46 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ const config: HardhatUserConfig = {
}
```


## 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 @@ -340,3 +339,4 @@ Example of a report produced with `reportFormat: "markdown"`:
[9]: https://docs.optimism.io/stack/transactions/fees#ecotone
[10]: https://basescan.org/address/0x420000000000000000000000000000000000000F#readProxyContract
[11]: https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000015#readProxyContract
[12]: https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas#breaking-down-the-formula
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"@ethersproject/units": "^5.7.0",
"@solidity-parser/parser": "^0.18.0",
"axios": "^1.6.7",
"brotli-wasm": "^2.0.1",
"chalk": "4.1.2",
"cli-table3": "^0.6.3",
"ethereum-cryptography": "^2.1.3",
Expand Down
9 changes: 9 additions & 0 deletions scripts/gen-options-md.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,15 @@ advancedSubtitle,
"Gwei base fee per gas unit used to calculate L1 calldata costs for L2 transactions (Ex: `25`). " +
"By default, this is fetched from live network when `L2` & `coinmarketcap` options are defined"
],
// baseFeePerByte
[
"baseFeePerByte",
"_number_",
"-",
"Gwei fee per byte used to calculate L1 calldata costs for Arbitrum transactions (Ex: `25`). " +
"See [arbitrum gas estimation docs for details][112]. " +
"By default, this is fetched from live network when `L2` is set to 'arbitrum' & `coinmarketcap` options are defined"
],
// blobBaseFee
[
"blobBaseFee",
Expand Down
26 changes: 26 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,32 @@ export const BASE_ECOTONE_BLOB_BASE_FEE_SCALAR = 659851;
export const UNICODE_CIRCLE = "◯";
export const UNICODE_TRIANGLE = "△"

export const RANDOM_R_COMPONENT = "0x12354631f8e7f6d04a0f71b4e2a7b50b165ad2e50a83d531cbd88587b4bd62d5";
export const RANDOM_S_COMPONENT = "0x49cd68893c5952ea1e00288b05699be582081c5fba8c2c6f6e90dd416cdc2e07";

/**
* Generated with:
*
* erc20Calldata = ethersV5.Interface.encodeFunctionData("decimals()", [])
*
* ethersV5.Interface.encodeFunctionData(
* "gasEstimateL1Component(address to, bool contractCreation, bytes calldata data)",
* [
* "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC
* false,
* erc20Calldata
* ]
* );
*
*/
export const ARBITRUM_L1_ESTIMATE_CALLDATA = "0x77d488a2000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004313ce56700000000000000000000000000000000000000000000000000000000";

// Source: @arbitrum/sdk/dist/lib/dataEntities/constants
export const ARBITRUM_NODE_INTERFACE_ADDRESS = "0x00000000000000000000000000000000000000C8";

export const DEFAULT_BASE_FEE_PER_BYTE_API_ARGS =
`action=eth_call&data=${ARBITRUM_L1_ESTIMATE_CALLDATA}&tag=latest&to=${ARBITRUM_NODE_INTERFACE_ADDRESS}`;

export const OPTIMISM_GAS_ORACLE_ABI_PARTIAL = [
{
constant: true,
Expand Down
23 changes: 1 addition & 22 deletions src/lib/options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { HardhatUserConfig } from "hardhat/types";
import {
// TODO: enable when arbitrum support added
// DEFAULT_ARBITRUM_HARDFORK,
DEFAULT_CURRENCY,
DEFAULT_CURRENCY_DISPLAY_PRECISION,
DEFAULT_JSON_OUTPUT_FILE,
Expand All @@ -13,7 +11,7 @@ import {
TABLE_NAME_TERMINAL
} from "../constants";

import { /* ArbitrumHardfork,*/ GasReporterOptions, OptimismHardfork } from "../types";
import { GasReporterOptions, OptimismHardfork } from "../types";

/**
* Validates Optimism hardfork option
Expand All @@ -26,23 +24,10 @@ function isOptimismHardfork(hardfork: string | undefined) {
return ["bedrock, ecotone"].includes(hardfork);
}

// TODO: Enabled when arbitrum support added
/**
* Validates Arbitrum hardfork option
* @param hardfork
* @returns
*/
// function isArbitrumHardfork(hardfork: string | undefined) {
// if (hardfork === undefined) return false;

// return ["arbOS11"].includes(hardfork);
// }

/**
* Sets default reporter options
*/
export function getDefaultOptions(userConfig: Readonly<HardhatUserConfig>): GasReporterOptions {
// let arbitrumHardfork: ArbitrumHardfork;
let optimismHardfork: OptimismHardfork;
let opStackBaseFeeScalar: number = 0;
let opStackBlobBaseFeeScalar: number = 0;
Expand Down Expand Up @@ -73,15 +58,9 @@ export function getDefaultOptions(userConfig: Readonly<HardhatUserConfig>): GasR
opStackBlobBaseFeeScalar = BASE_ECOTONE_BLOB_BASE_FEE_SCALAR
}
}

// TODO: enable when arbitrum support added
// if (userOptions.L2 === "arbitrum" && !isArbitrumHardfork(userOptions.arbitrumHardfork)) {
// arbitrumHardfork = DEFAULT_ARBITRUM_HARDFORK;
// }
}

return {
// arbitrumHardfork,
currency: DEFAULT_CURRENCY,
currencyDisplayPrecision: DEFAULT_CURRENCY_DISPLAY_PRECISION,
darkMode: false,
Expand Down
22 changes: 16 additions & 6 deletions src/lib/render/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ export function generateMarkdownTable(

const addedContracts: string[] = [];

if (options.L2 !== undefined) {
if (options.L2 === "optimism" || options.L2 === "base") {
gasAverageTitle = ["L2 Avg (Exec)", "L1 Avg (Data)"];
}

if (options.L2 === "arbitrum") {
gasAverageTitle = ["L2 Avg (Exec)", "L1 Avg (Bytes)"];
}

// ---------------------------------------------------------------------------------------------
// Assemble section: Build options
// ---------------------------------------------------------------------------------------------
Expand All @@ -61,11 +65,17 @@ export function generateMarkdownTable(
} = getCommonTableVals(options));

gasPrices = (options.L2)
? [
[`L1 Base Fee`, `${options.baseFee!} gwei`],
[`L1 Blob Base Fee`, `${options.blobBaseFee!} gwei`],
[`L2 Gas Price`, `${l2gwei} gwei` ]
]
? (options.L2 === "arbitrum")
? [
[`L1 Base Fee Per Byte`, `${options.baseFeePerByte!} gwei`],
[`L2 Gas Price`, `${l2gwei} gwei` ]
]
: [
[`L1 Base Fee`, `${options.baseFee!} gwei`],
[`L1 Blob Base Fee`, `${options.blobBaseFee!} gwei`],
[`L2 Gas Price`, `${l2gwei} gwei` ]
]

: [[`L1 Gas Price`, `${l1gwei} gwei`]];

tokenPrice = `${rate} ${currency}/${token}`
Expand Down
9 changes: 8 additions & 1 deletion src/lib/render/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,14 @@ export function generateTerminalTextTable(
deploymentsTitleSpacerWidth = 3;
contractTitleSpacerWidth = 6;
executionGasAverageTitle = "L2 Avg (Exec)";
calldataGasAverageTitle = "L1 Avg (Data)"

if (options.L2 === "optimism" || options.L2 === "base") {
calldataGasAverageTitle = "L1 Avg (Data)";
}

if (options.L2 === "arbitrum") {
calldataGasAverageTitle = "L1 Avg (Bytes)";
}
}

// eslint-disable-next-line
Expand Down
17 changes: 11 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ declare module "hardhat/types/config" {
}
}

export type ArbitrumHardfork = "arbOS11" | "arbOS20" | undefined;
export type OptimismHardfork = "bedrock" | "ecotone" | undefined;

export interface GasReporterOptions {
Expand All @@ -20,6 +19,13 @@ export interface GasReporterOptions {
*/
baseFee?: number;

/** @property Arbitrum-specific gwei price per byte of L1 calldata. */
/**
* This is the `l1BaseFeeEstimate` value returned by NodeInterface.gasEstimateL1Component(...) multiplied by 16.
* See: https://docs.arbitrum.io/build-decentralized-apps/how-to-estimate-gas#an-example-of-how-to-apply-this-formula-in-your-code
*/
baseFeePerByte?: number;

/** @property Gwei blob base fee per gas unit */
/*
* Used to calculate L1 calldata costs post EIP-7516 and typically obtained via api call
Expand Down Expand Up @@ -75,9 +81,8 @@ export interface GasReporterOptions {
/** @property L1 Network to calculate execution or data costs for */
L1?: "ethereum" | "polygon" | "binance" | "fantom" | "moonbeam" | "moonriver" | "gnosis" | "avalanche";

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

/** @property Etherscan API key for L1 networks */
L1Etherscan?: string;
Expand Down Expand Up @@ -150,9 +155,6 @@ export interface GasReporterOptions {

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

/** @ignore Arbitrum client version to emulate gas costs for. Only applied when L2 is "arbitrum" */
arbitrumHardfork?: ArbitrumHardfork,
}

export interface GasReporterExecutionContext {
Expand Down Expand Up @@ -267,6 +269,9 @@ export interface JsonRpcTx {
hash: string
nonce: string
value: string
v? : string,
r? : string,
s? : string
// maxFeePerBlobGas?: string // QUANTITY - max data fee for blob transactions
// blobVersionedHashes?: string[] // DATA - array of 32 byte versioned hashes for blob transactions
}
Expand Down
23 changes: 21 additions & 2 deletions src/utils/chains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import {
DEFAULT_API_KEY_ARGS,
DEFAULT_GAS_PRICE_API_ARGS,
DEFAULT_GET_BLOCK_API_ARGS,
DEFAULT_BLOB_BASE_FEE_API_ARGS
DEFAULT_BLOB_BASE_FEE_API_ARGS,
DEFAULT_BASE_FEE_PER_BYTE_API_ARGS,
} from "../constants"
import { GasReporterOptions } from "../types"

Expand Down Expand Up @@ -66,7 +67,7 @@ export function getBlockUrlForChain(options: GasReporterOptions): string {
}

/**
* Gets Etherscan eth_call api url to read OP Stack GasPriceOracle for blobBaseFee.
* Gets Etherscan eth_call api url to read OP Stack GasPriceOracle for blobBaseFee.
* Attaches L2 apikey if configured. (This fee fetched from L2 contract b/c its the only available place at
* time of PR - eth_blobBaseFee hasn't been implemented in geth yet)
* @param {GasReporterOptions} options
Expand All @@ -83,6 +84,23 @@ export function getBlobBaseFeeUrlForChain(options: GasReporterOptions): string {
return `${L2[options.L2!].baseUrl}${DEFAULT_BLOB_BASE_FEE_API_ARGS}${L2[options.L2!].gasPriceOracle}${apiKey}`;
}

/**
* Gets Etherscan eth_call api url to read OP Stack GasPriceOracle for blobBaseFee.
* Attaches L2 apikey if configured. (This fee fetched from L2 contract b/c its the only available place at
* time of PR - eth_blobBaseFee hasn't been implemented in geth yet)
* @param {GasReporterOptions} options
* @returns
*/
export function getBaseFeePerByteUrlForChain(options: GasReporterOptions): string {
if (options.L2 !== "arbitrum") return "";

const apiKey = (options.L2Etherscan)
? `${DEFAULT_API_KEY_ARGS}${options.L2Etherscan}`
: "";

return `${L2[options.L2!].baseUrl}${DEFAULT_BASE_FEE_PER_BYTE_API_ARGS}${apiKey}`;
}

/**
* L1 & L2 chain configurations for fetching gas price and block fee data from Etherscan as well
* as currency prices from Coinmarketcap
Expand Down Expand Up @@ -135,6 +153,7 @@ export const L2 = {
},
arbitrum: {
baseUrl: "https://api.arbiscan.io/api?module=proxy&",
gasPriceOracle: "",
token: "ETH"
}
}
Loading
Loading