Skip to content

Commit c4c9209

Browse files
author
allthatjazzleo
committed
Enhance dynamic gas price configuration and querying support for Cosmos EVM, Osmosis, and SkipFeeMarket
1 parent 50873fa commit c4c9209

File tree

9 files changed

+217
-47
lines changed

9 files changed

+217
-47
lines changed

config.toml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -311,14 +311,24 @@ gas_multiplier = 1.1
311311
# Query the current gas price from the chain instead of using the static `gas_price` from the config.
312312
# Useful for chains which have [EIP-1559][eip]-like dynamic gas price.
313313
#
314-
# At the moment, only chains which support the `osmosis.txfees.v1beta1.Query/GetEipBaseFee`
315-
# query or have enabled Skip's `x/feemarket` module https://github.com/skip-mev/feemarket
314+
# At the moment, only chains which support the `/cosmos.evm.feemarket.v1.Query/BaseFee` query or
315+
# `osmosis.txfees.v1beta1.Query/GetEipBaseFee` query or have enabled Skip's `x/feemarket` module https://github.com/skip-mev/feemarket
316316
# can be used with dynamic gas price enabled.
317317
#
318+
# The `type` field allows you to specify which query method to use:
319+
# - `skip_fee_market`: Use Skip's feemarket module query at `/feemarket.feemarket.v1.Query/GasPrices`
320+
# - `osmosis`: Use Osmosis EIP-1559 query at `/osmosis.txfees.v1beta1.Query/GetEipBaseFee`
321+
# - `cosmos_evm`: Use Cosmos EVM query at `/cosmos.evm.feemarket.v1.Query/BaseFee`
322+
#
323+
# If `type` is not specified, Hermes will attempt to auto-detect for backwards compatibility:
324+
# - Chains starting with "osmosis" or "osmo-test" will use `osmosis` type
325+
# - All other chains will default to `skip_fee_market` type
326+
#
318327
# See this page in the Hermes guide for more information:
319328
# https://hermes.informal.systems/documentation/configuration/dynamic-gas-fees.html
320329
#
321330
# Default: { enabled = false, multiplier = 1.1, max = 0.6 }
331+
# Example with type specified: { enabled = true, multiplier = 1.1, max = 0.6, type = 'cosmos_evm' }
322332
dynamic_gas_price = { enabled = false, multiplier = 1.1, max = 0.6 }
323333

324334
# Specify how many IBC messages at most to include in a single transaction.

crates/relayer-cli/src/chain_registry.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ where
118118

119119
// Use EIP-1559 dynamic gas price for Osmosis
120120
let dynamic_gas_price = if chain_data.chain_id.as_str() == "osmosis-1" {
121-
DynamicGasPrice::unsafe_new(true, 1.1, 0.6)
121+
DynamicGasPrice::unsafe_new(true, 1.1, 0.6, None)
122122
} else {
123123
DynamicGasPrice::disabled()
124124
};

crates/relayer/src/chain/cosmos/eip_base_fee.rs

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,29 @@ use tracing::{debug, trace};
1010
use ibc_proto::cosmos::base::v1beta1::{DecCoin, DecProto};
1111
use ibc_relayer_types::core::ics24_host::identifier::ChainId;
1212

13-
use crate::error::Error;
13+
use crate::{config::dynamic_gas::DynamicGasType, error::Error};
1414

1515
pub async fn query_eip_base_fee(
1616
rpc_address: &Url,
1717
gas_price_denom: &str,
18+
dynamic_gas_type: &Option<DynamicGasType>,
1819
chain_id: &ChainId,
1920
) -> Result<f64, Error> {
20-
debug!("Querying Omosis EIP-1559 base fee from {rpc_address}");
21-
22-
let chain_name = chain_id.name();
23-
24-
let is_osmosis = chain_name.starts_with("osmosis") || chain_name.starts_with("osmo-test");
25-
26-
let url = if is_osmosis {
27-
format!(
28-
"{}abci_query?path=\"/osmosis.txfees.v1beta1.Query/GetEipBaseFee\"",
29-
rpc_address
30-
)
31-
} else {
32-
format!(
33-
"{}abci_query?path=\"/feemarket.feemarket.v1.Query/GasPrices\"&denom={}",
34-
rpc_address, gas_price_denom
35-
)
21+
let dynamic_gas_type = match dynamic_gas_type {
22+
Some(dynamic_gas_type) => dynamic_gas_type,
23+
None => {
24+
// backward compatibility for chains that do not specify dynamic gas type
25+
if chain_id.name().starts_with("osmosis") || chain_id.name().starts_with("osmo-test") {
26+
&DynamicGasType::Osmosis
27+
} else {
28+
&DynamicGasType::SkipFeeMarket
29+
}
30+
}
3631
};
3732

33+
debug!("Querying {dynamic_gas_type} base fee from {rpc_address}");
34+
35+
let url = dynamic_gas_type.get_url(rpc_address, gas_price_denom);
3836
let response = reqwest::get(&url).await.map_err(Error::http_request)?;
3937

4038
if !response.status().is_success() {
@@ -58,19 +56,15 @@ pub async fn query_eip_base_fee(
5856

5957
let result: EipBaseFeeHTTPResult = response.json().await.map_err(Error::http_response_body)?;
6058

61-
let amount = if is_osmosis {
62-
extract_dynamic_gas_price_osmosis(result.result.response.value)?
63-
} else {
64-
extract_dynamic_gas_price(result.result.response.value)?
65-
};
59+
let amount = dynamic_gas_type.extract_dynamic_gas_price(result.result.response.value)?;
6660

6761
trace!("EIP-1559 base fee: {amount}");
6862

6963
Ok(amount)
7064
}
7165

7266
/// This method extracts the gas base fee from Skip's feemarket
73-
fn extract_dynamic_gas_price(encoded: String) -> Result<f64, Error> {
67+
fn extract_dynamic_gas_price_fee_market(encoded: String) -> Result<f64, Error> {
7468
let decoded = base64::decode(encoded).map_err(Error::base64_decode)?;
7569

7670
let gas_price_response: GasPriceResponse =
@@ -84,8 +78,8 @@ fn extract_dynamic_gas_price(encoded: String) -> Result<f64, Error> {
8478
f64::from_str(dec.to_string().as_str()).map_err(Error::parse_float)
8579
}
8680

87-
/// This method extracts the gas base fee from Osmosis EIP-1559
88-
fn extract_dynamic_gas_price_osmosis(encoded: String) -> Result<f64, Error> {
81+
/// This method extracts the gas base fee from Osmosis EIP-1559 and Cosmos EVM EIP-1559
82+
fn extract_dynamic_gas_price(encoded: String) -> Result<f64, Error> {
8983
let decoded = base64::decode(encoded).map_err(Error::base64_decode)?;
9084

9185
let dec_proto: DecProto = prost::Message::decode(decoded.as_ref())
@@ -189,3 +183,74 @@ impl fmt::Display for Decimal {
189183
}
190184
}
191185
}
186+
187+
impl DynamicGasType {
188+
fn get_url(&self, rpc_address: &Url, gas_price_denom: &str) -> String {
189+
match self {
190+
DynamicGasType::SkipFeeMarket => format!(
191+
"{}abci_query?path=\"/feemarket.feemarket.v1.Query/GasPrices\"&denom={}",
192+
rpc_address, gas_price_denom
193+
),
194+
DynamicGasType::Osmosis => format!(
195+
"{}abci_query?path=\"/osmosis.txfees.v1beta1.Query/GetEipBaseFee\"",
196+
rpc_address
197+
),
198+
DynamicGasType::CosmosEvm => format!(
199+
"{}abci_query?path=\"/cosmos.evm.feemarket.v1.Query/BaseFee\"",
200+
rpc_address
201+
),
202+
}
203+
}
204+
205+
fn extract_dynamic_gas_price(&self, encoded: String) -> Result<f64, Error> {
206+
match self {
207+
DynamicGasType::SkipFeeMarket => extract_dynamic_gas_price_fee_market(encoded),
208+
DynamicGasType::Osmosis => extract_dynamic_gas_price(encoded),
209+
DynamicGasType::CosmosEvm => extract_dynamic_gas_price(encoded),
210+
}
211+
}
212+
}
213+
214+
#[cfg(test)]
215+
mod tests {
216+
use super::*;
217+
218+
#[test]
219+
fn test_extract_dynamic_gas_price_fee_market() {
220+
// Test with the provided encoded value
221+
let encoded = "ChgKA3VvbRIRMTAwMDAwMDAwMDAwMDAwMDA".to_string();
222+
223+
let result = extract_dynamic_gas_price_fee_market(encoded);
224+
225+
assert!(result.is_ok());
226+
let gas_price = result.unwrap();
227+
228+
assert_eq!(gas_price, 0.01);
229+
}
230+
231+
#[test]
232+
fn test_extract_dynamic_gas_price_osmosis() {
233+
// Test with the provided encoded value
234+
let encoded = "ChAyNTAwMDAwMDAwMDAwMDAw".to_string();
235+
236+
let result = extract_dynamic_gas_price(encoded);
237+
238+
assert!(result.is_ok());
239+
let gas_price = result.unwrap();
240+
241+
assert_eq!(gas_price, 0.0025);
242+
}
243+
244+
#[test]
245+
fn test_extract_dynamic_gas_price_cosmos_evm() {
246+
// Test with the provided encoded value
247+
let encoded = "ChExMDAwMDAwMDAwMDAwMDAwMA==".to_string();
248+
249+
let result = extract_dynamic_gas_price(encoded);
250+
251+
assert!(result.is_ok());
252+
let gas_price = result.unwrap();
253+
254+
assert_eq!(gas_price, 0.01);
255+
}
256+
}

crates/relayer/src/chain/cosmos/gas.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,18 @@ pub async fn dynamic_gas_price(
4343
rpc_address: &Url,
4444
) -> GasPrice {
4545
if config.dynamic_gas_price.enabled {
46-
let dynamic_gas_price = query_eip_base_fee(rpc_address, &config.gas_price.denom, chain_id)
47-
.await
48-
.map(|base_fee| base_fee * config.dynamic_gas_price.multiplier)
49-
.map(|new_price| GasPrice {
50-
price: new_price,
51-
denom: config.gas_price.denom.clone(),
52-
});
46+
let dynamic_gas_price = query_eip_base_fee(
47+
rpc_address,
48+
&config.gas_price.denom,
49+
&config.dynamic_gas_price.r#type,
50+
chain_id,
51+
)
52+
.await
53+
.map(|base_fee| base_fee * config.dynamic_gas_price.multiplier)
54+
.map(|new_price| GasPrice {
55+
price: new_price,
56+
denom: config.gas_price.denom.clone(),
57+
});
5358

5459
let dynamic_gas_price = match dynamic_gas_price {
5560
Ok(dynamic_gas_price) => {

crates/relayer/src/config/dynamic_gas.rs

Lines changed: 95 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use core::fmt;
12
use serde::de::Error as DeserializeError;
23
use serde::de::Unexpected;
34
use serde::Deserialize;
@@ -15,31 +16,60 @@ flex_error::define_error! {
1516
}
1617
}
1718

19+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
20+
#[serde(rename_all = "snake_case")]
21+
pub enum DynamicGasType {
22+
SkipFeeMarket,
23+
Osmosis,
24+
CosmosEvm,
25+
}
26+
27+
impl fmt::Display for DynamicGasType {
28+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29+
match self {
30+
DynamicGasType::SkipFeeMarket => write!(f, "SkipFeeMarket"),
31+
DynamicGasType::Osmosis => write!(f, "Osmosis"),
32+
DynamicGasType::CosmosEvm => write!(f, "CosmosEVM"),
33+
}
34+
}
35+
}
36+
1837
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize)]
1938
pub struct DynamicGasPrice {
2039
pub enabled: bool,
2140
pub multiplier: f64,
2241
pub max: f64,
42+
pub r#type: Option<DynamicGasType>,
2343
}
2444

2545
impl DynamicGasPrice {
2646
const DEFAULT_MULTIPLIER: f64 = 1.1;
2747
const DEFAULT_MAX: f64 = 0.6;
2848
const MIN_MULTIPLIER: f64 = 1.0;
2949

30-
pub fn enabled(multiplier: f64, max: f64) -> Result<Self, Error> {
31-
Self::new(true, multiplier, max)
50+
pub fn enabled(
51+
multiplier: f64,
52+
max: f64,
53+
r#type: Option<DynamicGasType>,
54+
) -> Result<Self, Error> {
55+
Self::new(true, multiplier, max, r#type)
3256
}
3357

3458
pub fn disabled() -> Self {
3559
Self {
3660
enabled: false,
3761
multiplier: Self::DEFAULT_MULTIPLIER,
3862
max: Self::DEFAULT_MAX,
63+
r#type: None,
3964
}
4065
}
4166

42-
pub fn new(enabled: bool, multiplier: f64, max: f64) -> Result<Self, Error> {
67+
pub fn new(
68+
enabled: bool,
69+
multiplier: f64,
70+
max: f64,
71+
r#type: Option<DynamicGasType>,
72+
) -> Result<Self, Error> {
4373
if multiplier < Self::MIN_MULTIPLIER {
4474
return Err(Error::multiplier_too_small(multiplier));
4575
}
@@ -48,15 +78,22 @@ impl DynamicGasPrice {
4878
enabled,
4979
multiplier,
5080
max,
81+
r#type,
5182
})
5283
}
5384

5485
// Unsafe GasMultiplier used for test cases only.
55-
pub fn unsafe_new(enabled: bool, multiplier: f64, max: f64) -> Self {
86+
pub fn unsafe_new(
87+
enabled: bool,
88+
multiplier: f64,
89+
max: f64,
90+
r#type: Option<DynamicGasType>,
91+
) -> Self {
5692
Self {
5793
enabled,
5894
multiplier,
5995
max,
96+
r#type,
6097
}
6198
}
6299
}
@@ -77,15 +114,17 @@ impl<'de> Deserialize<'de> for DynamicGasPrice {
77114
enabled: bool,
78115
multiplier: f64,
79116
max: f64,
117+
r#type: Option<DynamicGasType>,
80118
}
81119

82120
let DynGas {
83121
enabled,
84122
multiplier,
85123
max,
124+
r#type,
86125
} = DynGas::deserialize(deserializer)?;
87126

88-
DynamicGasPrice::new(enabled, multiplier, max).map_err(|e| match e.detail() {
127+
DynamicGasPrice::new(enabled, multiplier, max, r#type).map_err(|e| match e.detail() {
89128
ErrorDetail::MultiplierTooSmall(_) => D::Error::invalid_value(
90129
Unexpected::Float(multiplier),
91130
&format!(
@@ -126,7 +165,7 @@ mod tests {
126165

127166
#[test]
128167
fn safe_gas_multiplier() {
129-
let dynamic_gas = DynamicGasPrice::new(true, 0.6, 0.6);
168+
let dynamic_gas = DynamicGasPrice::new(true, 0.6, 0.6, None);
130169
assert!(
131170
dynamic_gas.is_err(),
132171
"Gas multiplier should be an error if value is lower than 1.0: {dynamic_gas:?}"
@@ -135,8 +174,57 @@ mod tests {
135174

136175
#[test]
137176
fn unsafe_gas_multiplier() {
138-
let dynamic_gas = DynamicGasPrice::unsafe_new(true, 0.6, 0.4);
177+
let dynamic_gas = DynamicGasPrice::unsafe_new(true, 0.6, 0.4, None);
139178
assert_eq!(dynamic_gas.multiplier, 0.6);
140179
assert_eq!(dynamic_gas.max, 0.4);
141180
}
181+
182+
#[test]
183+
fn parse_valid_dynamic_gas_type() {
184+
#[derive(Debug, Deserialize)]
185+
struct DummyConfig {
186+
dynamic_gas: DynamicGasPrice,
187+
}
188+
let config: DummyConfig = toml::from_str(
189+
"dynamic_gas = { enabled = true, multiplier = 1.1, max = 0.6, type = 'skip_fee_market' }",
190+
)
191+
.unwrap();
192+
193+
assert_eq!(config.dynamic_gas.multiplier, 1.1);
194+
assert_eq!(config.dynamic_gas.max, 0.6);
195+
assert_eq!(
196+
config.dynamic_gas.r#type,
197+
Some(DynamicGasType::SkipFeeMarket)
198+
);
199+
}
200+
201+
#[test]
202+
fn parse_invalid_dynamic_gas_type() {
203+
#[derive(Debug, Deserialize)]
204+
struct DummyConfig {
205+
dynamic_gas: DynamicGasPrice,
206+
}
207+
let err = toml::from_str::<DummyConfig>(
208+
"dynamic_gas = { enabled = true, multiplier = 1.1, max = 0.6, type = 'invalid_type' }",
209+
)
210+
.unwrap_err()
211+
.to_string();
212+
213+
assert!(err.contains("unknown variant `invalid_type`, expected one of `skip_fee_market`, `osmosis`, `cosmos_evm`"));
214+
}
215+
216+
#[test]
217+
fn parse_no_dynamic_gas_type() {
218+
#[derive(Debug, Deserialize)]
219+
struct DummyConfig {
220+
dynamic_gas: DynamicGasPrice,
221+
}
222+
let config: DummyConfig =
223+
toml::from_str("dynamic_gas = { enabled = true, multiplier = 1.1, max = 0.6 }")
224+
.unwrap();
225+
226+
assert_eq!(config.dynamic_gas.multiplier, 1.1);
227+
assert_eq!(config.dynamic_gas.max, 0.6);
228+
assert!(config.dynamic_gas.r#type.is_none());
229+
}
142230
}

0 commit comments

Comments
 (0)