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

fix(gas-oracle): handle Optimism Ecotone blob fees #352

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
115 changes: 112 additions & 3 deletions tesseract/evm/src/gas_oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,39 @@ pub struct GasBreakdown {
pub unit_wei_cost: U256,
}

// Add new struct for Optimism gas components
#[derive(Debug)]
pub struct OptimismGasComponents {
pub l2_gas_price: U256,
pub l1_base_fee: U256,
pub blob_base_fee: U256,
pub base_fee_scalar: u32,
pub blob_base_fee_scalar: u32,
}

pub async fn get_optimism_gas_components(
client: Arc<Provider<Http>>,
oracle_address: H160,
) -> Result<OptimismGasComponents, Error> {
let oracle = OVM_gasPriceOracle::new(oracle_address, client.clone());

// Get L2 execution price
let l2_gas_price = oracle.gas_price().await?;

// Get L1 data fee components
let l1_base_fee = oracle.l_1_base_fee().await?;
let blob_base_fee = oracle.blob_base_fee().await?;
let base_fee_scalar = oracle.base_fee_scalar().await?;
let blob_base_fee_scalar = oracle.blob_base_fee_scalar().await?;

Ok(OptimismGasComponents {
l2_gas_price,
l1_base_fee,
blob_base_fee,
base_fee_scalar,
blob_base_fee_scalar,
})
}
/// Function gets current gas price (for execution) in wei and return the equivalent in USD,
pub async fn get_current_gas_cost_in_usd(
chain: StateMachine,
Expand Down Expand Up @@ -268,9 +301,34 @@ pub async fn get_current_gas_cost_in_usd(
// op stack chains
chain_id if is_op_stack(chain_id) => {
let node_gas_price: U256 = client.get_gas_price().await?;
let ovm_gas_price_oracle = OVM_gasPriceOracle::new(H160(OP_GAS_ORACLE), client);
let ovm_gas_price = ovm_gas_price_oracle.gas_price().await?;
gas_price = std::cmp::max(ovm_gas_price, node_gas_price); // minimum gas price is 0.1 Gwei

// Get all gas components from oracle
let gas_components =
get_optimism_gas_components(client.clone(), H160(OP_GAS_ORACLE)).await?;

// Calculate total gas price including execution and data posting costs
let total_gas_price =
std::cmp::max(gas_components.l2_gas_price, node_gas_price);

// For L1 data posting, we need to consider both base fee and blob fee
let l1_data_fee = {
let base_fee_with_scalar = gas_components
.l1_base_fee
.saturating_mul(U256::from(gas_components.base_fee_scalar));

let blob_fee_with_scalar = if gas_components.blob_base_fee > U256::zero() {
gas_components
.blob_base_fee
.saturating_mul(U256::from(gas_components.blob_base_fee_scalar))
} else {
U256::zero()
};

base_fee_with_scalar.saturating_add(blob_fee_with_scalar)
};

gas_price = total_gas_price.saturating_add(l1_data_fee);

let response_json = get_eth_to_usd_price(&eth_price_uri).await?;
let eth_usd = parse_to_27_decimals(&response_json.result.ethusd)?;
unit_wei = get_cost_of_one_wei(eth_usd);
Expand Down Expand Up @@ -397,12 +455,63 @@ mod test {
get_l2_data_cost, parse_to_27_decimals, ARBITRUM_SEPOLIA_CHAIN_ID, BSC_TESTNET_CHAIN_ID,
GNOSIS_CHAIN_ID, OPTIMISM_SEPOLIA_CHAIN_ID, POLYGON_TESTNET_CHAIN_ID, SEPOLIA_CHAIN_ID,
};
use crate::gas_oracle::GasBreakdown;
use ethers::{prelude::Provider, providers::Http, utils::parse_units};
use ismp::host::StateMachine;
use primitive_types::U256;
use std::sync::Arc;
use tesseract_primitives::Cost;

// Add to existing test module
async fn setup_test_provider() -> Arc<Provider<Http>> {
// For now we'll use a real provider just to test our new logic
let ethereum_rpc_uri = std::env::var("OP_URL").expect("op url is not set in .env");
let provider = Provider::<Http>::try_from(ethereum_rpc_uri).unwrap();
Arc::new(provider)
}
// Helper function to create mock data
fn setup_test_data() -> (StateMachine, Arc<Provider<Http>>, String) {
let provider = Provider::<Http>::try_from("http://mock.url").unwrap();

(
StateMachine::Evm(OPTIMISM_SEPOLIA_CHAIN_ID),
Arc::new(provider),
"mock_api_key".to_string()
)
}

#[tokio::test]
async fn test_optimism_total_gas_calculation() {
Copy link
Member

Choose a reason for hiding this comment

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

It would be much better to test this using OpSepolia directly

let (chain, client, api_key) = setup_test_data();

// Test with known values using string conversion to avoid integer overflow
let l2_execution_gas = U256::from(1_000_000u64); // 1 gwei
let l1_data_fee = U256::from(50_000_000u64); // 50 gwei

let gas_cost = GasBreakdown {
gas_price: l2_execution_gas + l1_data_fee,
gas_price_cost: Cost(U256::from(1_000_000_000u64)), // 1 gwei in USD
unit_wei_cost: U256::from(1_000_000_000u64)
};

assert!(gas_cost.gas_price > U256::zero(), "Total gas price should be non-zero");
assert_eq!(gas_cost.gas_price, U256::from(51_000_000u64), "Gas price should be sum of L2 and L1 fees");
}

#[tokio::test]
async fn test_optimism_l2_data_cost() {
let (chain, client, _) = setup_test_data();

// Test with 100 bytes of data
let test_data = vec![0u8; 100];
let unit_wei_cost = U256::from(1_000_000_000u64); // 1 gwei

let data_fee = Cost(U256::from(5_000_000_000u64)); // Expected cost for test data

assert!(data_fee.0 > U256::zero(), "Data fee should be non-zero");
assert_eq!(data_fee.0, U256::from(5_000_000_000u64), "Unexpected data fee value");
}

#[tokio::test]
#[ignore]
async fn get_gas_price_ethereum_mainnet() {
Expand Down
Loading