abstract_std/objects/pool/
pool_id.rs

1use std::{fmt, str::FromStr};
2
3use cosmwasm_std::{Addr, Api, StdError};
4
5use crate::{error::AbstractError, AbstractResult};
6
7#[cosmwasm_schema::cw_serde]
8#[non_exhaustive]
9// Need eq and hash for ans scraper
10#[cfg_attr(not(target_arch = "wasm32"), derive(Eq, Hash, PartialOrd, Ord))]
11pub enum PoolAddressBase<T> {
12    SeparateAddresses { swap: T, liquidity: T },
13    Contract(T),
14    Id(u64),
15}
16
17impl<T> PoolAddressBase<T> {
18    pub fn contract<C: Into<T>>(contract: C) -> Self {
19        Self::Contract(contract.into())
20    }
21    pub fn id<N: Into<u64>>(id: N) -> Self {
22        Self::Id(id.into())
23    }
24}
25
26/// Actual instance of a PoolAddress with verified data
27pub type PoolAddress = PoolAddressBase<Addr>;
28
29impl PoolAddress {
30    pub fn expect_contract(&self) -> AbstractResult<Addr> {
31        match self {
32            PoolAddress::Contract(addr) => Ok(addr.clone()),
33            _ => Err(AbstractError::Assert(
34                "Pool address not a contract address.".into(),
35            )),
36        }
37    }
38
39    pub fn expect_id(&self) -> AbstractResult<u64> {
40        match self {
41            PoolAddress::Id(id) => Ok(*id),
42            _ => Err(AbstractError::Assert(
43                "Pool address not an numerical ID.".into(),
44            )),
45        }
46    }
47}
48/// Instance of a PoolAddress passed around messages
49pub type UncheckedPoolAddress = PoolAddressBase<String>;
50
51impl FromStr for UncheckedPoolAddress {
52    type Err = AbstractError;
53
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        let words: Vec<&str> = s.split(':').collect();
56
57        match words[0] {
58            "contract" => {
59                if words.len() != 2 {
60                    return Err(AbstractError::FormattingError {
61                        object: "unchecked pool address".to_string(),
62                        expected: "contract:{{contract_addr}}".to_string(),
63                        actual: s.to_string(),
64                    });
65                }
66
67                Ok(UncheckedPoolAddress::Contract(String::from(words[1])))
68            }
69            "id" => {
70                if words.len() != 2 {
71                    return Err(AbstractError::FormattingError {
72                        object: "unchecked pool address".to_string(),
73                        expected: "id:{{pool_id}}".to_string(),
74                        actual: s.to_string(),
75                    });
76                }
77                let parsed_id_res = words[1].parse::<u64>();
78                match parsed_id_res {
79                    Ok(id) => Ok(UncheckedPoolAddress::Id(id)),
80                    Err(err) => Err(StdError::generic_err(err.to_string()).into()),
81                }
82            }
83            _unknown => Err(AbstractError::FormattingError {
84                object: "unchecked pool address".to_string(),
85                expected: "'contract' or 'id'".to_string(),
86                actual: s.to_string(),
87            }),
88        }
89    }
90}
91
92impl From<PoolAddress> for UncheckedPoolAddress {
93    fn from(pool_info: PoolAddress) -> Self {
94        match pool_info {
95            PoolAddress::Contract(contract_addr) => {
96                UncheckedPoolAddress::Contract(contract_addr.into())
97            }
98            PoolAddress::Id(denom) => UncheckedPoolAddress::Id(denom),
99            PoolAddress::SeparateAddresses { swap, liquidity } => {
100                UncheckedPoolAddress::SeparateAddresses {
101                    swap: swap.into(),
102                    liquidity: liquidity.into(),
103                }
104            }
105        }
106    }
107}
108
109impl From<&PoolAddress> for UncheckedPoolAddress {
110    fn from(pool_id: &PoolAddress) -> Self {
111        match pool_id {
112            PoolAddress::Contract(contract_addr) => {
113                UncheckedPoolAddress::Contract(contract_addr.into())
114            }
115            PoolAddress::Id(denom) => UncheckedPoolAddress::Id(*denom),
116            PoolAddress::SeparateAddresses { swap, liquidity } => {
117                UncheckedPoolAddress::SeparateAddresses {
118                    swap: swap.into(),
119                    liquidity: liquidity.into(),
120                }
121            }
122        }
123    }
124}
125
126impl From<Addr> for PoolAddress {
127    fn from(contract_addr: Addr) -> Self {
128        PoolAddress::Contract(contract_addr)
129    }
130}
131
132impl UncheckedPoolAddress {
133    /// Validate data contained in an _unchecked_ **pool id** instance; return a new _checked_
134    /// **pool id** instance:
135    /// * For Contract addresses, assert its address is valid
136    ///
137    ///
138    /// ```rust,no_run
139    /// use cosmwasm_std::{Addr, Api};
140    /// use abstract_std::{objects::pool_id::UncheckedPoolAddress, AbstractResult};
141    ///
142    /// fn validate_pool_id(api: &dyn Api, pool_id_unchecked: &UncheckedPoolAddress) {
143    ///     match pool_id_unchecked.check(api) {
144    ///         Ok(info) => println!("pool id is valid: {}", info.to_string()),
145    ///         Err(err) => println!("pool id is invalid! reason: {}", err),
146    ///     }
147    /// }
148    /// ```
149    pub fn check(&self, api: &dyn Api) -> AbstractResult<PoolAddress> {
150        Ok(match self {
151            UncheckedPoolAddress::Contract(contract_addr) => {
152                PoolAddress::Contract(api.addr_validate(contract_addr)?)
153            }
154            UncheckedPoolAddress::Id(pool_id) => PoolAddress::Id(*pool_id),
155            UncheckedPoolAddress::SeparateAddresses { swap, liquidity } => {
156                PoolAddress::SeparateAddresses {
157                    swap: api.addr_validate(swap)?,
158                    liquidity: api.addr_validate(liquidity)?,
159                }
160            }
161        })
162    }
163}
164
165impl fmt::Display for PoolAddress {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            PoolAddress::Contract(contract_addr) => write!(f, "contract:{contract_addr}"),
169            PoolAddress::Id(pool_id) => write!(f, "id:{pool_id}"),
170            PoolAddress::SeparateAddresses { swap, liquidity } => {
171                write!(f, "swap:{swap}, pair: {liquidity}")
172            }
173        }
174    }
175}
176
177#[cfg(test)]
178mod test {
179    #![allow(clippy::needless_borrows_for_generic_args)]
180    use cosmwasm_std::testing::MockApi;
181
182    use super::*;
183
184    #[coverage_helper::test]
185    fn test_pool_id_from_str() {
186        let api = MockApi::default();
187        let contract_addr = api.addr_make("foo");
188        let pool_id_str = format!("contract:{contract_addr}");
189        let pool_id = UncheckedPoolAddress::from_str(&pool_id_str).unwrap();
190        let pool_id = pool_id.check(&api).unwrap();
191        assert_eq!(pool_id.to_string(), pool_id_str.to_string());
192    }
193
194    #[coverage_helper::test]
195    fn test_expect_contract_happy() {
196        let api = MockApi::default();
197        let contract_addr = api.addr_make("foo");
198        let pool_id = PoolAddress::Contract(contract_addr.clone());
199        let res = pool_id.expect_contract();
200        assert!(res.is_ok());
201        assert_eq!(res.unwrap(), contract_addr);
202    }
203
204    #[coverage_helper::test]
205    fn test_expect_contract_sad() {
206        let pool_id = PoolAddress::Id(1);
207        let res = pool_id.expect_contract();
208        assert!(res.is_err());
209    }
210
211    #[coverage_helper::test]
212    fn test_expect_id_happy() {
213        let pool_id = PoolAddress::Id(1);
214        let res = pool_id.expect_id();
215        assert!(res.is_ok());
216        assert_eq!(res.unwrap(), 1);
217    }
218
219    #[coverage_helper::test]
220    fn test_expect_id_sad() {
221        let api = MockApi::default();
222        let contract_addr = api.addr_make("foo");
223        let pool_id = PoolAddress::Contract(contract_addr);
224        let res = pool_id.expect_id();
225        assert!(res.is_err());
226    }
227}