diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index e41a09ffd..bd0d1d21a 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,6 @@ about: Suggest an idea for this project title: '' labels: '' assignees: '' - --- ## Problem diff --git a/rust/Cargo.lock b/rust/Cargo.lock index b922edc5d..741b6acaa 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -584,6 +584,19 @@ dependencies = [ "num-traits", ] +[[package]] +name = "bigdecimal" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06619be423ea5bb86c95f087d5707942791a08a85530df0db2209a3ecfb8bc9" +dependencies = [ + "autocfg", + "libm", + "num-bigint 0.4.4", + "num-integer", + "num-traits", +] + [[package]] name = "bincode" version = "1.3.3" @@ -750,11 +763,11 @@ dependencies = [ [[package]] name = "borsh" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9897ef0f1bd2362169de6d7e436ea2237dc1085d7d1e4db75f4be34d86f309d1" +checksum = "26d4d6dafc1a3bb54687538972158f07b2c948bc57d5890df22c0739098b3028" dependencies = [ - "borsh-derive 1.2.1", + "borsh-derive 1.3.0", "cfg_aliases", ] @@ -773,9 +786,9 @@ dependencies = [ [[package]] name = "borsh-derive" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478b41ff04256c5c8330f3dfdaaae2a5cc976a8e75088bafa4625b0d0208de8c" +checksum = "bf4918709cc4dd777ad2b6303ed03cb37f3ca0ccede8c1b0d28ac6db8f4710e0" dependencies = [ "once_cell", "proc-macro-crate 2.0.0", @@ -1339,9 +1352,9 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "const-oid" -version = "0.9.5" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" @@ -1954,7 +1967,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" dependencies = [ - "const-oid 0.9.5", + "const-oid 0.9.6", "zeroize", ] @@ -1964,7 +1977,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ - "const-oid 0.9.5", + "const-oid 0.9.6", "zeroize", ] @@ -2107,7 +2120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.5", + "const-oid 0.9.6", "crypto-common", "subtle", ] @@ -3854,11 +3867,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3925,9 +3938,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -3940,7 +3953,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.5", "tokio", "tower-service", "tracing", @@ -4079,6 +4092,7 @@ version = "0.1.0" dependencies = [ "async-trait", "auto_impl 1.1.0", + "bigdecimal 0.4.2", "borsh 0.9.3", "bs58 0.5.0", "bytes", @@ -4958,6 +4972,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libredox" version = "0.0.1" @@ -6698,6 +6718,7 @@ dependencies = [ "itertools 0.11.0", "num-derive 0.4.1", "num-traits", + "once_cell", "prometheus", "regex", "reqwest", @@ -6722,9 +6743,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "async-compression", "base64 0.21.5", @@ -6828,12 +6849,13 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.42" +version = "0.7.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +checksum = "527a97cdfef66f65998b5f3b637c26f5a5ec09cc52a3f9932313ac645f4190f5" dependencies = [ "bitvec 1.0.1", "bytecheck", + "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -6845,9 +6867,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.42" +version = "0.7.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +checksum = "b5c462a1328c8e67e4d6dbad1eb0355dd43e8ab432c6e227a43657f16ade5033" dependencies = [ "proc-macro2 1.0.70", "quote 1.0.33", @@ -6917,6 +6939,7 @@ dependencies = [ "hex 0.4.3", "hpl-interface", "hyperlane-core", + "hyperlane-cosmos", "jobserver", "k256 0.13.2", "macro_rules_attribute", @@ -7061,7 +7084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06676aec5ccb8fc1da723cc8c0f9a46549f21ebb8753d3915c6c41db1e7f1dc4" dependencies = [ "arrayvec", - "borsh 1.2.1", + "borsh 1.3.0", "bytes", "num-traits", "rand 0.8.5", @@ -7419,7 +7442,7 @@ checksum = "fade86e8d41fd1a4721f84cb834f4ca2783f973cc30e6212b7fafc134f169214" dependencies = [ "async-stream", "async-trait", - "bigdecimal", + "bigdecimal 0.3.1", "chrono", "futures", "log", @@ -7491,7 +7514,7 @@ version = "0.28.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbab99b8cd878ab7786157b7eb8df96333a6807cc6e45e8888c85b51534b401a" dependencies = [ - "bigdecimal", + "bigdecimal 0.3.1", "chrono", "rust_decimal", "sea-query-derive", @@ -7506,7 +7529,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cea85029985b40dfbf18318d85fe985c04db7c1b4e5e8e0a0a0cdff5f1e30f9" dependencies = [ - "bigdecimal", + "bigdecimal 0.3.1", "chrono", "rust_decimal", "sea-query", @@ -8974,7 +8997,7 @@ dependencies = [ "ahash 0.7.7", "atoi", "base64 0.13.1", - "bigdecimal", + "bigdecimal 0.3.1", "bitflags 1.3.2", "byteorder", "bytes", @@ -9477,18 +9500,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2 1.0.70", "quote 1.0.33", @@ -9507,9 +9530,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" dependencies = [ "deranged", "itoa", @@ -9527,9 +9550,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" dependencies = [ "time-core", ] @@ -10689,9 +10712,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.28" +version = "0.5.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c830786f7720c2fd27a1a0e27a709dbd3c4d009b56d098fc742d4f4eab91fe2" +checksum = "9b5c3db89721d50d0e2a673f5043fc4722f76dcc352d7b1ab8b8288bed4ed2c5" dependencies = [ "memchr", ] @@ -10789,18 +10812,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.30" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "306dca4455518f1f31635ec308b6b3e4eb1b11758cefafc782827d0aa7acb5c7" +checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.30" +version = "0.7.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be912bf68235a88fbefd1b73415cb218405958d1655b2ece9035a19920bdf6ba" +checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" dependencies = [ "proc-macro2 1.0.70", "quote 1.0.33", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 478d8e352..ae5fd5e37 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -55,6 +55,7 @@ async-trait = "0.1" auto_impl = "1.0" backtrace = "0.3" base64 = "0.21.2" +bigdecimal = "0.4.2" bincode = "1.3" borsh = "0.9" bs58 = "0.5.0" diff --git a/rust/agents/relayer/Cargo.toml b/rust/agents/relayer/Cargo.toml index 7eb5b83fe..0bd569797 100644 --- a/rust/agents/relayer/Cargo.toml +++ b/rust/agents/relayer/Cargo.toml @@ -39,6 +39,7 @@ hyperlane-base = { path = "../../hyperlane-base" } hyperlane-ethereum = { path = "../../chains/hyperlane-ethereum" } [dev-dependencies] +once_cell.workspace = true tokio-test.workspace = true hyperlane-test = { path = "../../hyperlane-test" } hyperlane-base = { path = "../../hyperlane-base", features = ["test-utils"] } diff --git a/rust/agents/relayer/src/msg/gas_payment/mod.rs b/rust/agents/relayer/src/msg/gas_payment/mod.rs index 50f010d24..cd9dd61c0 100644 --- a/rust/agents/relayer/src/msg/gas_payment/mod.rs +++ b/rust/agents/relayer/src/msg/gas_payment/mod.rs @@ -4,8 +4,8 @@ use async_trait::async_trait; use eyre::Result; use hyperlane_base::db::HyperlaneRocksDB; use hyperlane_core::{ - GasPaymentKey, HyperlaneMessage, InterchainGasExpenditure, InterchainGasPayment, - TxCostEstimate, TxOutcome, U256, + FixedPointNumber, GasPaymentKey, HyperlaneMessage, InterchainGasExpenditure, + InterchainGasPayment, TxCostEstimate, TxOutcome, U256, }; use tracing::{debug, error, trace}; @@ -135,7 +135,8 @@ impl GasPaymentEnforcer { self.db.process_gas_expenditure(InterchainGasExpenditure { message_id: message.id(), gas_used: outcome.gas_used, - tokens_used: outcome.gas_used * outcome.gas_price, + tokens_used: (FixedPointNumber::try_from(outcome.gas_used)? * outcome.gas_price) + .try_into()?, })?; Ok(()) } diff --git a/rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs b/rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs index 23f329d09..ea2c4d23d 100644 --- a/rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs +++ b/rust/agents/relayer/src/msg/gas_payment/policies/minimum.rs @@ -59,7 +59,7 @@ async fn test_gas_payment_policy_minimum() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100000u32), + gas_price: U256::from(100000u32).try_into().unwrap(), l2_gas_limit: None, }, ) @@ -83,7 +83,7 @@ async fn test_gas_payment_policy_minimum() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100001u32), + gas_price: U256::from(100001u32).try_into().unwrap(), l2_gas_limit: None, }, ) @@ -101,7 +101,7 @@ async fn test_gas_payment_policy_minimum() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100001u32), + gas_price: U256::from(100001u32).try_into().unwrap(), l2_gas_limit: Some(U256::from(22222u32)), }, ) diff --git a/rust/agents/relayer/src/msg/gas_payment/policies/none.rs b/rust/agents/relayer/src/msg/gas_payment/policies/none.rs index 9ce88a39c..4d15359f4 100644 --- a/rust/agents/relayer/src/msg/gas_payment/policies/none.rs +++ b/rust/agents/relayer/src/msg/gas_payment/policies/none.rs @@ -52,7 +52,7 @@ async fn test_gas_payment_policy_none() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100001u32), + gas_price: U256::from(100001u32).try_into().unwrap(), l2_gas_limit: None, }, ) @@ -70,7 +70,7 @@ async fn test_gas_payment_policy_none() { ¤t_expenditure, &TxCostEstimate { gas_limit: U256::from(100000u32), - gas_price: U256::from(100001u32), + gas_price: U256::from(100001u32).try_into().unwrap(), l2_gas_limit: Some(U256::from(22222u32)), }, ) diff --git a/rust/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs b/rust/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs index cdc017def..af71d6330 100644 --- a/rust/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs +++ b/rust/agents/relayer/src/msg/gas_payment/policies/on_chain_fee_quoting.rs @@ -64,6 +64,7 @@ impl GasPaymentPolicy for GasPaymentPolicyOnChainFeeQuoting { #[cfg(test)] mod test { use hyperlane_core::H256; + use once_cell::sync::Lazy; use super::*; @@ -85,11 +86,11 @@ mod test { } const MIN: U256 = U256([1000, 0, 0, 0]); - const COST_ESTIMATE: TxCostEstimate = TxCostEstimate { + static COST_ESTIMATE: Lazy = Lazy::new(|| TxCostEstimate { gas_limit: U256([2000, 0, 0, 0]), // MIN * 2 - gas_price: U256([100001, 0, 0, 0]), + gas_price: U256([100001, 0, 0, 0]).try_into().unwrap(), l2_gas_limit: None, - }; + }); #[test] fn ensure_little_endian() { @@ -203,7 +204,7 @@ mod test { let tx_cost_estimate = TxCostEstimate { gas_limit: MIN * 100, // Large gas limit - gas_price: COST_ESTIMATE.gas_price, + gas_price: COST_ESTIMATE.gas_price.clone().try_into().unwrap(), l2_gas_limit: Some(MIN * 2), }; @@ -217,7 +218,7 @@ mod test { ¤t_expenditure(0), &TxCostEstimate { l2_gas_limit: None, - ..tx_cost_estimate + ..tx_cost_estimate.clone() } ) .await diff --git a/rust/agents/relayer/src/msg/pending_message.rs b/rust/agents/relayer/src/msg/pending_message.rs index 860c1017a..9836a47d7 100644 --- a/rust/agents/relayer/src/msg/pending_message.rs +++ b/rust/agents/relayer/src/msg/pending_message.rs @@ -238,7 +238,7 @@ impl PendingOperation for PendingMessage { "processing message" ); - op_try!(critical: self.ctx.origin_gas_payment_enforcer.record_tx_outcome(&self.message, tx_outcome), "recording tx outcome"); + op_try!(critical: self.ctx.origin_gas_payment_enforcer.record_tx_outcome(&self.message, tx_outcome.clone()), "recording tx outcome"); if tx_outcome.executed { info!( txid=?tx_outcome.transaction_id, diff --git a/rust/chains/hyperlane-cosmos/src/mailbox.rs b/rust/chains/hyperlane-cosmos/src/mailbox.rs index 4aafd29c8..0b3927cda 100644 --- a/rust/chains/hyperlane-cosmos/src/mailbox.rs +++ b/rust/chains/hyperlane-cosmos/src/mailbox.rs @@ -215,7 +215,7 @@ impl Mailbox for CosmosMailbox { let result = TxCostEstimate { gas_limit: gas_limit.into(), - gas_price: U256::from(2500), + gas_price: self.provider.grpc().gas_price(), l2_gas_limit: None, }; diff --git a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs index fd85287d0..9653020ad 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/grpc.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/grpc.rs @@ -22,19 +22,18 @@ use cosmrs::{ traits::Message, }, tx::{self, Fee, MessageExt, SignDoc, SignerInfo}, - Amount, Coin, + Coin, +}; +use hyperlane_core::{ + ChainCommunicationError, ChainResult, ContractLocator, FixedPointNumber, U256, }; -use hyperlane_core::{ChainCommunicationError, ChainResult, ContractLocator, U256}; use serde::Serialize; use tonic::transport::{Channel, Endpoint}; -use crate::address::CosmosAddress; use crate::HyperlaneCosmosError; +use crate::{address::CosmosAddress, CosmosAmount}; use crate::{signers::Signer, ConnectionConf}; -/// The gas price to use for transactions. -/// TODO: is there a nice way to get a suggested price dynamically? -const DEFAULT_GAS_PRICE: f64 = 0.05; /// A multiplier applied to a simulated transaction's gas usage to /// calculate the estimated gas. const GAS_ESTIMATE_MULTIPLIER: f64 = 1.25; @@ -91,12 +90,14 @@ pub struct WasmGrpcProvider { /// GRPC Channel that can be cheaply cloned. /// See `` channel: Channel, + gas_price: CosmosAmount, } impl WasmGrpcProvider { /// Create new CosmWasm GRPC Provider. pub fn new( conf: ConnectionConf, + gas_price: CosmosAmount, locator: Option, signer: Option, ) -> ChainResult { @@ -112,6 +113,7 @@ impl WasmGrpcProvider { contract_address, signer, channel, + gas_price, }) } @@ -121,9 +123,12 @@ impl WasmGrpcProvider { .as_ref() .ok_or(ChainCommunicationError::SignerUnavailable) } -} -impl WasmGrpcProvider { + /// Get the gas price + pub fn gas_price(&self) -> FixedPointNumber { + self.gas_price.amount.clone() + } + /// Generates an unsigned SignDoc for a transaction. async fn generate_unsigned_sign_doc( &self, @@ -145,9 +150,13 @@ impl WasmGrpcProvider { ); let signer_info = SignerInfo::single_direct(Some(signer.public_key), account_info.sequence); + let amount: u128 = (FixedPointNumber::from(gas_limit) * self.gas_price()) + .ceil_to_integer() + .try_into()?; let auth_info = signer_info.auth_info(Fee::from_amount_and_gas( Coin::new( - Amount::from((gas_limit as f64 * DEFAULT_GAS_PRICE) as u64), + // The fee to pay is the gas limit * the gas price + amount, self.conf.get_canonical_asset().as_str(), ) .map_err(Into::::into)?, diff --git a/rust/chains/hyperlane-cosmos/src/providers/mod.rs b/rust/chains/hyperlane-cosmos/src/providers/mod.rs index 973a886a3..21216f087 100644 --- a/rust/chains/hyperlane-cosmos/src/providers/mod.rs +++ b/rust/chains/hyperlane-cosmos/src/providers/mod.rs @@ -5,7 +5,7 @@ use hyperlane_core::{ }; use tendermint_rpc::{client::CompatMode, HttpClient}; -use crate::{ConnectionConf, HyperlaneCosmosError, Signer}; +use crate::{ConnectionConf, CosmosAmount, HyperlaneCosmosError, Signer}; use self::grpc::WasmGrpcProvider; @@ -31,7 +31,8 @@ impl CosmosProvider { locator: Option, signer: Option, ) -> ChainResult { - let grpc_client = WasmGrpcProvider::new(conf.clone(), locator, signer)?; + let gas_price = CosmosAmount::try_from(conf.get_minimum_gas_price().clone())?; + let grpc_client = WasmGrpcProvider::new(conf.clone(), gas_price.clone(), locator, signer)?; let rpc_client = HttpClient::builder( conf.get_rpc_url() .parse() diff --git a/rust/chains/hyperlane-cosmos/src/trait_builder.rs b/rust/chains/hyperlane-cosmos/src/trait_builder.rs index 8629970bd..81c16b784 100644 --- a/rust/chains/hyperlane-cosmos/src/trait_builder.rs +++ b/rust/chains/hyperlane-cosmos/src/trait_builder.rs @@ -1,3 +1,8 @@ +use std::str::FromStr; + +use derive_new::new; +use hyperlane_core::{ChainCommunicationError, FixedPointNumber}; + /// Cosmos connection configuration #[derive(Debug, Clone)] pub struct ConnectionConf { @@ -11,6 +16,38 @@ pub struct ConnectionConf { prefix: String, /// Canoncial Assets Denom canonical_asset: String, + /// The gas price set by the cosmos-sdk validator. Note that this represents the + /// minimum price set by the validator. + /// More details here: https://docs.cosmos.network/main/learn/beginner/gas-fees#antehandler + gas_price: RawCosmosAmount, +} + +/// Untyped cosmos amount +#[derive(serde::Serialize, serde::Deserialize, new, Clone, Debug)] +pub struct RawCosmosAmount { + /// Coin denom (e.g. `untrn`) + pub denom: String, + /// Amount in the given denom + pub amount: String, +} + +/// Typed cosmos amount +#[derive(Clone, Debug)] +pub struct CosmosAmount { + /// Coin denom (e.g. `untrn`) + pub denom: String, + /// Amount in the given denom + pub amount: FixedPointNumber, +} + +impl TryFrom for CosmosAmount { + type Error = ChainCommunicationError; + fn try_from(raw: RawCosmosAmount) -> Result { + Ok(Self { + denom: raw.denom, + amount: FixedPointNumber::from_str(&raw.amount)?, + }) + } } /// An error type when parsing a connection configuration. @@ -59,6 +96,11 @@ impl ConnectionConf { self.canonical_asset.clone() } + /// Get the minimum gas price + pub fn get_minimum_gas_price(&self) -> RawCosmosAmount { + self.gas_price.clone() + } + /// Create a new connection configuration pub fn new( grpc_url: String, @@ -66,6 +108,7 @@ impl ConnectionConf { chain_id: String, prefix: String, canonical_asset: String, + minimum_gas_price: RawCosmosAmount, ) -> Self { Self { grpc_url, @@ -73,6 +116,7 @@ impl ConnectionConf { chain_id, prefix, canonical_asset, + gas_price: minimum_gas_price, } } } diff --git a/rust/chains/hyperlane-cosmos/src/types.rs b/rust/chains/hyperlane-cosmos/src/types.rs index 264ae8791..d7647e7dd 100644 --- a/rust/chains/hyperlane-cosmos/src/types.rs +++ b/rust/chains/hyperlane-cosmos/src/types.rs @@ -29,6 +29,6 @@ pub fn tx_response_to_outcome(response: TxResponse) -> ChainResult { transaction_id: H256::from_slice(hex::decode(response.txhash)?.as_slice()).into(), executed: response.code == 0, gas_used: U256::from(response.gas_used), - gas_price: U256::one(), + gas_price: U256::one().try_into()?, }) } diff --git a/rust/chains/hyperlane-ethereum/src/mailbox.rs b/rust/chains/hyperlane-ethereum/src/mailbox.rs index 60bf35c9d..4c566abbd 100644 --- a/rust/chains/hyperlane-ethereum/src/mailbox.rs +++ b/rust/chains/hyperlane-ethereum/src/mailbox.rs @@ -373,15 +373,16 @@ where None }; - let gas_price = self + let gas_price: U256 = self .provider .get_gas_price() .await - .map_err(ChainCommunicationError::from_other)?; + .map_err(ChainCommunicationError::from_other)? + .into(); Ok(TxCostEstimate { gas_limit: gas_limit.into(), - gas_price: gas_price.into(), + gas_price: gas_price.try_into()?, l2_gas_limit: l2_gas_limit.map(|v| v.into()), }) } @@ -484,7 +485,7 @@ mod test { tx_cost_estimate, TxCostEstimate { gas_limit: estimated_gas_limit, - gas_price, + gas_price: gas_price.try_into().unwrap(), l2_gas_limit: Some(l2_gas_limit), }, ); diff --git a/rust/chains/hyperlane-sealevel/src/mailbox.rs b/rust/chains/hyperlane-sealevel/src/mailbox.rs index 62fb1a2f1..f7a22d89e 100644 --- a/rust/chains/hyperlane-sealevel/src/mailbox.rs +++ b/rust/chains/hyperlane-sealevel/src/mailbox.rs @@ -9,9 +9,9 @@ use tracing::{debug, info, instrument, warn}; use hyperlane_core::{ accumulator::incremental::IncrementalMerkle, ChainCommunicationError, ChainResult, Checkpoint, - ContractLocator, Decode as _, Encode as _, HyperlaneAbi, HyperlaneChain, HyperlaneContract, - HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexer, LogMeta, Mailbox, - MerkleTreeHook, SequenceIndexer, TxCostEstimate, TxOutcome, H256, H512, U256, + ContractLocator, Decode as _, Encode as _, FixedPointNumber, HyperlaneAbi, HyperlaneChain, + HyperlaneContract, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider, Indexer, LogMeta, + Mailbox, MerkleTreeHook, SequenceIndexer, TxCostEstimate, TxOutcome, H256, H512, U256, }; use hyperlane_sealevel_interchain_security_module_interface::{ InterchainSecurityModuleInstruction, VerifyInstruction, @@ -470,7 +470,7 @@ impl Mailbox for SealevelMailbox { transaction_id: txid, executed, // TODO use correct data upon integrating IGP support - gas_price: U256::zero(), + gas_price: U256::zero().try_into()?, gas_used: U256::zero(), }) } @@ -484,7 +484,7 @@ impl Mailbox for SealevelMailbox { // TODO use correct data upon integrating IGP support Ok(TxCostEstimate { gas_limit: U256::zero(), - gas_price: U256::zero(), + gas_price: FixedPointNumber::zero(), l2_gas_limit: None, }) } diff --git a/rust/chains/hyperlane-sealevel/src/validator_announce.rs b/rust/chains/hyperlane-sealevel/src/validator_announce.rs index e7fc8dca8..c6ce2233d 100644 --- a/rust/chains/hyperlane-sealevel/src/validator_announce.rs +++ b/rust/chains/hyperlane-sealevel/src/validator_announce.rs @@ -128,7 +128,7 @@ impl ValidatorAnnounce for SealevelValidatorAnnounce { transaction_id: H512::zero(), executed: false, gas_used: U256::zero(), - gas_price: U256::zero(), + gas_price: U256::zero().try_into()?, }) } } diff --git a/rust/config/mainnet3_config.json b/rust/config/mainnet3_config.json index 70f804cc9..d18839dc8 100644 --- a/rust/config/mainnet3_config.json +++ b/rust/config/mainnet3_config.json @@ -432,6 +432,10 @@ "grpcUrl": "https://grpc-kralum.neutron-1.neutron.org:80", "canonicalAsset": "untrn", "prefix": "neutron", + "gasPrice": { + "amount": "0.5", + "denom": "untrn" + }, "index": { "from": 4000000, "chunk": 100000 diff --git a/rust/hyperlane-base/src/settings/parser/connection_parser.rs b/rust/hyperlane-base/src/settings/parser/connection_parser.rs index b7a0a1244..55cce2be3 100644 --- a/rust/hyperlane-base/src/settings/parser/connection_parser.rs +++ b/rust/hyperlane-base/src/settings/parser/connection_parser.rs @@ -6,7 +6,7 @@ use url::Url; use crate::settings::envs::*; use crate::settings::ChainConnectionConf; -use super::ValueParser; +use super::{parse_cosmos_gas_price, ValueParser}; pub fn build_ethereum_connection_conf( rpcs: &[Url], @@ -94,6 +94,12 @@ pub fn build_cosmos_connection_conf( None }; + let gas_price = chain + .chain(err) + .get_opt_key("gasPrice") + .and_then(parse_cosmos_gas_price) + .end(); + if !local_err.is_ok() { err.merge(local_err); None @@ -104,6 +110,7 @@ pub fn build_cosmos_connection_conf( chain_id.unwrap().to_string(), prefix.unwrap().to_string(), canonical_asset.unwrap(), + gas_price.unwrap(), ))) } } diff --git a/rust/hyperlane-base/src/settings/parser/mod.rs b/rust/hyperlane-base/src/settings/parser/mod.rs index 76ed1d594..aad2e8735 100644 --- a/rust/hyperlane-base/src/settings/parser/mod.rs +++ b/rust/hyperlane-base/src/settings/parser/mod.rs @@ -11,6 +11,7 @@ use std::{ use convert_case::{Case, Casing}; use eyre::{eyre, Context}; +use h_cosmos::RawCosmosAmount; use hyperlane_core::{ cfg_unwrap_all, config::*, HyperlaneDomain, HyperlaneDomainProtocol, IndexMode, }; @@ -398,3 +399,22 @@ pub fn recase_json_value(mut val: Value, case: Case) -> Value { } val } + +/// Expects AgentSigner. +fn parse_cosmos_gas_price(gas_price: ValueParser) -> ConfigResult { + let mut err = ConfigParsingError::default(); + + let amount = gas_price + .chain(&mut err) + .get_opt_key("amount") + .parse_string() + .end(); + + let denom = gas_price + .chain(&mut err) + .get_opt_key("denom") + .parse_string() + .end(); + cfg_unwrap_all!(&gas_price.cwp, err: [denom, amount]); + err.into_result(RawCosmosAmount::new(denom.to_owned(), amount.to_owned())) +} diff --git a/rust/hyperlane-core/Cargo.toml b/rust/hyperlane-core/Cargo.toml index bbb0835b8..d5c93b937 100644 --- a/rust/hyperlane-core/Cargo.toml +++ b/rust/hyperlane-core/Cargo.toml @@ -12,6 +12,7 @@ version = { workspace = true } [dependencies] async-trait.workspace = true auto_impl.workspace = true +bigdecimal.workspace = true borsh.workspace = true bs58.workspace = true bytes = { workspace = true, features = ["serde"] } diff --git a/rust/hyperlane-core/src/error.rs b/rust/hyperlane-core/src/error.rs index ae7c5f8b5..b3fb6ce35 100644 --- a/rust/hyperlane-core/src/error.rs +++ b/rust/hyperlane-core/src/error.rs @@ -3,6 +3,8 @@ use std::error::Error as StdError; use std::fmt::{Debug, Display, Formatter}; use std::ops::Deref; +use bigdecimal::ParseBigDecimalError; + use crate::config::StrOrIntParseError; use std::string::FromUtf8Error; @@ -123,6 +125,9 @@ pub enum ChainCommunicationError { /// Primitive type error #[error(transparent)] PrimitiveTypeError(#[from] PrimitiveTypeError), + /// Big decimal parsing error + #[error(transparent)] + ParseBigDecimalError(#[from] ParseBigDecimalError), } impl ChainCommunicationError { diff --git a/rust/hyperlane-core/src/traits/mod.rs b/rust/hyperlane-core/src/traits/mod.rs index ec2c9e04f..1b8259989 100644 --- a/rust/hyperlane-core/src/traits/mod.rs +++ b/rust/hyperlane-core/src/traits/mod.rs @@ -15,6 +15,8 @@ pub use routing_ism::*; pub use signing::*; pub use validator_announce::*; +use crate::{FixedPointNumber, U256}; + mod aggregation_ism; mod ccip_read_ism; mod cursor; @@ -33,7 +35,7 @@ mod signing; mod validator_announce; /// The result of a transaction -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct TxOutcome { /// The transaction identifier/hash pub transaction_id: crate::H512, @@ -42,7 +44,7 @@ pub struct TxOutcome { /// Amount of gas used on this transaction. pub gas_used: crate::U256, /// Price paid for the gas - pub gas_price: crate::U256, + pub gas_price: FixedPointNumber, // TODO: more? What can be abstracted across all chains? } @@ -55,8 +57,8 @@ impl From for TxOutcome { gas_used: t.gas_used.map(Into::into).unwrap_or(crate::U256::zero()), gas_price: t .effective_gas_price - .map(Into::into) - .unwrap_or(crate::U256::zero()), + .and_then(|price| U256::from(price).try_into().ok()) + .unwrap_or(FixedPointNumber::zero()), } } } diff --git a/rust/hyperlane-core/src/types/mod.rs b/rust/hyperlane-core/src/types/mod.rs index 9ae26aa1e..58d466abc 100644 --- a/rust/hyperlane-core/src/types/mod.rs +++ b/rust/hyperlane-core/src/types/mod.rs @@ -224,7 +224,7 @@ pub struct TxCostEstimate { /// The gas limit for the transaction. pub gas_limit: U256, /// The gas price for the transaction. - pub gas_price: U256, + pub gas_price: FixedPointNumber, /// The amount of L2 gas for the transaction. /// If Some, `gas_limit` is the sum of the gas limit /// covering L1 costs and the L2 gas limit. diff --git a/rust/hyperlane-core/src/types/primitive_types.rs b/rust/hyperlane-core/src/types/primitive_types.rs index 2b0f5112e..acc649113 100644 --- a/rust/hyperlane-core/src/types/primitive_types.rs +++ b/rust/hyperlane-core/src/types/primitive_types.rs @@ -3,11 +3,15 @@ #![allow(clippy::assign_op_pattern)] #![allow(clippy::reversed_empty_ranges)] +use std::{ops::Mul, str::FromStr}; + +use bigdecimal::BigDecimal; use borsh::{BorshDeserialize, BorshSerialize}; use fixed_hash::impl_fixed_hash_conversions; +use num_traits::Zero; use uint::construct_uint; -use crate::types::serialize; +use crate::{types::serialize, ChainCommunicationError}; /// Error type for conversion. #[derive(Debug, PartialEq, Eq, thiserror::Error)] @@ -337,3 +341,90 @@ impl From for H512 { H512(sig.into()) } } + +/// Wrapper type around `BigDecimal` to implement various traits on it +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FixedPointNumber(BigDecimal); + +impl FixedPointNumber { + /// Zero + pub fn zero() -> Self { + Self(BigDecimal::zero()) + } + + /// Round up to the nearest integer + pub fn ceil_to_integer(&self) -> Self { + Self(self.0.with_scale(0)) + } + + /// Ceil + pub fn ceil(&self, fractional_digit_count: i64) -> Self { + Self( + self.0 + .with_scale_round(fractional_digit_count, bigdecimal::RoundingMode::Ceiling), + ) + } +} + +impl Default for FixedPointNumber { + fn default() -> Self { + Self::zero() + } +} + +impl TryFrom for FixedPointNumber { + type Error = ChainCommunicationError; + fn try_from(val: U256) -> Result { + let u256_string = val.to_string(); + Ok(Self(BigDecimal::from_str(&u256_string)?)) + } +} + +impl TryInto for FixedPointNumber { + type Error = ChainCommunicationError; + + fn try_into(self) -> Result { + // Remove all decimals + let big_integer_string = self.0.with_scale(0).to_string(); + let value = U256::from_dec_str(&big_integer_string)?; + Ok(value) + } +} + +impl TryInto for FixedPointNumber { + type Error = ChainCommunicationError; + + fn try_into(self) -> Result { + let u256: U256 = self.try_into()?; + Ok(u256.as_u128()) + } +} + +impl From for FixedPointNumber +where + T: Into, +{ + fn from(val: T) -> Self { + Self(val.into()) + } +} + +impl Mul for FixedPointNumber +where + T: Into, +{ + type Output = FixedPointNumber; + + fn mul(self, rhs: T) -> Self::Output { + let rhs = rhs.into(); + Self(self.0 * rhs.0) + } +} + +impl FromStr for FixedPointNumber { + type Err = ChainCommunicationError; + + fn from_str(s: &str) -> Result { + Ok(Self(BigDecimal::from_str(s)?)) + } +} diff --git a/rust/utils/run-locally/Cargo.toml b/rust/utils/run-locally/Cargo.toml index 4d0d7538f..03d771734 100644 --- a/rust/utils/run-locally/Cargo.toml +++ b/rust/utils/run-locally/Cargo.toml @@ -11,6 +11,7 @@ version.workspace = true [dependencies] hyperlane-core = { path = "../../hyperlane-core", features = ["float"]} +hyperlane-cosmos = { path = "../../chains/hyperlane-cosmos"} toml_edit.workspace = true k256.workspace = true jobserver.workspace = true diff --git a/rust/utils/run-locally/src/cosmos/cli.rs b/rust/utils/run-locally/src/cosmos/cli.rs index b221c9f49..4258f149c 100644 --- a/rust/utils/run-locally/src/cosmos/cli.rs +++ b/rust/utils/run-locally/src/cosmos/cli.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, io::Write, path::PathBuf, process::Stdio}; +use hyperlane_cosmos::RawCosmosAmount; use k256::ecdsa::SigningKey; use crate::{ @@ -9,7 +10,7 @@ use crate::{ use super::{ crypto::KeyPair, default_keys, modify_toml, sed, types::BalanceResponse, wait_for_node, Codes, - Coin, TxResponse, + TxResponse, }; const GENESIS_FUND: u128 = 1000000000000; @@ -253,7 +254,7 @@ impl OsmosisCLI { sender: &str, contract: &str, execute_msg: T, - funds: Vec, + funds: Vec, ) -> TxResponse { let mut cmd = self .cli() diff --git a/rust/utils/run-locally/src/cosmos/crypto.rs b/rust/utils/run-locally/src/cosmos/crypto.rs index e4520cbcb..9b336f4df 100644 --- a/rust/utils/run-locally/src/cosmos/crypto.rs +++ b/rust/utils/run-locally/src/cosmos/crypto.rs @@ -1,6 +1,4 @@ -// TODO: this file can be removed if `CosmosAddress` can be imported from `hyperlane-cosmos`. -// However, adding a hyperlane-cosmos dep creates a dep cycle. -// Look into how this can be fixed. +// TODO: this file can be removed by replacing `KeyPair` uses with `CosmosAddress` use k256::ecdsa::{SigningKey, VerifyingKey}; use ripemd::Ripemd160; diff --git a/rust/utils/run-locally/src/cosmos/mod.rs b/rust/utils/run-locally/src/cosmos/mod.rs index 4ef4fce63..a61df9416 100644 --- a/rust/utils/run-locally/src/cosmos/mod.rs +++ b/rust/utils/run-locally/src/cosmos/mod.rs @@ -6,6 +6,7 @@ use std::{env, fs}; use cosmwasm_schema::cw_serde; use hpl_interface::types::bech32_decode; +use hyperlane_cosmos::RawCosmosAmount; use macro_rules_attribute::apply; use maplit::hashmap; use tempfile::tempdir; @@ -506,7 +507,7 @@ fn run_locally() { metadata: "".to_string(), }, }, - vec![Coin { + vec![RawCosmosAmount { denom: "uosmo".to_string(), amount: 25_000_000.to_string(), }], diff --git a/rust/utils/run-locally/src/cosmos/types.rs b/rust/utils/run-locally/src/cosmos/types.rs index f50986b60..d3a515c4e 100644 --- a/rust/utils/run-locally/src/cosmos/types.rs +++ b/rust/utils/run-locally/src/cosmos/types.rs @@ -1,6 +1,7 @@ use std::{collections::BTreeMap, path::PathBuf}; use hpl_interface::types::bech32_decode; +use hyperlane_cosmos::RawCosmosAmount; use super::{cli::OsmosisCLI, CosmosNetwork}; @@ -35,12 +36,6 @@ pub struct TxResponse { pub logs: Vec, } -#[derive(serde::Serialize, serde::Deserialize)] -pub struct Coin { - pub denom: String, - pub amount: String, -} - #[derive(serde::Serialize, serde::Deserialize, Clone)] pub struct Codes { pub hpl_hook_merkle: u64, @@ -73,7 +68,7 @@ pub struct Deployments { #[derive(serde::Serialize, serde::Deserialize)] pub struct BalanceResponse { - pub balances: Vec, + pub balances: Vec, } #[derive(serde::Serialize, serde::Deserialize)] @@ -125,6 +120,7 @@ pub struct AgentConfig { pub prefix: String, pub signer: AgentConfigSigner, pub index: AgentConfigIndex, + pub gas_price: RawCosmosAmount, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] @@ -164,6 +160,10 @@ impl AgentConfig { key: format!("0x{}", hex::encode(validator.priv_key.to_bytes())), prefix: "osmo".to_string(), }, + gas_price: RawCosmosAmount { + denom: "uosmo".to_string(), + amount: "0.05".to_string(), + }, index: AgentConfigIndex { from: 1, chunk: 100,