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
Empty file removed .changelog/unreleased/.gitkeep
Empty file.
2 changes: 2 additions & 0 deletions .changelog/unreleased/features/4369-support-cosmos-evm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- missing CosmosEvmDynamicFee for extension_options and dynamic gas price support for cosmos evm
([\#4369](https://github.com/informalsystems/hermes/issues/4369))
14 changes: 12 additions & 2 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,24 @@ gas_multiplier = 1.1
# Query the current gas price from the chain instead of using the static `gas_price` from the config.
# Useful for chains which have [EIP-1559][eip]-like dynamic gas price.
#
# At the moment, only chains which support the `osmosis.txfees.v1beta1.Query/GetEipBaseFee`
# query or have enabled Skip's `x/feemarket` module https://github.com/skip-mev/feemarket
# At the moment, only chains which support the `/cosmos.evm.feemarket.v1.Query/BaseFee` query or
# `osmosis.txfees.v1beta1.Query/GetEipBaseFee` query or have enabled Skip's `x/feemarket` module https://github.com/skip-mev/feemarket
# can be used with dynamic gas price enabled.
#
# The `type` field allows you to specify which query method to use:
# - `skip_fee_market`: Use Skip's feemarket module query at `/feemarket.feemarket.v1.Query/GasPrices`
# - `osmosis`: Use Osmosis EIP-1559 query at `/osmosis.txfees.v1beta1.Query/GetEipBaseFee`
# - `cosmos_evm`: Use Cosmos EVM query at `/cosmos.evm.feemarket.v1.Query/BaseFee`
#
# If `type` is not specified, Hermes will attempt to auto-detect for backwards compatibility:
# - Chains starting with "osmosis" or "osmo-test" will use `osmosis` type
# - All other chains will default to `skip_fee_market` type
#
# See this page in the Hermes guide for more information:
# https://hermes.informal.systems/documentation/configuration/dynamic-gas-fees.html
#
# Default: { enabled = false, multiplier = 1.1, max = 0.6 }
# Example with type specified: { enabled = true, multiplier = 1.1, max = 0.6, type = 'cosmos_evm' }
dynamic_gas_price = { enabled = false, multiplier = 1.1, max = 0.6 }

# Specify how many IBC messages at most to include in a single transaction.
Expand Down
2 changes: 1 addition & 1 deletion crates/relayer-cli/src/chain_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ where

// Use EIP-1559 dynamic gas price for Osmosis
let dynamic_gas_price = if chain_data.chain_id.as_str() == "osmosis-1" {
DynamicGasPrice::unsafe_new(true, 1.1, 0.6)
DynamicGasPrice::unsafe_new(true, 1.1, 0.6, None)
} else {
DynamicGasPrice::disabled()
};
Expand Down
115 changes: 90 additions & 25 deletions crates/relayer/src/chain/cosmos/eip_base_fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,29 @@ use tracing::{debug, trace};
use ibc_proto::cosmos::base::v1beta1::{DecCoin, DecProto};
use ibc_relayer_types::core::ics24_host::identifier::ChainId;

use crate::error::Error;
use crate::{config::dynamic_gas::DynamicGasType, error::Error};

pub async fn query_eip_base_fee(
rpc_address: &Url,
gas_price_denom: &str,
dynamic_gas_type: &Option<DynamicGasType>,
chain_id: &ChainId,
) -> Result<f64, Error> {
debug!("Querying Omosis EIP-1559 base fee from {rpc_address}");

let chain_name = chain_id.name();

let is_osmosis = chain_name.starts_with("osmosis") || chain_name.starts_with("osmo-test");

let url = if is_osmosis {
format!(
"{}abci_query?path=\"/osmosis.txfees.v1beta1.Query/GetEipBaseFee\"",
rpc_address
)
} else {
format!(
"{}abci_query?path=\"/feemarket.feemarket.v1.Query/GasPrices\"&denom={}",
rpc_address, gas_price_denom
)
let dynamic_gas_type = match dynamic_gas_type {
Some(dynamic_gas_type) => dynamic_gas_type,
None => {
// backward compatibility for chains that do not specify dynamic gas type
if chain_id.name().starts_with("osmosis") || chain_id.name().starts_with("osmo-test") {
&DynamicGasType::Osmosis
} else {
&DynamicGasType::SkipFeeMarket
}
}
};

debug!("Querying {dynamic_gas_type} base fee from {rpc_address}");

let url = dynamic_gas_type.get_url(rpc_address, gas_price_denom);
let response = reqwest::get(&url).await.map_err(Error::http_request)?;

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

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

let amount = if is_osmosis {
extract_dynamic_gas_price_osmosis(result.result.response.value)?
} else {
extract_dynamic_gas_price(result.result.response.value)?
};
let amount = dynamic_gas_type.extract_dynamic_gas_price(result.result.response.value)?;

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

Ok(amount)
}

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

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

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

let dec_proto: DecProto = prost::Message::decode(decoded.as_ref())
Expand Down Expand Up @@ -189,3 +183,74 @@ impl fmt::Display for Decimal {
}
}
}

impl DynamicGasType {
fn get_url(&self, rpc_address: &Url, gas_price_denom: &str) -> String {
match self {
DynamicGasType::SkipFeeMarket => format!(
"{}abci_query?path=\"/feemarket.feemarket.v1.Query/GasPrices\"&denom={}",
rpc_address, gas_price_denom
),
DynamicGasType::Osmosis => format!(
"{}abci_query?path=\"/osmosis.txfees.v1beta1.Query/GetEipBaseFee\"",
rpc_address
),
DynamicGasType::CosmosEvm => format!(
"{}abci_query?path=\"/cosmos.evm.feemarket.v1.Query/BaseFee\"",
rpc_address
),
}
}

fn extract_dynamic_gas_price(&self, encoded: String) -> Result<f64, Error> {
match self {
DynamicGasType::SkipFeeMarket => extract_dynamic_gas_price_fee_market(encoded),
DynamicGasType::Osmosis => extract_dynamic_gas_price(encoded),
DynamicGasType::CosmosEvm => extract_dynamic_gas_price(encoded),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_extract_dynamic_gas_price_fee_market() {
// Test with the provided encoded value
let encoded = "ChgKA3VvbRIRMTAwMDAwMDAwMDAwMDAwMDA".to_string();

let result = extract_dynamic_gas_price_fee_market(encoded);

assert!(result.is_ok());
let gas_price = result.unwrap();

assert_eq!(gas_price, 0.01);
}

#[test]
fn test_extract_dynamic_gas_price_osmosis() {
// Test with the provided encoded value
let encoded = "ChAyNTAwMDAwMDAwMDAwMDAw".to_string();

let result = extract_dynamic_gas_price(encoded);

assert!(result.is_ok());
let gas_price = result.unwrap();

assert_eq!(gas_price, 0.0025);
}

#[test]
fn test_extract_dynamic_gas_price_cosmos_evm() {
// Test with the provided encoded value
let encoded = "ChExMDAwMDAwMDAwMDAwMDAwMA==".to_string();

let result = extract_dynamic_gas_price(encoded);

assert!(result.is_ok());
let gas_price = result.unwrap();

assert_eq!(gas_price, 0.01);
}
}
19 changes: 12 additions & 7 deletions crates/relayer/src/chain/cosmos/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,18 @@ pub async fn dynamic_gas_price(
rpc_address: &Url,
) -> GasPrice {
if config.dynamic_gas_price.enabled {
let dynamic_gas_price = query_eip_base_fee(rpc_address, &config.gas_price.denom, chain_id)
.await
.map(|base_fee| base_fee * config.dynamic_gas_price.multiplier)
.map(|new_price| GasPrice {
price: new_price,
denom: config.gas_price.denom.clone(),
});
let dynamic_gas_price = query_eip_base_fee(
rpc_address,
&config.gas_price.denom,
&config.dynamic_gas_price.r#type,
chain_id,
)
.await
.map(|base_fee| base_fee * config.dynamic_gas_price.multiplier)
.map(|new_price| GasPrice {
price: new_price,
denom: config.gas_price.denom.clone(),
});

let dynamic_gas_price = match dynamic_gas_price {
Ok(dynamic_gas_price) => {
Expand Down
24 changes: 23 additions & 1 deletion crates/relayer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ pub fn parse_gas_prices(prices: String) -> Vec<GasPrice> {
)]
pub enum ExtensionOption {
EthermintDynamicFee(String),
CosmosEvmDynamicFee(String),
CosmosEvmDynamicFeeV1(String),
}

impl ExtensionOption {
Expand All @@ -129,7 +131,15 @@ impl ExtensionOption {
Self::EthermintDynamicFee(max_priority_price) => ExtensionOptionDynamicFeeTx {
max_priority_price: max_priority_price.into(),
}
.to_any(),
.to_any("/ethermint.types.v1.ExtensionOptionDynamicFeeTx"),
Self::CosmosEvmDynamicFee(max_priority_price) => ExtensionOptionDynamicFeeTx {
max_priority_price: max_priority_price.into(),
}
.to_any("/cosmos.evm.types.v1.ExtensionOptionDynamicFeeTx"),
Self::CosmosEvmDynamicFeeV1(max_priority_price) => ExtensionOptionDynamicFeeTx {
max_priority_price: max_priority_price.into(),
}
.to_any("/cosmos.evm.ante.v1.ExtensionOptionDynamicFeeTx"),
}
}
}
Expand All @@ -143,6 +153,18 @@ impl Display for ExtensionOption {
"EthermintDynamicFee(max_priority_price: {max_priority_price})"
)
}
Self::CosmosEvmDynamicFee(max_priority_price) => {
write!(
f,
"CosmosEvmDynamicFee(max_priority_price: {max_priority_price})"
)
}
Self::CosmosEvmDynamicFeeV1(max_priority_price) => {
write!(
f,
"CosmosEvmDynamicFeeV1(max_priority_price: {max_priority_price})"
)
}
}
}
}
Expand Down
Loading