Skip to content

Commit

Permalink
fix(gas-oracle): handle Optimism Ecotone blob fees
Browse files Browse the repository at this point in the history
- Add OptimismGasComponents struct to track L1/L2 fees
- Implement proper L1 data posting cost calculation
- Consider both base fee and blob fee with scalars
- Fix gas price overestimation issue

Also:
- Added inline code comments and OpStack references as requested by maintainers.
- Will consider direct OpSepolia testing in the future.
  • Loading branch information
sylvaincormier committed Dec 16, 2024
1 parent 46da0fb commit f6827d0
Showing 1 changed file with 122 additions and 5 deletions.
127 changes: 122 additions & 5 deletions tesseract/evm/src/gas_oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,45 @@ pub struct GasBreakdown {
pub unit_wei_cost: U256,
}

/// Function gets current gas price (for execution) in wei and return the equivalent in USD,
// 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.
/// For OpStack chains (Optimism, Base), we incorporate blob fees after the Ecotone upgrade.
/// Reference for OpStack fees:
/// https://github.com/ethereum-optimism/optimism/tree/develop/op-stack
///
/// TODO: Ideally, we should test this against OpSepolia directly for more accurate scenario validation.
pub async fn get_current_gas_cost_in_usd(
chain: StateMachine,
api_keys: &str,
Expand Down Expand Up @@ -268,9 +306,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 All @@ -295,7 +358,10 @@ fn get_cost_of_one_wei(eth_usd: U256) -> U256 {
eth_usd / eth_to_wei
}

/// Returns the L2 data cost for a given transaction data in usd
// Returns the L2 data cost for a given transaction data in usd.
/// For OpStack-based chains (Optimism, Base), we fetch the L1 fee for data posting.
/// This was introduced in Ecotone to handle blob fees.
/// Reference: https://github.com/ethereum-optimism/optimism/tree/develop/op-stack
pub async fn get_l2_data_cost(
rlp_tx: Bytes,
chain: StateMachine,
Expand Down Expand Up @@ -397,12 +463,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() {
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

0 comments on commit f6827d0

Please sign in to comment.