From b0a53f5f073839dd0a64e54f36fd8254fd46a994 Mon Sep 17 00:00:00 2001 From: Luca Joss <43531661+ljoss17@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:52:29 +0100 Subject: [PATCH] Add dynamic gas fees metrics (#3798) * Add simple metric for dynamic gas price after multiplication * Add max gas price configuration to dynamic gas configuration * Apply dynamic max gas configuration * Add 2 new metrics for dynamic gas fees * Add guide entry for Dynamic gas configuration and metrics * Fix EIP query parsing * Use chain id instead of RPC address in emitted metrics --------- Co-authored-by: Romain Ruetschi <106849+romac@users.noreply.github.com> --- crates/relayer/src/chain/cosmos.rs | 7 +- crates/relayer/src/chain/cosmos/batch.rs | 4 ++ .../relayer/src/chain/cosmos/eip_base_fee.rs | 12 +++- crates/relayer/src/chain/cosmos/estimate.rs | 2 +- crates/relayer/src/chain/cosmos/gas.rs | 55 ++++++++++++++-- crates/relayer/src/chain/cosmos/types/gas.rs | 2 + crates/relayer/src/config/dynamic_gas.rs | 66 ++++++++++++++----- crates/telemetry/src/state.rs | 58 ++++++++++++++++ guide/src/SUMMARY.md | 3 +- .../configuration/dynamic-gas-fees.md | 21 ++++++ .../src/documentation/telemetry/operators.md | 18 ++++- .../src/tests/dynamic_gas_fee.rs | 5 +- tools/test-framework/src/relayer/tx.rs | 1 + 13 files changed, 223 insertions(+), 31 deletions(-) create mode 100644 guide/src/documentation/configuration/dynamic-gas-fees.md diff --git a/crates/relayer/src/chain/cosmos.rs b/crates/relayer/src/chain/cosmos.rs index 1c61ac6490..d5ccc13242 100644 --- a/crates/relayer/src/chain/cosmos.rs +++ b/crates/relayer/src/chain/cosmos.rs @@ -496,8 +496,11 @@ impl CosmosSdkChain { pub fn dynamic_gas_price(&self) -> GasPrice { let gas_config = GasConfig::from(self.config()); - self.rt - .block_on(dynamic_gas_price(&gas_config, &self.config.rpc_addr)) + self.rt.block_on(dynamic_gas_price( + &gas_config, + &self.config.id, + &self.config.rpc_addr, + )) } /// The unbonding period of this chain diff --git a/crates/relayer/src/chain/cosmos/batch.rs b/crates/relayer/src/chain/cosmos/batch.rs index 9e39da9679..3e52f52aec 100644 --- a/crates/relayer/src/chain/cosmos/batch.rs +++ b/crates/relayer/src/chain/cosmos/batch.rs @@ -256,6 +256,7 @@ async fn batch_messages( let max_fee = gas_amount_to_fee( &config.gas_config, config.gas_config.max_gas, + &config.chain_id, &config.rpc_address, ) .await; @@ -376,6 +377,7 @@ mod tests { let max_fee = gas_amount_to_fee( &config.gas_config, config.gas_config.max_gas, + &config.chain_id, &config.rpc_address, ) .await; @@ -463,6 +465,7 @@ mod tests { let max_fee = gas_amount_to_fee( &config.gas_config, config.gas_config.max_gas, + &config.chain_id, &config.rpc_address, ) .await; @@ -593,6 +596,7 @@ mod tests { let max_fee = gas_amount_to_fee( &config.gas_config, config.gas_config.max_gas, + &config.chain_id, &config.rpc_address, ) .await; diff --git a/crates/relayer/src/chain/cosmos/eip_base_fee.rs b/crates/relayer/src/chain/cosmos/eip_base_fee.rs index 70dfeac9c5..75cc9eedd4 100644 --- a/crates/relayer/src/chain/cosmos/eip_base_fee.rs +++ b/crates/relayer/src/chain/cosmos/eip_base_fee.rs @@ -4,13 +4,14 @@ use std::str::FromStr; use serde::Deserialize; use subtle_encoding::base64; +use tendermint_rpc::Url; use tracing::debug; use ibc_proto::cosmos::base::v1beta1::DecProto; use crate::error::Error; -pub async fn query_eip_base_fee(rpc_address: &str) -> Result { +pub async fn query_eip_base_fee(rpc_address: &Url) -> Result { debug!("Querying Omosis EIP-1559 base fee from {rpc_address}"); let url = @@ -22,6 +23,11 @@ pub async fn query_eip_base_fee(rpc_address: &str) -> Result { return Err(Error::http_response(response.status())); } + #[derive(Deserialize)] + struct EipBaseFeeHTTPResult { + result: EipBaseFeeResult, + } + #[derive(Deserialize)] struct EipBaseFeeResult { response: EipBaseFeeResponse, @@ -32,9 +38,9 @@ pub async fn query_eip_base_fee(rpc_address: &str) -> Result { value: String, } - let result: EipBaseFeeResult = response.json().await.map_err(Error::http_response_body)?; + let result: EipBaseFeeHTTPResult = response.json().await.map_err(Error::http_response_body)?; - let encoded = result.response.value; + let encoded = result.result.response.value; let decoded = base64::decode(encoded).map_err(Error::base64_decode)?; let dec_proto: DecProto = prost::Message::decode(decoded.as_ref()) diff --git a/crates/relayer/src/chain/cosmos/estimate.rs b/crates/relayer/src/chain/cosmos/estimate.rs index c8717630be..87a18a26b4 100644 --- a/crates/relayer/src/chain/cosmos/estimate.rs +++ b/crates/relayer/src/chain/cosmos/estimate.rs @@ -88,7 +88,7 @@ async fn estimate_fee_with_tx( )); } - let adjusted_fee = gas_amount_to_fee(gas_config, estimated_gas, rpc_address).await; + let adjusted_fee = gas_amount_to_fee(gas_config, estimated_gas, chain_id, rpc_address).await; debug!( id = %chain_id, diff --git a/crates/relayer/src/chain/cosmos/gas.rs b/crates/relayer/src/chain/cosmos/gas.rs index 5ed01f9ff4..22601ff2d7 100644 --- a/crates/relayer/src/chain/cosmos/gas.rs +++ b/crates/relayer/src/chain/cosmos/gas.rs @@ -1,16 +1,24 @@ use core::cmp::min; use ibc_proto::cosmos::base::v1beta1::Coin; use ibc_proto::cosmos::tx::v1beta1::Fee; +use ibc_relayer_types::core::ics24_host::identifier::ChainId; use num_bigint::BigInt; use num_rational::BigRational; use tendermint_rpc::Url; +use tracing::warn; use crate::chain::cosmos::types::gas::GasConfig; use crate::config::GasPrice; +use crate::telemetry; use super::eip_base_fee::query_eip_base_fee; -pub async fn gas_amount_to_fee(config: &GasConfig, gas_amount: u64, rpc_address: &Url) -> Fee { +pub async fn gas_amount_to_fee( + config: &GasConfig, + gas_amount: u64, + chain_id: &ChainId, + rpc_address: &Url, +) -> Fee { let adjusted_gas_limit = adjust_estimated_gas(AdjustGas { gas_multiplier: config.gas_multiplier, max_gas: config.max_gas, @@ -18,7 +26,7 @@ pub async fn gas_amount_to_fee(config: &GasConfig, gas_amount: u64, rpc_address: }); // The fee in coins based on gas amount - let dynamic_gas_price = dynamic_gas_price(config, rpc_address).await; + let dynamic_gas_price = dynamic_gas_price(config, chain_id, rpc_address).await; let amount = calculate_fee(adjusted_gas_limit, &dynamic_gas_price); Fee { @@ -29,16 +37,51 @@ pub async fn gas_amount_to_fee(config: &GasConfig, gas_amount: u64, rpc_address: } } -pub async fn dynamic_gas_price(config: &GasConfig, rpc_address: &Url) -> GasPrice { +pub async fn dynamic_gas_price( + config: &GasConfig, + chain_id: &ChainId, + rpc_address: &Url, +) -> GasPrice { if let Some(dynamic_gas_price_multiplier) = config.dynamic_gas_price_multiplier { - query_eip_base_fee(&rpc_address.to_string()) + let dynamic_gas_price = query_eip_base_fee(rpc_address) .await .map(|base_fee| base_fee * dynamic_gas_price_multiplier) .map(|new_price| GasPrice { price: new_price, denom: config.gas_price.denom.clone(), - }) - .unwrap_or_else(|_| config.gas_price.clone()) + }); + + let dynamic_gas_price = match dynamic_gas_price { + Ok(dynamic_gas_price) => { + telemetry!( + dynamic_gas_queried_success_fees, + chain_id, + dynamic_gas_price.price + ); + + dynamic_gas_price + } + Err(e) => { + warn!("failed to query EIP base fee, will fallback to configured `gas_price`: {e}"); + config.gas_price.clone() + } + }; + + telemetry!(dynamic_gas_queried_fees, chain_id, dynamic_gas_price.price); + + if dynamic_gas_price.price > config.max_dynamic_gas_price { + warn!( + "queried EIP gas price is higher than configured max gas price, \ + will fallback to configured `max_dynamic_gas_price`. Queried: {}, maximum: {}", + dynamic_gas_price.price, config.max_dynamic_gas_price + ); + + return GasPrice::new(config.max_dynamic_gas_price, dynamic_gas_price.denom); + } + + telemetry!(dynamic_gas_paid_fees, chain_id, dynamic_gas_price.price); + + dynamic_gas_price } else { config.gas_price.clone() } diff --git a/crates/relayer/src/chain/cosmos/types/gas.rs b/crates/relayer/src/chain/cosmos/types/gas.rs index 10d0aa19b0..0c96b01c7b 100644 --- a/crates/relayer/src/chain/cosmos/types/gas.rs +++ b/crates/relayer/src/chain/cosmos/types/gas.rs @@ -18,6 +18,7 @@ pub struct GasConfig { pub max_fee: Fee, pub fee_granter: String, pub dynamic_gas_price_multiplier: Option, + pub max_dynamic_gas_price: f64, } impl<'a> From<&'a CosmosSdkConfig> for GasConfig { @@ -30,6 +31,7 @@ impl<'a> From<&'a CosmosSdkConfig> for GasConfig { max_fee: max_fee_from_config(config), fee_granter: fee_granter_from_config(config), dynamic_gas_price_multiplier: config.dynamic_gas.dynamic_gas_price(), + max_dynamic_gas_price: config.dynamic_gas.max_gas_price, } } } diff --git a/crates/relayer/src/config/dynamic_gas.rs b/crates/relayer/src/config/dynamic_gas.rs index 1c70656539..728dbfd675 100644 --- a/crates/relayer/src/config/dynamic_gas.rs +++ b/crates/relayer/src/config/dynamic_gas.rs @@ -6,12 +6,19 @@ use serde_derive::Serialize; flex_error::define_error! { Error { - TooSmall + MultiplierTooSmall { value: f64 } |e| { format_args!("`gas_price_multiplier` in dynamic_gas configuration must be greater than or equal to {}, found {}", DynamicGas::MIN_BOUND, e.value) }, + + MaxPriceTooSmall + { value: f64 } + |e| { + format_args!("`max_gas_price` in dynamic_gas configuration must be greater than or equal to {}, found {}", + DynamicGas::CHAIN_MIN_PRICE, e.value) + }, } } @@ -19,27 +26,36 @@ flex_error::define_error! { pub struct DynamicGas { pub enabled: bool, pub gas_price_multiplier: f64, + pub max_gas_price: f64, } impl DynamicGas { const DEFAULT: f64 = 1.1; + const DEFAULT_MAX_PRICE: f64 = 0.6; const MIN_BOUND: f64 = 1.0; + // Using Osmosis min https://github.com/osmosis-labs/osmosis/blob/v21.2.1/x/txfees/keeper/mempool-1559/code.go#L45 + const CHAIN_MIN_PRICE: f64 = 0.0025; - pub fn new(enabled: bool, value: f64) -> Result { - if value < Self::MIN_BOUND { - return Err(Error::too_small(value)); + pub fn new(enabled: bool, multiplier: f64, max_price: f64) -> Result { + if multiplier < Self::MIN_BOUND { + return Err(Error::multiplier_too_small(multiplier)); + } + if max_price < Self::CHAIN_MIN_PRICE { + return Err(Error::max_price_too_small(max_price)); } Ok(Self { enabled, - gas_price_multiplier: value, + gas_price_multiplier: multiplier, + max_gas_price: max_price, }) } // Unsafe GasMultiplier used for test cases only. - pub fn unsafe_new(enabled: bool, value: f64) -> Self { + pub fn unsafe_new(enabled: bool, multiplier: f64, max_price: f64) -> Self { Self { enabled, - gas_price_multiplier: value, + gas_price_multiplier: multiplier, + max_gas_price: max_price, } } @@ -57,6 +73,7 @@ impl Default for DynamicGas { Self { enabled: false, gas_price_multiplier: Self::DEFAULT, + max_gas_price: Self::DEFAULT_MAX_PRICE, } } } @@ -76,11 +93,29 @@ impl<'de> Deserialize<'de> for DynamicGas { D::Error::invalid_value(Unexpected::Other("missing field"), &"gas_price_multiplier") })?; - DynamicGas::new(enabled, gas_price_multiplier).map_err(|e| match e.detail() { - ErrorDetail::TooSmall(_) => D::Error::invalid_value( - Unexpected::Float(gas_price_multiplier), - &format!("a floating-point value less than {}", Self::MIN_BOUND).as_str(), - ), + let max_gas_price = value["max_gas_price"].as_f64().ok_or_else(|| { + D::Error::invalid_value(Unexpected::Other("missing field"), &"max_gas_price") + })?; + + DynamicGas::new(enabled, gas_price_multiplier, max_gas_price).map_err(|e| { + match e.detail() { + ErrorDetail::MultiplierTooSmall(_) => D::Error::invalid_value( + Unexpected::Float(gas_price_multiplier), + &format!( + "a floating-point value less than {} for multiplier", + Self::MIN_BOUND + ) + .as_str(), + ), + ErrorDetail::MaxPriceTooSmall(_) => D::Error::invalid_value( + Unexpected::Float(gas_price_multiplier), + &format!( + "a floating-point value less than {} for max gas price", + Self::CHAIN_MIN_PRICE + ) + .as_str(), + ), + } }) } } @@ -101,7 +136,7 @@ mod tests { } let err = toml::from_str::( - "dynamic_gas = { enabled = true, gas_price_multiplier = 0.9 }", + "dynamic_gas = { enabled = true, gas_price_multiplier = 0.9, max_gas_price = 0.6 }", ) .unwrap_err() .to_string(); @@ -113,7 +148,7 @@ mod tests { #[test] fn safe_gas_multiplier() { - let dynamic_gas = DynamicGas::new(true, 0.6); + let dynamic_gas = DynamicGas::new(true, 0.6, 0.6); assert!( dynamic_gas.is_err(), "Gas multiplier should be an error if value is lower than 1.0: {dynamic_gas:?}" @@ -122,7 +157,8 @@ mod tests { #[test] fn unsafe_gas_multiplier() { - let dynamic_gas = DynamicGas::unsafe_new(true, 0.6); + let dynamic_gas = DynamicGas::unsafe_new(true, 0.6, 0.4); assert_eq!(dynamic_gas.gas_price_multiplier, 0.6); + assert_eq!(dynamic_gas.max_gas_price, 0.4); } } diff --git a/crates/telemetry/src/state.rs b/crates/telemetry/src/state.rs index 00461eb2fc..0cc341fa46 100644 --- a/crates/telemetry/src/state.rs +++ b/crates/telemetry/src/state.rs @@ -200,6 +200,15 @@ pub struct TelemetryState { /// Number of errors observed by Hermes when broadcasting a Tx broadcast_errors: Counter, + + /// The EIP-1559 base fee queried + dynamic_gas_queried_fees: ObservableGauge, + + /// The EIP-1559 base fee paid + dynamic_gas_paid_fees: ObservableGauge, + + /// The EIP-1559 base fee successfully queried + dynamic_gas_queried_success_fees: ObservableGauge, } impl TelemetryState { @@ -381,6 +390,21 @@ impl TelemetryState { "Number of errors observed by Hermes when broadcasting a Tx", ) .init(), + + dynamic_gas_queried_fees: meter + .f64_observable_gauge("dynamic_gas_queried_fees") + .with_description("The EIP-1559 base fee queried") + .init(), + + dynamic_gas_paid_fees: meter + .f64_observable_gauge("dynamic_gas_paid_fees") + .with_description("The EIP-1559 base fee paid") + .init(), + + dynamic_gas_queried_success_fees: meter + .f64_observable_gauge("dynamic_gas_queried_fees") + .with_description("The EIP-1559 base fee successfully queried") + .init(), } } @@ -1127,6 +1151,31 @@ impl TelemetryState { self.broadcast_errors.add(&cx, 1, labels); } + + pub fn dynamic_gas_queried_fees(&self, chain_id: &ChainId, amount: f64) { + let cx = Context::current(); + + let labels = &[KeyValue::new("identifier", chain_id.to_string())]; + + self.dynamic_gas_queried_fees.observe(&cx, amount, labels); + } + + pub fn dynamic_gas_paid_fees(&self, chain_id: &ChainId, amount: f64) { + let cx = Context::current(); + + let labels = &[KeyValue::new("identifier", chain_id.to_string())]; + + self.dynamic_gas_paid_fees.observe(&cx, amount, labels); + } + + pub fn dynamic_gas_queried_success_fees(&self, chain_id: &ChainId, amount: f64) { + let cx = Context::current(); + + let labels = &[KeyValue::new("identifier", chain_id.to_string())]; + + self.dynamic_gas_queried_success_fees + .observe(&cx, amount, labels); + } } use std::sync::Arc; @@ -1196,6 +1245,15 @@ impl AggregatorSelector for CustomAggregatorSelector { // TODO: Once quantile sketches are supported, replace histograms with that. "tx_latency_submitted" => Some(Arc::new(histogram(&self.get_submitted_range()))), "tx_latency_confirmed" => Some(Arc::new(histogram(&self.get_confirmed_range()))), + "dynamic_gas_queried_fees" => Some(Arc::new(histogram(&[ + 0.0025, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0, + ]))), + "dynamic_gas_paid_fees" => Some(Arc::new(histogram(&[ + 0.0025, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0, + ]))), + "dynamic_gas_queried_success_fees" => Some(Arc::new(histogram(&[ + 0.0025, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0, + ]))), "ics29_period_fees" => Some(Arc::new(last_value())), _ => Some(Arc::new(sum())), } diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index e85b810e6b..fbef9c31b3 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -28,12 +28,13 @@ - [Start relaying](./tutorials/production/start-relaying.md) - [Configuration](./documentation/configuration/index.md) + - [CometBFT Compatibility modes](./documentation/configuration/comet-compat-mode.md) - [Configure Hermes](./documentation/configuration/configure-hermes.md) - [Description of the parameters](./documentation/configuration/description.md) + - [Dynamic gas fees](./documentation/configuration/dynamic-gas-fees.md) - [Filter incentivized packets](./documentation/configuration/filter-incentivized.md) - [Packet clearing](./documentation/configuration/packet-clearing.md) - [Performance tuning](./documentation/configuration/performance.md) - - [CometBFT Compatibility modes](./documentation/configuration/comet-compat-mode.md) - [Telemetry](./documentation/telemetry/index.md) - [Operators guide](./documentation/telemetry/operators.md) diff --git a/guide/src/documentation/configuration/dynamic-gas-fees.md b/guide/src/documentation/configuration/dynamic-gas-fees.md new file mode 100644 index 0000000000..24c5540b8e --- /dev/null +++ b/guide/src/documentation/configuration/dynamic-gas-fees.md @@ -0,0 +1,21 @@ +# Dynamic Gas Fees + +Some chains use a dynamic gas price system instead of static gas price. By configuring the `dynamic_gas` for those chains, Hermes will query the gas price and apply the configured multiplier instead of using the configured static gas price: + +```toml +... +[.dynamic_gas] +enabled = true +gas_price_multiplier = 1.1 +max_gas_price = 0.6 +... +``` + +## Notes + +* If the query fails, Hermes will fallback to the configured static gas price. +* If the queried gas price is higher than the maximum configured gas price, Hermes will use the maximum gas price but this might cause the relaying of the packet to fail due to insufficient fees. + +## Monitoring + +As this feature can be delicate to handle, multiple metrics have been added in order to monitor the dynamic gas fees. Please consult the [Dynamic Gas Metrics](../telemetry/operators.md#dynamic-gas-fees) section for detailed information on these metrics. \ No newline at end of file diff --git a/guide/src/documentation/telemetry/operators.md b/guide/src/documentation/telemetry/operators.md index 96204843c4..8177e62e5d 100644 --- a/guide/src/documentation/telemetry/operators.md +++ b/guide/src/documentation/telemetry/operators.md @@ -161,4 +161,20 @@ Note that this metrics is disabled if `misbehaviour = false` in your Hermes conf | Name | Description | OpenTelemetry type | Configuration Dependencies | | ------------------- | --------------------------------------------------------------------------- | ------------------- | -------------------------- | | `ics29_fee_amounts_total` | Total amount received from ICS29 fees  | `u64` Counter  | None | -| `ics29_period_fees` | Amount of ICS29 fees rewarded over the past 7 days type | `u64` ValueRecorder | None | \ No newline at end of file +| `ics29_period_fees` | Amount of ICS29 fees rewarded over the past 7 days type | `u64` ValueRecorder | None | + +## Dynamic gas fees + +The introduction of dynamic gas fees adds additional configuration which can be delicate to handle correctly. The following metrics can help correctly configure your relayer. + +| Name | Description | OpenTelemetry type | Configuration Dependencies | +| ---------------------------------- | -------------------------------------------------------------------- | ------------------- | -------------------------- | +| `dynamic_gas_queried_fees` | The EIP-1559 base fee queried  | `u64` ValueRecorder | None | +| `dynamic_gas_queried_success_fees` | The EIP-1559 base fee successfully queried   | `u64` ValueRecorder | None | +| `dynamic_gas_paid_fees` | The EIP-1559 base fee paid  | `u64` ValueRecorder | None | + +Notes: + +- The `dynamic_gas_queried_fees` contains the gas price used after the query but before filtering by configured `max_gas_price`. This means that this metric might contain the static gas price if the query failed. +- The `dynamic_gas_queried_success_fees` will only contain the gas price when the query succeeds, if this metric doesn't contain values or less values that the `dynamic_gas_queried_fees` this could indicate an issue with the endpoint used to query the fees. +- `dynamic_gas_paid_fees` will contain the price used by the relayer, the maximum value for this metric is `max_gas_price`. If there are multiple values in the same bucket as the `max_gas_price` it could indicate that the gas price queried is often higher than the configured `max_gas_price`. \ No newline at end of file diff --git a/tools/integration-test/src/tests/dynamic_gas_fee.rs b/tools/integration-test/src/tests/dynamic_gas_fee.rs index 378ed8ba3c..fb07f0934a 100644 --- a/tools/integration-test/src/tests/dynamic_gas_fee.rs +++ b/tools/integration-test/src/tests/dynamic_gas_fee.rs @@ -50,7 +50,7 @@ impl TestOverrides for DynamicGasTest { ChainConfig::CosmosSdk(chain_config_a) => { chain_config_a.gas_price = GasPrice::new(0.1, chain_config_a.gas_price.denom.clone()); - chain_config_a.dynamic_gas = DynamicGas::unsafe_new(false, 1.1); + chain_config_a.dynamic_gas = DynamicGas::unsafe_new(false, 1.1, 0.6); } } @@ -58,7 +58,8 @@ impl TestOverrides for DynamicGasTest { ChainConfig::CosmosSdk(chain_config_b) => { chain_config_b.gas_price = GasPrice::new(0.1, chain_config_b.gas_price.denom.clone()); - chain_config_b.dynamic_gas = DynamicGas::unsafe_new(self.dynamic_gas_enabled, 1.1); + chain_config_b.dynamic_gas = + DynamicGas::unsafe_new(self.dynamic_gas_enabled, 1.1, 0.6); } } } diff --git a/tools/test-framework/src/relayer/tx.rs b/tools/test-framework/src/relayer/tx.rs index 81e4a0d75e..eaf0bb273b 100644 --- a/tools/test-framework/src/relayer/tx.rs +++ b/tools/test-framework/src/relayer/tx.rs @@ -44,6 +44,7 @@ pub fn gas_config_for_test(native_token: String) -> GasConfig { max_fee, fee_granter, dynamic_gas_price_multiplier: None, + max_dynamic_gas_price: 0.6, } }