Skip to content

Commit

Permalink
Fetch live blobBaseFee from OP Stack GasPriceOracle (#222)
Browse files Browse the repository at this point in the history
  • Loading branch information
cgewecke authored Mar 30, 2024
1 parent 5bfa708 commit 5c17386
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 70 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ const config: HardhatUserConfig = {
| 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` |
| excludeAutoGeneratedGetters | _bool_ | `false` | Exclude solc generated public state vars when reporting gas for pure and view methods. Incurs a performance penalty on test startup when `true`. ⚠️ SLOW ⚠️ |
| excludeAutoGeneratedGetters | _bool_ | `false` | Exclude solc generated public state vars when reporting gas for pure and view methods. (Incurs a performance penalty on test startup when `true`) ⚠️ SLOW ⚠️ |
| excludeContracts | _string[]_ | `[]` | Names of contracts to exclude from report. Ex: `["MyContract"]` |
| includeIntrinsicGas | _bool_ | `true` | Include standard 21_000 + calldata bytes overhead in method gas usage data. (Setting to `false` can be useful for modelling contract infra that will never be called by an EOA) |
| L1 | _string_ | `ethereum` | Auto-configure reporter to emulate an L1 network. (See [supported networks][6]) |
| L2 | _string_ | - | Auto-configure reporter to emulate an L2 network (See [supported networks][6]) |
| L1 | _string_ | `ethereum` | Auto-configure reporter to emulate an L1 network. (See [supported networks][6]) |
| L2 | _string_ | - | Auto-configure reporter to emulate an L2 network (See [supported networks][6]) |
| L1Etherscan | _string_ | - | [API key][4] to use when fetching live gasPrice, baseFee, and blobBaseFee data from an L1 network. (Optional, see [Supported Networks][6]) |
| L2Etherscan | _string_ | - | [API key][4] to use when fetching live gasPrice data from an L2 network (Optional, see [Supported Networks][6]) |
| L2Etherscan | _string_ | - | [API key][4] to use when fetching live gasPrice data from an L2 network (Optional, see [Supported Networks][6]) |
| offline | _bool_ | `false` | Turn off remote calls to fetch data |
| optimismHardfork | _string_ | `ecotone` | Optimism hardfork to emulate L1 & L2 gas costs for. |
| proxyResolver | _Class_ | - | User-defined class which helps reporter identify contract targets of proxied calls. (See [Advanced Usage][7]) |
Expand All @@ -110,13 +110,13 @@ const config: HardhatUserConfig = {
| gasPrice | _number_ | - | Gwei price per gas unit (Ex: `25`). By default, this is fetched from live network when `coinmarketcap` option is defined |
| baseFee | _number_ | - | 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 |
| blobBaseFee | _number_ | - | Gwei blob base fee per gas unit used to calculate post-EIP-7516 L1 calldata costs for L2 transactions (Ex: `25`). By default, this is fetched from live network when `L2` & `coinmarketcap` options are defined |
| 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) |
| 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"`) |



## Utility Tasks

The plugin also provides additional utility commands for managing gas reporter output
Expand Down
16 changes: 12 additions & 4 deletions scripts/gen-options-md.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ title,
"_bool_",
"`false`",
"Exclude solc generated public state vars when reporting gas for pure and view methods. " +
"(Incurs a performance penalty on test startup when `true`)"
"(Incurs a performance penalty on test startup when `true`) ⚠️ SLOW ⚠️"
],
// excludeContracts
[
Expand All @@ -77,14 +77,14 @@ title,
"L1",
"_string_",
"`ethereum`",
"Auto-configure reporter to emulate an L1 network. See [supported networks][6] "
"Auto-configure reporter to emulate an L1 network. (See [supported networks][6])"
],
// L2
[
"L2",
"_string_",
"-",
"Auto-configure reporter to emulate an L2 network (See [supported networks][6]"
"Auto-configure reporter to emulate an L2 network (See [supported networks][6])"
],
// L1Etherscan
[
Expand Down Expand Up @@ -113,7 +113,7 @@ title,
[
"optimismHardfork",
"_string_",
"`bedrock`",
"`ecotone`",
"Optimism hardfork to emulate L1 & L2 gas costs for."
],
// proxyResolver
Expand Down Expand Up @@ -273,6 +273,14 @@ advancedSubtitle,
"Gwei blob base fee per gas unit used to calculate post-EIP-7516 L1 calldata costs for L2 transactions " +
"(Ex: `25`). By default, this is fetched from live network when `L2` & `coinmarketcap` options are defined"
],
// blobBaseFeeApi
[
"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
[
"gasPriceApi",
Expand Down
4 changes: 4 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const DEFAULT_CURRENCY_DISPLAY_PRECISION = 2;
export const DEFAULT_JSON_OUTPUT_FILE = "./gasReporterOutput.json";
export const DEFAULT_GAS_PRICE_PRECISION = 5;

// Selector generated with: ethersV5.Interface.encodeFunctionData("blobBaseFee()", []);
export const DEFAULT_BLOB_BASE_FEE_API_ARGS = "action=eth_call&data=0xf8206140&tag=latest&to="

export const DEFAULT_GET_BLOCK_API_ARGS = "action=eth_getBlockByNumber&tag=latest&boolean=false"
export const DEFAULT_GAS_PRICE_API_ARGS = "action=eth_gasPrice"
export const DEFAULT_API_KEY_ARGS = "&apikey="
Expand All @@ -20,6 +23,7 @@ export const TOOLCHAIN_FOUNDRY = "foundry";

// EVM
export const EVM_BASE_TX_COST = 21000;
export const DEFAULT_BLOB_BASE_FEE = 10; // gwei

// Source:
// https://docs.optimism.io/stack/transactions/fees#bedrock
Expand Down
6 changes: 5 additions & 1 deletion src/lib/render/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ export function generateMarkdownTable(
} = getCommonTableVals(options));

gasPrices = (options.L2)
? [[`L1 Base Fee`, `${l1gwei} 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
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export interface GasReporterOptions {
*/
blobBaseFee?: number;

/** @property Etherscan-like url to fetch blobBasefee from */
blobBaseFeeApi?: string;

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

Expand Down
22 changes: 21 additions & 1 deletion src/utils/chains.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
DEFAULT_API_KEY_ARGS,
DEFAULT_GAS_PRICE_API_ARGS,
DEFAULT_GET_BLOCK_API_ARGS
DEFAULT_GET_BLOCK_API_ARGS,
DEFAULT_BLOB_BASE_FEE_API_ARGS
} from "../constants"
import { GasReporterOptions } from "../types"

Expand Down Expand Up @@ -64,6 +65,24 @@ export function getBlockUrlForChain(options: GasReporterOptions): string {
return `${L1[options.L1!].baseUrl}${DEFAULT_GET_BLOCK_API_ARGS}${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 getBlobBaseFeeUrlForChain(options: GasReporterOptions): string {
if (!options.L2) return "";
if (options.blobBaseFeeApi) return options.blobBaseFeeApi;

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

return `${L2[options.L2!].baseUrl}${DEFAULT_BLOB_BASE_FEE_API_ARGS}${L2[options.L2!].gasPriceOracle}${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 @@ -106,6 +125,7 @@ export const L1 = {
export const L2 = {
optimism: {
baseUrl: "https://api-optimistic.etherscan.io/api?module=proxy&",
gasPriceOracle: "0x420000000000000000000000000000000000000F",
token: "ETH"
},
arbitrum: {
Expand Down
69 changes: 14 additions & 55 deletions src/utils/prices.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import axios from "axios";

import { DEFAULT_COINMARKET_BASE_URL } from "../constants";
import { DEFAULT_COINMARKET_BASE_URL, DEFAULT_BLOB_BASE_FEE } from "../constants";
import { GasReporterOptions } from "../types";
import {
warnCMCRemoteCallFailed,
warnGasPriceRemoteCallFailed,
warnBaseFeeRemoteCallFailed,
warnBlobBaseFeeRemoteCallFailed,
warnUnsupportedChainConfig,
} from "./ui";
import { hexWeiToIntGwei } from "./gas";
import { getTokenForChain, getGasPriceUrlForChain, getBlockUrlForChain } from "./chains";
import { getTokenForChain, getGasPriceUrlForChain, getBlockUrlForChain, getBlobBaseFeeUrlForChain } from "./chains";

/**
* Fetches gas, base, & blob fee rates from etherscan as well as current market value of
Expand Down Expand Up @@ -37,12 +38,14 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise<
let block;
let blockUrl;
let gasPriceUrl;
let blobBaseFeeUrl;
const warnings: string[] = [];

try {
options.token = getTokenForChain(options);
gasPriceUrl = getGasPriceUrlForChain(options);
blockUrl = getBlockUrlForChain(options);
blobBaseFeeUrl = getBlobBaseFeeUrlForChain(options);
} catch (err: any){
if (options.L2)
warnings.push(warnUnsupportedChainConfig(options.L2!));
Expand Down Expand Up @@ -100,64 +103,20 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise<
}
}

// blobBaseFee data: alchemy or infura call to Optimism's gas oracle on L2
// blobBaseFee data: etherscan eth_call to OP Stack gas oracle on L2
if (
options.L2 === "optimism" &&
options.optimismHardfork === "ecotone" &&
!options.blobBaseFee
) {
options.blobBaseFee = .1;

// TODO: Check the GasOracle value against the eth_blobBaseFee value once
// it becomes available and then make a decision about how to
// fetch the data....
//
// At the moment oracle fee comes back as `1`, which seems fake/wrong and
// produces numbers that are 10% too high. `.1` gets the
// calculations in the right ballpark.

/*
import { OPTIMISM_GAS_ORACLE_ABI_PARTIAL, OPTIMISM_GAS_ORACLE_ADDRESS } from "../constants";
import { createPublicClient, http } from "viem";
import { optimism } from 'viem/chains'
import { AbiCoder, Interface } from "@ethersproject/abi";
import { BytesLike } from "@ethersproject/bytes";
const iface = new Interface(OPTIMISM_GAS_ORACLE_ABI_PARTIAL);
const blobBaseFeeData = iface.encodeFunctionData("blobBaseFee()", []);
const baseFeeScalarData = iface.encodeFunctionData("baseFeeScalar()", []);
const blobBaseFeeScalarData = iface.encodeFunctionData("blobBaseFeeScalar()", []);
// check that transport url exists....
const client = createPublicClient({
chain: optimism,
transport: http(process.env.ALCHEMY_OPTIMISM_URL)
});
const blobBaseFeeResponse = await client.call({
data: blobBaseFeeData as hexString,
to: OPTIMISM_GAS_ORACLE_ADDRESS as hexString,
})
const baseFeeScalarResponse = await client.call({
data: baseFeeScalarData as hexString,
to: OPTIMISM_GAS_ORACLE_ADDRESS as hexString,
});
const blobBaseFeeScalarResponse = await client.call({
data: blobBaseFeeScalarData as hexString,
to: OPTIMISM_GAS_ORACLE_ADDRESS as hexString,
});
const abiCoder = new AbiCoder();
const blobBaseFee = abiCoder.decode(["uint256"], blobBaseFeeResponse.data as BytesLike );
const baseFeeScalar = abiCoder.decode(["uint32"], baseFeeScalarResponse.data as BytesLike );
const blobBaseFeeScalar = abiCoder.decode(["uint32"], blobBaseFeeScalarResponse.data as BytesLike);
console.log("blobBaseFee: " + blobBaseFee);
console.log("baseFeeScalar: " + baseFeeScalar);
console.log("blobBaseFeeScalar: " + blobBaseFeeScalar);
*/
try {
const blobBaseFee = await axiosInstance.get(blobBaseFeeUrl);
checkForEtherscanError(blobBaseFee.data.result);
options.blobBaseFee = Math.round(hexWeiToIntGwei(blobBaseFee.data.result))
} catch (error) {
options.blobBaseFee = DEFAULT_BLOB_BASE_FEE;
warnings.push(warnBlobBaseFeeRemoteCallFailed(error, blobBaseFeeUrl));
}
}

return warnings;
Expand Down
8 changes: 4 additions & 4 deletions src/utils/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function remoteCallEndMessage(err: any) : string {
chalk.bold(`Error was: `)
}${chalk.red (err.message) }${EOL
}${chalk.bold(`Reported price data is missing or incorrect`) }${EOL
}${chalk.blue(`* Being rate limited? See the "Etherscan" section in the docs.`) }${EOL
}${chalk.blue(`* Being rate limited? See the Etherscan API key options in the docs.`) }${EOL
}${chalk.blue(`* Set the "offline" option to "true" to suppress these warnings`) }${EOL
}${chalk.yellow.bold(`>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) }${EOL}`;
};
Expand Down Expand Up @@ -92,7 +92,7 @@ export function warnBaseFeeRemoteCallFailed(err: any, url: string): string {
export function warnBlobBaseFeeRemoteCallFailed(err: any, url: string): string {
return `${
startWarning }${EOL
}${chalk.bold(`Failed to get L1 blob base fee from ${url}`) }${EOL
}${chalk.bold(`Failed to get blob base fee from ${url}`) }${EOL
}${remoteCallEndMessage(err)}`;
}

Expand Down 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.baseFee!;
let l1gwei: string | number = (usingL1) ? options.gasPrice!: options.blobBaseFee!;
let l2gwei: string | number = (usingL1) ? "" : options.gasPrice!;
const l1gweiNote: string = (usingL1) ? "" : "(baseFee)";
const l1gweiNote: string = (usingL1) ? "" : "(blobBaseFee)";
const l2gweiNote: string = (usingL1) ? "" : "(gasPrice)";
const network = (usingL1) ? options.L1!.toUpperCase() : options.L2!.toUpperCase();

Expand Down
1 change: 1 addition & 0 deletions test/projects/options/hardhat.options.c.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const config: HardhatUserConfig = {
L2: "optimism",
gasPrice: 0.098775564,
baseFee: 79,
blobBaseFee: 15,
reportFormat: "markdown",
enabled: true
}
Expand Down
1 change: 1 addition & 0 deletions test/projects/options/hardhat.options.e.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const config: HardhatUserConfig = {
gasReporter: {
coinmarketcap: process.env.CMC_API_KEY,
L2: "optimism",
L2Etherscan: process.env.OPTIMISTIC_API_KEY,
enabled: true,
reportPureAndViewMethods: true,
excludeAutoGeneratedGetters: true
Expand Down

0 comments on commit 5c17386

Please sign in to comment.