Skip to content
Open
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
3 changes: 2 additions & 1 deletion apps/fortuna/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod debug_gas;
mod generate;
mod get_request;
mod inspect;
Expand All @@ -8,7 +9,7 @@ mod setup_provider;
mod withdraw_fees;

pub use {
generate::generate, get_request::get_request, inspect::inspect,
debug_gas::debug_gas, generate::generate, get_request::get_request, inspect::inspect,
register_provider::register_provider, request_randomness::request_randomness, run::run,
setup_provider::setup_provider, withdraw_fees::withdraw_fees,
};
102 changes: 102 additions & 0 deletions apps/fortuna/src/command/debug_gas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use {
crate::{
chain::ethereum::{InstrumentedPythContract, InstrumentedSignablePythContract},
config::{Config, DebugGasOptions},
eth_utils::{traced_client::RpcMetrics, utils::estimate_tx_cost},
},
anyhow::Result,
ethers::providers::Middleware,
prometheus_client::registry::Registry,
std::sync::Arc,
tokio::{sync::RwLock, time::Duration},
};

const POLL_INTERVAL: Duration = Duration::from_secs(2);

pub async fn debug_gas(opts: &DebugGasOptions) -> Result<()> {
let config = Config::load(&opts.config.config)?;
let chain_config = config.get_chain_config(&opts.chain_id)?;

// Create metrics registry for the instrumented contract
let metrics_registry = Arc::new(RwLock::new(Registry::default()));
let rpc_metrics = Arc::new(RpcMetrics::new(metrics_registry.clone()).await);

// Get network_id by creating a temporary contract
let temp_contract = InstrumentedPythContract::from_config(
&chain_config,
opts.chain_id.clone(),
rpc_metrics.clone(),
)?;
let network_id = temp_contract.get_network_id().await?.as_u64();

// Get the keeper private key
let keeper_private_key =
config.keeper.private_key.load()?.ok_or_else(|| {
anyhow::anyhow!("Keeper private key is required for debug_gas command")
})?;

// Instantiate InstrumentedSignablePythContract
let contract = Arc::new(InstrumentedSignablePythContract::from_config(
&chain_config,
&keeper_private_key,
opts.chain_id.clone(),
rpc_metrics,
network_id,
)?);

tracing::info!("Starting gas price debugger for chain: {}", opts.chain_id);
tracing::info!("Watching for new blocks and calling estimate_tx_cost on each block...");
tracing::info!("Press Ctrl+C to stop");

let middleware = contract.client().clone();
let mut last_block_number: Option<u64> = None;

loop {
// Get the latest block number
let latest_block = match middleware.get_block_number().await {
Ok(block_num) => block_num.as_u64(),
Err(e) => {
tracing::error!("Failed to get latest block number: {}", e);
tokio::time::sleep(POLL_INTERVAL).await;
continue;
}
};

// Check if we have a new block
if let Some(last) = last_block_number {
if latest_block <= last {
tokio::time::sleep(POLL_INTERVAL).await;
continue;
}
}

// New block detected, estimate transaction cost
let gas_limit: u128 = chain_config.gas_limit as u128;
match estimate_tx_cost(middleware.clone(), chain_config.legacy_tx, gas_limit).await {
Ok(tx_cost) => {
let tx_cost_eth = tx_cost as f64 / 1e18;
let effective_gas_price = tx_cost / gas_limit;
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Division by zero panic when gas_limit is 0

The effective_gas_price calculation at line 78 performs tx_cost / gas_limit where gas_limit is derived from chain_config.gas_limit (a u32). If gas_limit is 0, this causes a panic due to integer division by zero.

Root Cause

The gas_limit field in EthereumConfig (apps/fortuna/src/config.rs:158) is a u32 with no default value and no validation that it is non-zero. At apps/fortuna/src/command/debug_gas.rs:74, it is cast to u128 and then used as a divisor at line 78:

let gas_limit: u128 = chain_config.gas_limit as u128;
// ...
let effective_gas_price = tx_cost / gas_limit;  // panics if gas_limit == 0

While a gas_limit of 0 is an unusual configuration, there is no guard against it. Other callers of estimate_tx_cost (e.g., apps/fortuna/src/keeper/fee.rs:305-306) get gas_limit from the on-chain provider info rather than the config, so they don't have this specific issue.

Impact: The debug tool panics at runtime with a division-by-zero error if the config has gas_limit: 0.

Suggested change
let effective_gas_price = tx_cost / gas_limit;
let effective_gas_price = if gas_limit > 0 { tx_cost / gas_limit } else { 0 };
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

let effective_gas_price_gwei = effective_gas_price as f64 / 1e9;
tracing::info!(
"Block {}: tx_cost ({} gas) = {} ETH ({} wei), effective_gas_price = {} gwei ({} wei)",
latest_block,
gas_limit,
tx_cost_eth,
tx_cost,
effective_gas_price_gwei,
effective_gas_price
);
}
Err(e) => {
tracing::error!(
"Block {}: Failed to estimate transaction cost: {}",
latest_block,
e
);
}
}

last_block_number = Some(latest_block);
tokio::time::sleep(POLL_INTERVAL).await;
}
}
12 changes: 8 additions & 4 deletions apps/fortuna/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ use {
std::{collections::HashMap, fs},
};
pub use {
generate::GenerateOptions, get_request::GetRequestOptions, inspect::InspectOptions,
prometheus_client::metrics::histogram::Histogram, register_provider::RegisterProviderOptions,
request_randomness::RequestRandomnessOptions, run::RunOptions,
setup_provider::SetupProviderOptions, withdraw_fees::WithdrawFeesOptions,
debug_gas::DebugGasOptions, generate::GenerateOptions, get_request::GetRequestOptions,
inspect::InspectOptions, prometheus_client::metrics::histogram::Histogram,
register_provider::RegisterProviderOptions, request_randomness::RequestRandomnessOptions,
run::RunOptions, setup_provider::SetupProviderOptions, withdraw_fees::WithdrawFeesOptions,
};

mod debug_gas;
mod generate;
mod get_request;
mod inspect;
Expand Down Expand Up @@ -58,6 +59,9 @@ pub enum Options {

/// Withdraw any of the provider's accumulated fees from the contract.
WithdrawFees(WithdrawFeesOptions),

/// Debug gas estimation issues on a blockchain by watching blocks and calling get_gas_price.
DebugGas(DebugGasOptions),
}

#[derive(Args, Clone, Debug)]
Expand Down
16 changes: 16 additions & 0 deletions apps/fortuna/src/config/debug_gas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use {
crate::{api::ChainId, config::ConfigOptions},
clap::Args,
};

#[derive(Args, Clone, Debug)]
#[command(next_help_heading = "Debug Gas Options")]
#[group(id = "DebugGas")]
pub struct DebugGasOptions {
#[command(flatten)]
pub config: ConfigOptions,

/// The chain ID to debug gas estimation for.
#[arg(long = "chain-id")]
pub chain_id: ChainId,
}
1 change: 1 addition & 0 deletions apps/fortuna/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ async fn main() -> Result<()> {
config::Options::RequestRandomness(opts) => command::request_randomness(&opts).await,
config::Options::Inspect(opts) => command::inspect(&opts).await,
config::Options::WithdrawFees(opts) => command::withdraw_fees(&opts).await,
config::Options::DebugGas(opts) => command::debug_gas(&opts).await,
}
}
2 changes: 1 addition & 1 deletion target_chains/fuel/sdk/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@
"./package.json": "./package.json"
},
"module": "./dist/esm/index.mjs"
}
}
Loading