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 Optimism Ecotone Support #213

Merged
merged 5 commits into from
Mar 14, 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,6 @@
"lodash": "^4.17.21",
"markdown-table": "2.0.0",
"sha1": "^1.1.1",
"viem": "^2.7.14"
"viem": "2.7.14"
}
}
54 changes: 50 additions & 4 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ export const TABLE_NAME_MARKDOWN = "markdown";
export const DEFAULT_CURRENCY = "USD";
export const DEFAULT_CURRENCY_DISPLAY_PRECISION = 2;
export const DEFAULT_JSON_OUTPUT_FILE = "./gasReporterOutput.json";
export const DEFAULT_GAS_PRICE_PRECISION = 7;
export const DEFAULT_GAS_PRICE_PRECISION = 5;

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="
export const DEFAULT_COINMARKET_BASE_URL = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/"

export const DEFAULT_OPTIMISM_HARDFORK = "bedrock";
export const DEFAULT_OPTIMISM_HARDFORK = "ecotone";
export const DEFAULT_ARBITRUM_HARDFORK = "arbOS11";

export const TOOLCHAIN_HARDHAT = "hardhat";
Expand All @@ -29,8 +29,54 @@ 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
export const OPTIMISM_ECOTONE_BASE_FEE_SCALAR = 11000
export const OPTIMISM_ECOTONE_BLOB_BASE_FEE_SCALAR = 1087000
export const OPTIMISM_ECOTONE_BASE_FEE_SCALAR = 1368
export const OPTIMISM_ECOTONE_BLOB_BASE_FEE_SCALAR = 810949

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

export const OPTIMISM_GAS_ORACLE_ADDRESS = "0xb528d11cc114e026f138fe568744c6d45ce6da7a";
export const OPTIMISM_GAS_ORACLE_ABI_PARTIAL = [
{
constant: true,
inputs: [],
name: "blobBaseFee",
outputs: [
{
name: "",
type: "uint256",
},
],
payable: false,
stateMutability: "view",
type: "function",
},
{
constant: true,
inputs: [],
name: "baseFeeScalar",
outputs: [
{
name: "",
type: "uint32",
},
],
payable: false,
stateMutability: "view",
type: "function",
},
{
constant: true,
inputs: [],
name: "blobBaseFeeScalar",
outputs: [
{
name: "",
type: "uint32",
},
],
payable: false,
stateMutability: "view",
type: "function",
}];

30 changes: 21 additions & 9 deletions src/utils/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ export function getOptimismBedrockL1Cost(txDataGas: number, baseFee: number): nu
*/

/**
* Gets compressed transaction calldata gas usage (an input into the cost function below)
* Gets transaction calldata gas usage (an input into the cost function below)
* @param tx JSONRPC formatted getTransaction response
* @returns
*/
export function getOptimismEcotoneL1Gas(tx: JsonRpcTx) {
return Math.floor(getSerializedTxDataGas(tx) / 16);
return getSerializedTxDataGas(tx);
}

/**
Expand All @@ -93,15 +93,28 @@ export function getOptimismEcotoneL1Gas(tx: JsonRpcTx) {
* @param baseFee
* @param blobBaseFee
* @returns
*
* Source: https://github.com/ethereum-optimism/optimism/blob/e57787ea7d0b9782cea5f32bcb92d0fdeb7bd870/ +
* packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L88-L92
*
* DECIMALS = 6
*
* function _getL1FeeEcotone(bytes memory _data) internal view returns (uint256) {
* uint256 l1GasUsed = _getCalldataGas(_data);
* uint256 scaledBaseFee = baseFeeScalar() * 16 * l1BaseFee();
* uint256 scaledBlobBaseFee = blobBaseFeeScalar() * blobBaseFee();
* uint256 fee = l1GasUsed * (scaledBaseFee + scaledBlobBaseFee);
* return fee / (16 * 10 ** DECIMALS);
* }
*/
export function getOptimismEcotoneL1Cost(
txCompressed: number,
txSerialized: number,
baseFee: number,
blobBaseFee: number
): number {
const weightedBaseFee = 16 * OPTIMISM_ECOTONE_BASE_FEE_SCALAR * baseFee;
const weightedBlobBaseFee = OPTIMISM_ECOTONE_BLOB_BASE_FEE_SCALAR * blobBaseFee;
return txCompressed * (weightedBaseFee + weightedBlobBaseFee);
return (txSerialized * (weightedBaseFee + weightedBlobBaseFee)) / 16000000;
}

// ==========================
Expand Down Expand Up @@ -339,14 +352,13 @@ export function hexWeiToIntGwei(val: string): number {
return hexToDecimal(val) / Math.pow(10, 9);
}

export function normalizeTxType(_type: string) {
export function normalizeTxType(_type: string): ("legacy" | "eip1559" | "eip2930" | "eip4844") {
switch(hexToDecimal(_type)) {
case 0: return 'legacy';
case 1: return 'eip2930;'
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

smh

case 1: return 'eip2930';
case 2: return 'eip1559';

// This will error within viem.serializeTransaction
default: return _type;
case 3: return 'eip4844';
default: return 'legacy';
}
}

77 changes: 58 additions & 19 deletions src/utils/prices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import { hexWeiToIntGwei } from "./gas";
import { getTokenForChain, getGasPriceUrlForChain, getBlockUrlForChain } from "./chains";


/**
* Fetches gas, base, & blob fee rates from etherscan as well as current market value of
* network token in nation state currency specified by the options from coinmarketcap
Expand Down Expand Up @@ -101,24 +100,64 @@ export async function setGasAndPriceRates(options: GasReporterOptions): Promise<
}
}

// blobBaseFee data: etherscan (or `getBlockAPI`)
if (options.L2 && !options.blobBaseFee) {
options.blobBaseFee = 0;

// TODO: DENCUN
/* if (block === undefined) {
try {
block = await axiosInstance.get(blockUrl);
checkForEtherscanError(block.data.result);
} catch (error) {
options.blobBaseFee = 0;
warnings.push(warnBlobBaseFeeRemoteCallFailed(error, blockUrl));
return;
}
}
options.baseFee = Math.round(
parseInt(block.data.result.blobBaseFeePerGas, 16) / Math.pow(10, 9)
);*/
// blobBaseFee data: alchemy or infura call to Optimism's 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);
*/
}

return warnings;
Expand Down
1 change: 1 addition & 0 deletions test/integration/options.e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe("Options E", function () {
after(() => execSync(`rm ${outputPath}`));

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

Expand Down
97 changes: 92 additions & 5 deletions test/unit/cases/optimism.ts

Large diffs are not rendered by default.

48 changes: 47 additions & 1 deletion test/unit/gas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ describe("EVM L1: gasToCost", function() {
describe("Optimism: getCalldataCostForNetwork", function () {
const options: GasReporterOptions = {
L2: "optimism",
optimismHardfork: "bedrock",
tokenPrice: "1",
currencyDisplayPrecision: 8,
}
Expand All @@ -62,6 +61,7 @@ describe("Optimism: getCalldataCostForNetwork", function () {
const fn = optimismCases.bedrockFunction_1;
options.gasPrice = fn.l2GasPrice;
options.baseFee = fn.l1BaseFee;
options.optimismHardfork = "bedrock";

const gas = getCalldataGasForNetwork(options, fn.tx);
const cost = gasToCost(fn.l2GasUsed, gas, options);
Expand All @@ -75,6 +75,7 @@ describe("Optimism: getCalldataCostForNetwork", function () {
const fn = optimismCases.bedrockFunction_2;
options.gasPrice = fn.l2GasPrice;
options.baseFee = fn.l1BaseFee;
options.optimismHardfork = "bedrock";

const gas = getCalldataGasForNetwork(options, fn.tx);
const cost = gasToCost(fn.l2GasUsed, gas, options);
Expand All @@ -88,6 +89,7 @@ describe("Optimism: getCalldataCostForNetwork", function () {
const fn = optimismCases.bedrockDeployment;
options.gasPrice = fn.l2GasPrice;
options.baseFee = fn.l1BaseFee;
options.optimismHardfork = "bedrock";

const gas = getCalldataGasForNetwork(options, fn.tx);
const cost = gasToCost(fn.l2GasUsed, gas, options);
Expand All @@ -96,4 +98,48 @@ describe("Optimism: getCalldataCostForNetwork", function () {
// Actual < 0.06%
assert(diff < .01);
});
it("calculates gas cost for small function call tx (ecotone)", function () {
const fn = optimismCases.ecotoneFunction_1;
options.gasPrice = fn.l2GasPrice;
options.baseFee = fn.l1BaseFee;
options.blobBaseFee = fn.l1BlobBaseFee;
options.optimismHardfork = "ecotone";

const gas = getCalldataGasForNetwork(options, fn.tx);
const cost = gasToCost(fn.l2GasUsed, gas, options);
const diff = getPercentDiff(parseFloat(cost), fn.txFeeETH);

// actual 0.013
assert(diff < .015);
});

it("calculates gas cost for large function call tx (ecotone) (I)", function () {
const fn = optimismCases.ecotoneFunction_2;
options.gasPrice = fn.l2GasPrice;
options.baseFee = fn.l1BaseFee;
options.blobBaseFee = fn.l1BlobBaseFee;
options.optimismHardfork = "ecotone";

const gas = getCalldataGasForNetwork(options, fn.tx);;
const cost = gasToCost(fn.l2GasUsed, gas, options);
const diff = getPercentDiff(parseFloat(cost), fn.txFeeETH);

// actual 0.0105
assert(diff < .015);
});

it("calculates gas cost for large function call tx (ecotone) (II)", function () {
const fn = optimismCases.ecotoneFunction_3;
options.gasPrice = fn.l2GasPrice;
options.baseFee = fn.l1BaseFee;
options.blobBaseFee = fn.l1BlobBaseFee;
options.optimismHardfork = "ecotone";

const gas = getCalldataGasForNetwork(options, fn.tx);
const cost = gasToCost(fn.l2GasUsed, gas, options);
const diff = getPercentDiff(parseFloat(cost), fn.txFeeETH);

// actual 0.0008
assert(diff < .015);
});
});
4 changes: 2 additions & 2 deletions test/unit/prices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
import { getDefaultOptions } from "../../src/lib/options";
import { GasReporterOptions } from "../types";


describe("setGasAndPriceRates", function(){
let options: GasReporterOptions;

Expand Down Expand Up @@ -94,11 +93,12 @@ describe("setGasAndPriceRates", function(){
assert.typeOf(options.baseFee, "number");
});

it ("when tokenPrice, gasPrice and baseFee are set but blobBaseFee is not set", async function(){
it("when tokenPrice, gasPrice and baseFee are set but blobBaseFee is not set", async function(){
options.tokenPrice = "1";
options.gasPrice = 1;
options.baseFee = 1;
options.L2 = 'optimism';
options.optimismHardfork = "ecotone";
options.coinmarketcap = process.env.CMC_API_KEY;
options.L2Etherscan = process.env.OPTIMISTIC_API_KEY;

Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4128,7 +4128,7 @@ uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"

viem@^2.7.14:
[email protected]:
version "2.7.14"
resolved "https://registry.yarnpkg.com/viem/-/viem-2.7.14.tgz#347d316cb5400f0b896b2205b1bc8073aa5e27e0"
dependencies:
Expand Down
Loading