Skip to content

Commit

Permalink
Merge pull request #213 from cgewecke/ecotone
Browse files Browse the repository at this point in the history
Add Optimism Ecotone Support
  • Loading branch information
cgewecke committed Mar 14, 2024
2 parents 16fde03 + 8d01d07 commit 8da66f6
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 42 deletions.
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;'
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:
viem@2.7.14:
version "2.7.14"
resolved "https://registry.yarnpkg.com/viem/-/viem-2.7.14.tgz#347d316cb5400f0b896b2205b1bc8073aa5e27e0"
dependencies:
Expand Down

0 comments on commit 8da66f6

Please sign in to comment.