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

Closed
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
129 changes: 108 additions & 21 deletions tesseract/evm/src/gas_oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ use serde::de::DeserializeOwned;
use std::{fmt::Debug, sync::Arc, time::Duration};
use tesseract_primitives::Cost;




#[derive(Debug)]
pub struct OptimismGasComponents {
pub l1_data_fee: U256,
pub l2_execution_fee: U256,
pub blob_fee: Option<U256>,
}

#[derive(Debug, Default, Deserialize, Clone)]
#[serde(rename_all = "PascalCase")]
pub struct GasResult {
Expand Down Expand Up @@ -295,29 +305,48 @@ 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.
/// Implementation follows Optimism's SDK and official contracts:
/// - SDK implementation: https://github.com/ethereum-optimism/ecosystem/blob/main/packages/sdk/src/l2-provider.ts
/// - Contract reference: https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L2/GasPriceOracle.sol
///
/// Uses the contract's getL1Fee method directly to calculate costs, matching the SDK's estimateL1GasCost function.
pub async fn get_l2_data_cost(
rlp_tx: Bytes,
chain: StateMachine,
client: Arc<Provider<Http>>,
// Unit wei cost in 27 decimals
unit_wei_cost: U256,
rlp_tx: Bytes,
chain: StateMachine,
client: Arc<Provider<Http>>,
unit_wei_cost: U256,
) -> Result<Cost, anyhow::Error> {
let mut data_cost = U256::zero();
match chain {
StateMachine::Evm(inner_evm) => match inner_evm {
id if is_op_stack(id) => {
let ovm_gas_price_oracle = OVM_gasPriceOracle::new(H160(OP_GAS_ORACLE), client);
let data_cost_bytes = ovm_gas_price_oracle.get_l1_fee(rlp_tx).await?; // this is in wei
data_cost = data_cost_bytes * unit_wei_cost
},

_ => {},
},
_ => Err(anyhow!("Unknown chain: {chain:?}"))?,
}

Ok(convert_27_decimals_to_18_decimals(data_cost)?.into())
let mut data_cost = U256::zero();
match chain {
StateMachine::Evm(inner_evm) => match inner_evm {
id if is_op_stack(id) => {
let ovm_gas_price_oracle = OVM_gasPriceOracle::new(H160(OP_GAS_ORACLE), client.clone());

// Get and print all relevant values
let l1_gas_used: U256 = rlp_tx.iter().fold(0, |acc, byte| {
acc + if *byte == 0 { 4 } else { 16 }
}).into();

let l1_base_fee = ovm_gas_price_oracle.l_1_base_fee().await?;
let base_fee_scalar = ovm_gas_price_oracle.base_fee_scalar().await?;
let l1_fee = (l1_gas_used * l1_base_fee) / base_fee_scalar;

println!("Debug values:");
println!("L1 gas used: {} gas", l1_gas_used);
println!("L1 base fee: {} wei", l1_base_fee);
println!("Base fee scalar: {}", base_fee_scalar);
println!("Final L1 fee: {} wei", l1_fee);
println!("Unit wei cost: {} wei", unit_wei_cost);

data_cost = l1_fee * unit_wei_cost;
},
_ => {},
},
_ => Err(anyhow!("Unknown chain: {chain:?}"))?,
}

Ok(convert_27_decimals_to_18_decimals(data_cost)?.into())
}

async fn make_request<T: DeserializeOwned>(url: &str, header_map: HeaderMap) -> anyhow::Result<T> {
Expand Down Expand Up @@ -402,6 +431,7 @@ mod test {
use primitive_types::U256;
use std::sync::Arc;
use tesseract_primitives::Cost;


#[tokio::test]
#[ignore]
Expand Down Expand Up @@ -645,4 +675,61 @@ mod test {
dbg!(Cost(cost));
assert!(cost > U256::zero())
}
#[tokio::test]
async fn test_optimism_sepolia_gas_calculation() {
use ethers::types::Bytes;

dotenv::dotenv().ok();
let provider = Arc::new(Provider::<Http>::try_from(std::env::var("OP_URL").expect("OP_URL must be set")).unwrap());
let ethereum_etherscan_api_key = std::env::var("ETHERSCAN_ETHEREUM_KEY")
.expect("ETHERSCAN_ETHEREUM_KEY must be set");

// Get base gas costs first to get unit_wei_cost
let gas_breakdown = get_current_gas_cost_in_usd(
StateMachine::Evm(OPTIMISM_SEPOLIA_CHAIN_ID),
&ethereum_etherscan_api_key,
provider.clone(),
)
.await
.unwrap();

// Create transaction data with different content
let small_tx = Bytes::from(vec![1u8; 128]); // non-zero bytes
let large_tx = Bytes::from(vec![1u8; 1024]); // non-zero bytes

println!("Testing small transaction...");
let small_data_cost = get_l2_data_cost(
small_tx.clone(),
StateMachine::Evm(OPTIMISM_SEPOLIA_CHAIN_ID),
provider.clone(),
gas_breakdown.unit_wei_cost,
)
.await
.unwrap();

println!("Testing large transaction...");
let large_data_cost = get_l2_data_cost(
large_tx.clone(),
StateMachine::Evm(OPTIMISM_SEPOLIA_CHAIN_ID),
provider.clone(),
gas_breakdown.unit_wei_cost,
)
.await
.unwrap();

// Print raw values for debugging
println!("\nRaw Values:");
println!("Small TX Size: {} bytes", small_tx.len());
println!("Large TX Size: {} bytes", large_tx.len());

// Verify costs are reasonable and scale appropriately
assert!(small_data_cost > Cost(U256::zero()), "Small data cost should be non-zero");
assert!(large_data_cost > Cost(U256::zero()), "Large data cost should be non-zero");
assert!(large_data_cost > small_data_cost, "Large data cost should be greater than small data cost");

println!("\nFinal Results:");
println!("Small data cost: {} USD", small_data_cost);
println!("Large data cost: {} USD", large_data_cost);
}

}
Loading