1use cosmwasm_std::{Coin, StdError, StdResult, Uint128};
2use regex::Regex;
3
4pub fn coin_from_str(s: &str) -> Coin {
6 let idx = s
8 .char_indices()
9 .find(|(_, c)| !c.is_digit(10))
10 .map(|(idx, _)| idx)
11 .unwrap_or(s.len());
12
13 let amount: Uint128 = s[..idx].parse::<u128>().unwrap().into();
15 let denom = s[idx..].to_string();
16
17 Coin { denom, amount }
18}
19
20pub fn validate_denom(input: &str) -> StdResult<()> {
23 let re = Regex::new(r"^[a-zA-Z][a-zA-Z0-9/:._-]{2,127}$").unwrap();
24
25 if re.is_match(input) {
26 Ok(())
27 } else {
28 Err(StdError::generic_err(
29 "Provided string is not a valid CosmosSDK denom.",
30 ))
31 }
32}
33
34#[cfg(test)]
35mod tests {
36 use test_case::test_case;
37
38 use super::*;
39
40 #[test]
41 fn test_coin_from_sdk_str() {
42 let coin = coin_from_str("100000000000000000000gamm/pool/1");
43
44 assert_eq!(coin.amount, Uint128::from(100000000000000000000u128));
45 assert_eq!(coin.denom, "gamm/pool/1");
46 }
47
48 #[test_case("gamm/pool/1" => Ok(()); "valid osmosis LP denom")]
49 #[test_case("uatom" => Ok(()); "valid uatom denom")]
50 #[test_case("ibc/C140AFD542AE77BD7DCC83F13FDD8C5E5BB8C4929785E6EC2F4C636F98F17901" => Ok(()); "valid IBC denom")]
51 #[test_case("IBC/C140AFD542AE77BD7DCC83F13FDD8C5E5BB8C4929785E6EC2F4C636F98F17901" => Ok(()); "valid IBC denom capital IBC")]
52 #[test_case("factory/osmo1g3kmqpp8608szfp0pdag3r6z85npph7wmccat8lgl3mp407kv73qlj7qwp/VaultToken/1/14d/ATOM/OSMO" => Ok(()); "valid token factory denom")]
53 #[test_case("test:test/test-test.test_test" => Ok(()); "all valid separators")]
54 #[test_case("test test" => Err(StdError::generic_err("Provided string is not a valid CosmosSDK denom.")); "invalid separator space")]
55 #[test_case("test/test " => Err(StdError::generic_err("Provided string is not a valid CosmosSDK denom.")); "trailing space")]
56 #[test_case(" test/test" => Err(StdError::generic_err("Provided string is not a valid CosmosSDK denom.")); "leading space")]
57 #[test_case("/test/test" => Err(StdError::generic_err("Provided string is not a valid CosmosSDK denom.")); "leading separator")]
58 #[test_case("2test/test" => Err(StdError::generic_err("Provided string is not a valid CosmosSDK denom.")); "leading number")]
59 #[test_case("te" => Err(StdError::generic_err("Provided string is not a valid CosmosSDK denom.")); "too short")]
60 #[test_case("tes" => Ok(()); "min length")]
61 #[test_case("t//" => Ok(()); "min length with two consecutive separators")]
62 #[test_case("testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest" => Ok(()); "max length")]
63 #[test_case("testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestt" => Err(StdError::generic_err("Provided string is not a valid CosmosSDK denom.")); "too long")]
64 fn test_validate_denom(input: &str) -> StdResult<()> {
65 validate_denom(input)
66 }
67}