diff --git a/crates/ibc/src/clients/ics07_tendermint/client_state.rs b/crates/ibc/src/clients/ics07_tendermint/client_state.rs index 770c93adb0..2868f39890 100644 --- a/crates/ibc/src/clients/ics07_tendermint/client_state.rs +++ b/crates/ibc/src/clients/ics07_tendermint/client_state.rs @@ -637,7 +637,7 @@ impl TryFrom for ClientState { type Error = Error; fn try_from(raw: RawTmClientState) -> Result { - let chain_id = ChainId::from_string(raw.chain_id.as_str()); + let chain_id = ChainId::from(raw.chain_id.as_str()); let trust_level = { let trust_level = raw @@ -853,7 +853,7 @@ mod tests { Test { name: "Valid long (50 chars) chain-id".to_string(), params: ClientStateParams { - id: ChainId::new("a".repeat(48), 0), + id: ChainId::new(&"a".repeat(48), 0), ..default_params.clone() }, want_pass: true, @@ -861,7 +861,7 @@ mod tests { Test { name: "Invalid too-long (51 chars) chain-id".to_string(), params: ClientStateParams { - id: ChainId::new("a".repeat(49), 0), + id: ChainId::new(&"a".repeat(49), 0), ..default_params.clone() }, want_pass: false, @@ -974,7 +974,7 @@ mod tests { fn client_state_verify_height() { // Define a "default" set of parameters to reuse throughout these tests. let default_params: ClientStateParams = ClientStateParams { - id: ChainId::new("ibc".to_string(), 1), + id: ChainId::new("ibc", 1), trust_level: TrustThreshold::ONE_THIRD, trusting_period: Duration::new(64000, 0), unbonding_period: Duration::new(128000, 0), @@ -1151,7 +1151,7 @@ pub mod test_util { pub fn get_dummy_raw_tm_client_state(frozen_height: RawHeight) -> RawTmClientState { #[allow(deprecated)] RawTmClientState { - chain_id: ChainId::new("ibc".to_string(), 0).to_string(), + chain_id: ChainId::new("ibc", 0).to_string(), trust_level: Some(Fraction { numerator: 1, denominator: 3, diff --git a/crates/ibc/src/core/ics02_client/handler/update_client.rs b/crates/ibc/src/core/ics02_client/handler/update_client.rs index 59a8c8c976..9156965631 100644 --- a/crates/ibc/src/core/ics02_client/handler/update_client.rs +++ b/crates/ibc/src/core/ics02_client/handler/update_client.rs @@ -181,10 +181,10 @@ mod tests { let client_id = ClientId::new(tm_client_type(), 0).unwrap(); let client_height = Height::new(1, 20).unwrap(); let update_height = Height::new(1, 21).unwrap(); - let chain_id_b = ChainId::new("mockgaiaB".to_string(), 1); + let chain_id_b = ChainId::new("mockgaiaB", 1); let mut ctx = MockContext::new( - ChainId::new("mockgaiaA".to_string(), 1), + ChainId::new("mockgaiaA", 1), HostType::Mock, 5, Height::new(1, 1).unwrap(), @@ -227,10 +227,10 @@ mod tests { let client_id = ClientId::new(tm_client_type(), 0).unwrap(); let client_height = Height::new(1, 20).unwrap(); let update_height = Height::new(1, 21).unwrap(); - let chain_id_b = ChainId::new("mockgaiaB".to_string(), 1); + let chain_id_b = ChainId::new("mockgaiaB", 1); let mut ctx = MockContext::new( - ChainId::new("mockgaiaA".to_string(), 1), + ChainId::new("mockgaiaA", 1), HostType::Mock, 5, Height::new(1, 1).unwrap(), @@ -274,8 +274,8 @@ mod tests { let client_id = ClientId::new(tm_client_type(), 0).unwrap(); let client_height = Height::new(1, 20).unwrap(); - let ctx_a_chain_id = ChainId::new("mockgaiaA".to_string(), 1); - let ctx_b_chain_id = ChainId::new("mockgaiaB".to_string(), 1); + let ctx_a_chain_id = ChainId::new("mockgaiaA", 1); + let ctx_b_chain_id = ChainId::new("mockgaiaB", 1); let start_height = Height::new(1, 11).unwrap(); let mut ctx_a = MockContext::new(ctx_a_chain_id, HostType::Mock, 5, start_height) @@ -399,7 +399,7 @@ mod tests { let chain_start_height = Height::new(1, 11).unwrap(); let ctx = MockContext::new( - ChainId::new("mockgaiaA".to_string(), 1), + ChainId::new("mockgaiaA", 1), HostType::Mock, 5, chain_start_height, @@ -412,7 +412,7 @@ mod tests { ); let ctx_b = MockContext::new( - ChainId::new("mockgaiaB".to_string(), 1), + ChainId::new("mockgaiaB", 1), HostType::SyntheticTendermint, 5, client_height, @@ -538,11 +538,11 @@ mod tests { let client_id = ClientId::new(tm_client_type(), 0).unwrap(); let client_height = Height::new(1, 20).unwrap(); let misbehaviour_height = Height::new(1, 21).unwrap(); - let chain_id_b = ChainId::new("mockgaiaB".to_string(), 1); + let chain_id_b = ChainId::new("mockgaiaB", 1); // Create a mock context for chain-A with a synthetic tendermint light client for chain-B let mut ctx_a = MockContext::new( - ChainId::new("mockgaiaA".to_string(), 1), + ChainId::new("mockgaiaA", 1), HostType::Mock, 5, Height::new(1, 1).unwrap(), @@ -599,11 +599,11 @@ mod tests { let client_id = ClientId::new(tm_client_type(), 0).unwrap(); let client_height = Height::new(1, 20).unwrap(); let misbehaviour_height = Height::new(1, 21).unwrap(); - let chain_id_b = ChainId::new("mockgaiaB".to_string(), 1); + let chain_id_b = ChainId::new("mockgaiaB", 1); // Create a mock context for chain-A with a synthetic tendermint light client for chain-B let mut ctx_a = MockContext::new( - ChainId::new("mockgaiaA".to_string(), 1), + ChainId::new("mockgaiaA", 1), HostType::Mock, 5, Height::new(1, 1).unwrap(), diff --git a/crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs b/crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs index 1d906d6e0f..1f7433545e 100644 --- a/crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs +++ b/crates/ibc/src/core/ics03_connection/handler/conn_open_ack.rs @@ -254,7 +254,7 @@ mod tests { let ctx_default = MockContext::default(); let ctx_new = MockContext::new( - ChainId::new("mockgaia".to_string(), latest_height.revision_number()), + ChainId::new("mockgaia", latest_height.revision_number()), HostType::Mock, max_history_size, latest_height, diff --git a/crates/ibc/src/core/ics03_connection/handler/conn_open_try.rs b/crates/ibc/src/core/ics03_connection/handler/conn_open_try.rs index 9ddb7b577a..5f48d6e048 100644 --- a/crates/ibc/src/core/ics03_connection/handler/conn_open_try.rs +++ b/crates/ibc/src/core/ics03_connection/handler/conn_open_try.rs @@ -253,7 +253,7 @@ mod tests { }; let ctx_new = MockContext::new( - ChainId::new("mockgaia".to_string(), 0), + ChainId::new("mockgaia", 0), HostType::Mock, max_history_size, host_chain_height, diff --git a/crates/ibc/src/core/ics24_host/identifier.rs b/crates/ibc/src/core/ics24_host/identifier.rs index 8b028270f1..253d0dcf69 100644 --- a/crates/ibc/src/core/ics24_host/identifier.rs +++ b/crates/ibc/src/core/ics24_host/identifier.rs @@ -4,7 +4,7 @@ pub(crate) mod validate; use validate::*; use core::convert::{From, Infallible}; -use core::fmt::{Debug, Display, Error as FmtError, Formatter}; +use core::fmt::{Error as FmtError, Formatter}; use core::str::FromStr; use derive_more::Into; @@ -18,7 +18,6 @@ use crate::prelude::*; const CONNECTION_ID_PREFIX: &str = "connection"; const CHANNEL_ID_PREFIX: &str = "channel"; -const DEFAULT_CHAIN_ID: &str = "defaultChainId"; const DEFAULT_PORT_ID: &str = "defaultPort"; const TRANSFER_PORT_ID: &str = "transfer"; @@ -59,29 +58,16 @@ impl ChainId { /// use ibc::core::ics24_host::identifier::ChainId; /// /// let epoch_number = 10; - /// let id = ChainId::new("chainA".to_string(), epoch_number); + /// let id = ChainId::new("chainA", epoch_number); /// assert_eq!(id.version(), epoch_number); /// ``` - pub fn new(name: String, version: u64) -> Self { + pub fn new(name: &str, version: u64) -> Self { Self { id: format!("{name}-{version}"), version, } } - pub fn from_string(id: &str) -> Self { - let version = if Self::is_epoch_format(id) { - Self::chain_version(id) - } else { - 0 - }; - - Self { - id: id.to_string(), - version, - } - } - /// Get a reference to the underlying string. pub fn as_str(&self) -> &str { &self.id @@ -103,16 +89,7 @@ impl ChainId { /// assert_eq!(ChainId::chain_version("testnet-helloworld-2"), 2); /// ``` pub fn chain_version(chain_id: &str) -> u64 { - if !ChainId::is_epoch_format(chain_id) { - return 0; - } - - let split: Vec<_> = chain_id.split('-').collect(); - split - .last() - .expect("get revision number from chain_id") - .parse() - .unwrap_or(0) + Self::split_chain_id(chain_id).1.unwrap_or(0) } /// is_epoch_format() checks if a chain_id is in the format required for parsing epochs @@ -123,46 +100,95 @@ impl ChainId { /// assert_eq!(ChainId::is_epoch_format("chainA"), false); /// assert_eq!(ChainId::is_epoch_format("chainA-1"), true); /// assert_eq!(ChainId::is_epoch_format("c-1"), true); + /// assert_eq!(ChainId::is_epoch_format("-1"), false); /// ``` pub fn is_epoch_format(chain_id: &str) -> bool { - let re = safe_regex::regex!(br".*[^-]-[1-9][0-9]*"); - re.is_match(chain_id.as_bytes()) + Self::split_chain_id(chain_id).1.is_some() } /// with_version() checks if a chain_id is in the format required for parsing epochs, and if so /// replaces it's version with the specified version /// ``` /// use ibc::core::ics24_host::identifier::ChainId; - /// assert_eq!(ChainId::new("chainA".to_string(), 1).with_version(2), ChainId::new("chainA".to_string(), 2)); + /// assert_eq!(ChainId::new("chainA", 1).with_version(2), ChainId::new("chainA", 2)); /// assert_eq!("chain1".parse::().unwrap().with_version(2), "chain1".parse::().unwrap()); /// ``` pub fn with_version(mut self, version: u64) -> Self { - if Self::is_epoch_format(&self.id) { - self.id = { - let mut split: Vec<&str> = self.id.split('-').collect(); - let version = version.to_string(); - if let Some(last_elem) = split.last_mut() { - *last_elem = &version; - } - split.join("-") - }; - self.version = version; + if self.version != 0 { + if let (name, Some(_)) = Self::split_chain_id(&self.id) { + self.id = format!("{name}-{version}"); + self.version = version; + } } self } + + /// Splits chain_id into name and version if the id includes epoch number. + /// + /// Chain id with epoch number has format `{name}-{version}` where version + /// is a positive integer starting with non-zero digit. If `chain_id` is in + /// that format, returns `(name, Some(version))`; otherwise returns + /// `(chain_id, None)`. + /// + /// **Panics** if chain id is in epoch format but the version overflows + /// `u64`. + fn split_chain_id(chain_id: &str) -> (&str, Option) { + fn split(chain_id: &str) -> Option<(&str, u64)> { + let (name, version) = chain_id.rsplit_once('-')?; + if name.is_empty() { + return None; + } + let (car, cdr) = version.as_bytes().split_first()?; + if b'1' <= *car && *car <= b'9' && cdr.iter().all(u8::is_ascii_digit) { + Some((name, version.parse().unwrap())) + } else { + None + } + } + match split(chain_id) { + Some((name, version)) => (name, Some(version)), + None => (chain_id, None), + } + } +} + +impl Default for ChainId { + fn default() -> Self { + Self { + id: String::from("defaultChainId-1"), + version: 1, + } + } +} + +impl<'a> From<&'a str> for ChainId { + fn from(value: &'a str) -> Self { + let version = Self::chain_version(value); + Self { + id: String::from(value), + version, + } + } +} + +impl From for ChainId { + fn from(value: String) -> Self { + let version = Self::chain_version(&value); + Self { id: value, version } + } } impl FromStr for ChainId { type Err = Infallible; fn from_str(id: &str) -> Result { - Ok(Self::from_string(id)) + Ok(id.into()) } } -impl Display for ChainId { +impl core::fmt::Display for ChainId { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { - write!(f, "{}", self.id) + self.id.fmt(f) } } @@ -178,18 +204,6 @@ impl From for ChainId { } } -impl Default for ChainId { - fn default() -> Self { - Self::from_string(DEFAULT_CHAIN_ID) - } -} - -impl From for ChainId { - fn from(value: String) -> Self { - Self::from_string(&value) - } -} - #[cfg_attr( feature = "parity-scale-codec", derive( @@ -237,7 +251,7 @@ impl ClientId { } /// This implementation provides a `to_string` method. -impl Display for ClientId { +impl core::fmt::Display for ClientId { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "{}", self.0) } @@ -320,7 +334,7 @@ impl ConnectionId { } /// This implementation provides a `to_string` method. -impl Display for ConnectionId { +impl core::fmt::Display for ConnectionId { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "{}", self.0) } @@ -396,7 +410,7 @@ impl PortId { } /// This implementation provides a `to_string` method. -impl Display for PortId { +impl core::fmt::Display for PortId { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "{}", self.0) } @@ -472,7 +486,7 @@ impl ChannelId { } /// This implementation provides a `to_string` method. -impl Display for ChannelId { +impl core::fmt::Display for ChannelId { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { write!(f, "{}", self.0) } diff --git a/crates/ibc/src/mock/context.rs b/crates/ibc/src/mock/context.rs index b60e9dbc8c..8ba5125da5 100644 --- a/crates/ibc/src/mock/context.rs +++ b/crates/ibc/src/mock/context.rs @@ -96,7 +96,7 @@ pub struct MockContext { impl Default for MockContext { fn default() -> Self { Self::new( - ChainId::new("mockgaia".to_string(), 0), + ChainId::new("mockgaia", 0), HostType::Mock, 5, Height::new(0, 5).unwrap(), @@ -1487,7 +1487,7 @@ mod tests { Test { name: "Empty history, small pruning window".to_string(), ctx: MockContext::new( - ChainId::new("mockgaia".to_string(), cv), + ChainId::new("mockgaia", cv), HostType::Mock, 2, Height::new(cv, 1).unwrap(), @@ -1496,7 +1496,7 @@ mod tests { Test { name: "[Synthetic TM host] Empty history, small pruning window".to_string(), ctx: MockContext::new( - ChainId::new("mocksgaia".to_string(), cv), + ChainId::new("mocksgaia", cv), HostType::SyntheticTendermint, 2, Height::new(cv, 1).unwrap(), @@ -1505,7 +1505,7 @@ mod tests { Test { name: "Large pruning window".to_string(), ctx: MockContext::new( - ChainId::new("mockgaia".to_string(), cv), + ChainId::new("mockgaia", cv), HostType::Mock, 30, Height::new(cv, 2).unwrap(), @@ -1514,7 +1514,7 @@ mod tests { Test { name: "[Synthetic TM host] Large pruning window".to_string(), ctx: MockContext::new( - ChainId::new("mocksgaia".to_string(), cv), + ChainId::new("mocksgaia", cv), HostType::SyntheticTendermint, 30, Height::new(cv, 2).unwrap(), @@ -1523,7 +1523,7 @@ mod tests { Test { name: "Small pruning window".to_string(), ctx: MockContext::new( - ChainId::new("mockgaia".to_string(), cv), + ChainId::new("mockgaia", cv), HostType::Mock, 3, Height::new(cv, 30).unwrap(), @@ -1532,7 +1532,7 @@ mod tests { Test { name: "[Synthetic TM host] Small pruning window".to_string(), ctx: MockContext::new( - ChainId::new("mockgaia".to_string(), cv), + ChainId::new("mockgaia", cv), HostType::SyntheticTendermint, 3, Height::new(cv, 30).unwrap(), @@ -1541,7 +1541,7 @@ mod tests { Test { name: "Small pruning window, small starting height".to_string(), ctx: MockContext::new( - ChainId::new("mockgaia".to_string(), cv), + ChainId::new("mockgaia", cv), HostType::Mock, 3, Height::new(cv, 2).unwrap(), @@ -1550,7 +1550,7 @@ mod tests { Test { name: "[Synthetic TM host] Small pruning window, small starting height".to_string(), ctx: MockContext::new( - ChainId::new("mockgaia".to_string(), cv), + ChainId::new("mockgaia", cv), HostType::SyntheticTendermint, 3, Height::new(cv, 2).unwrap(), @@ -1559,7 +1559,7 @@ mod tests { Test { name: "Large pruning window, large starting height".to_string(), ctx: MockContext::new( - ChainId::new("mockgaia".to_string(), cv), + ChainId::new("mockgaia", cv), HostType::Mock, 50, Height::new(cv, 2000).unwrap(), @@ -1568,7 +1568,7 @@ mod tests { Test { name: "[Synthetic TM host] Large pruning window, large starting height".to_string(), ctx: MockContext::new( - ChainId::new("mockgaia".to_string(), cv), + ChainId::new("mockgaia", cv), HostType::SyntheticTendermint, 50, Height::new(cv, 2000).unwrap(), @@ -1817,7 +1817,7 @@ mod tests { } let mut ctx = MockContext::new( - ChainId::new("mockgaia".to_string(), 1), + ChainId::new("mockgaia", 1), HostType::Mock, 1, Height::new(1, 1).unwrap(), diff --git a/crates/ibc/src/mock/ics18_relayer/context.rs b/crates/ibc/src/mock/ics18_relayer/context.rs index 32d636aa76..d8aed6c98d 100644 --- a/crates/ibc/src/mock/ics18_relayer/context.rs +++ b/crates/ibc/src/mock/ics18_relayer/context.rs @@ -109,8 +109,8 @@ mod tests { let client_on_a_for_b = ClientId::new(tm_client_type(), 0).unwrap(); let client_on_b_for_a = ClientId::new(mock_client_type(), 0).unwrap(); - let chain_id_a = ChainId::new("mockgaiaA".to_string(), 1); - let chain_id_b = ChainId::new("mockgaiaB".to_string(), 1); + let chain_id_a = ChainId::new("mockgaiaA", 1); + let chain_id_b = ChainId::new("mockgaiaB", 1); // Create two mock contexts, one for each chain. let mut ctx_a =