abstract_core/objects/pool/
pool_metadata.rs

1use std::{fmt, str::FromStr};
2
3use cosmwasm_std::StdError;
4use cw_asset::AssetInfo;
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    constants::{ASSET_DELIMITER, ATTRIBUTE_DELIMITER, TYPE_DELIMITER},
10    objects::{pool_type::PoolType, AssetEntry},
11};
12
13type DexName = String;
14
15#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
16pub struct PoolMetadata {
17    pub dex: DexName,
18    pub pool_type: PoolType,
19    pub assets: Vec<AssetEntry>,
20}
21
22#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
23pub struct ResolvedPoolMetadata {
24    pub dex: DexName,
25    pub pool_type: PoolType,
26    pub assets: Vec<AssetInfo>,
27}
28
29impl PoolMetadata {
30    pub fn new<T: ToString, U: Into<AssetEntry>>(
31        dex_name: T,
32        pool_type: PoolType,
33        assets: Vec<U>,
34    ) -> Self {
35        let mut assets = assets
36            .into_iter()
37            .map(|a| a.into())
38            .collect::<Vec<AssetEntry>>();
39        // sort the asset name
40        assets.sort_unstable();
41        Self {
42            dex: dex_name.to_string(),
43            pool_type,
44            assets,
45        }
46    }
47
48    pub fn stable<T: ToString>(dex_name: T, assets: Vec<impl Into<AssetEntry>>) -> Self {
49        Self::new(dex_name, PoolType::Stable, assets)
50    }
51
52    pub fn weighted<T: ToString>(dex_name: T, assets: Vec<impl Into<AssetEntry>>) -> Self {
53        Self::new(dex_name, PoolType::Weighted, assets)
54    }
55
56    pub fn constant_product<T: ToString>(dex_name: T, assets: Vec<impl Into<AssetEntry>>) -> Self {
57        Self::new(dex_name, PoolType::ConstantProduct, assets)
58    }
59
60    pub fn liquidity_bootstrap<T: ToString>(
61        dex_name: T,
62        assets: Vec<impl Into<AssetEntry>>,
63    ) -> Self {
64        Self::new(dex_name, PoolType::LiquidityBootstrap, assets)
65    }
66
67    pub fn concentrated_liquidity<T: ToString>(
68        dex_name: T,
69        assets: Vec<impl Into<AssetEntry>>,
70    ) -> Self {
71        Self::new(dex_name, PoolType::ConcentratedLiquidity, assets)
72    }
73}
74
75impl FromStr for PoolMetadata {
76    type Err = StdError;
77
78    fn from_str(s: &str) -> Result<Self, Self::Err> {
79        // Split it into three parts
80        let parts = s.split_once(TYPE_DELIMITER).and_then(|(dex, remainder)| {
81            remainder
82                .split_once(ATTRIBUTE_DELIMITER)
83                .map(|(assets, pool_type)| (dex, assets, pool_type))
84        });
85        let Some((dex, assets, pool_type)) = parts else {
86            return Err(StdError::generic_err(format!(
87                "invalid pool metadata format `{s}`; must be in format `{{dex}}{TYPE_DELIMITER}{{asset1}},{{asset2}}{ATTRIBUTE_DELIMITER}{{pool_type}}...`"
88            )));
89        };
90
91        let assets: Vec<&str> = assets.split(ASSET_DELIMITER).collect();
92        let pool_type = PoolType::from_str(pool_type)?;
93
94        Ok(PoolMetadata::new(dex, pool_type, assets))
95    }
96}
97
98/// To string
99/// Ex: "junoswap/uusd,uust:stable"
100impl fmt::Display for PoolMetadata {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        let assets_str = self
103            .assets
104            .iter()
105            .map(|a| a.as_str())
106            .collect::<Vec<&str>>()
107            .join(ASSET_DELIMITER);
108        let pool_type_str = self.pool_type.to_string();
109        let dex = &self.dex;
110
111        write!(
112            f,
113            "{dex}{TYPE_DELIMITER}{assets_str}{ATTRIBUTE_DELIMITER}{pool_type_str}",
114        )
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use speculoos::prelude::*;
121
122    use super::*;
123
124    mod implementation {
125        use super::*;
126
127        #[test]
128        fn new_works() {
129            let dex = "junoswap";
130            let pool_type = PoolType::Stable;
131            let mut assets = vec!["uust".to_string(), "uusd".to_string()];
132            let actual = PoolMetadata::new(dex, pool_type, assets.clone());
133            // sort the asset names
134            assets.sort();
135            let expected = PoolMetadata {
136                dex: dex.to_string(),
137                pool_type,
138                assets: assets.into_iter().map(|a| a.into()).collect(),
139            };
140            assert_that!(actual).is_equal_to(expected);
141            assert_that!(actual.to_string()).is_equal_to("junoswap/uusd,uust:stable".to_string());
142        }
143
144        #[test]
145        fn stable_works() {
146            let dex = "junoswap";
147            let assets = vec!["uusd".to_string(), "uust".to_string()];
148            let actual = PoolMetadata::stable(dex, assets.clone());
149
150            let expected = PoolMetadata {
151                dex: dex.to_string(),
152                pool_type: PoolType::Stable,
153                assets: assets.into_iter().map(|a| a.into()).collect(),
154            };
155            assert_that!(actual).is_equal_to(expected);
156        }
157
158        #[test]
159        fn weighted_works() {
160            let dex = "junoswap";
161            let assets = vec!["uusd".to_string(), "uust".to_string()];
162            let actual = PoolMetadata::weighted(dex, assets.clone());
163
164            let expected = PoolMetadata {
165                dex: dex.to_string(),
166                pool_type: PoolType::Weighted,
167                assets: assets.into_iter().map(|a| a.into()).collect(),
168            };
169            assert_that!(actual).is_equal_to(expected);
170        }
171
172        #[test]
173        fn constant_product_works() {
174            let dex = "junoswap";
175            let assets = vec!["uusd".to_string(), "uust".to_string()];
176            let actual = PoolMetadata::constant_product(dex, assets.clone());
177
178            let expected = PoolMetadata {
179                dex: dex.to_string(),
180                pool_type: PoolType::ConstantProduct,
181                assets: assets.into_iter().map(|a| a.into()).collect(),
182            };
183            assert_that!(actual).is_equal_to(expected);
184        }
185
186        #[test]
187        fn liquidity_bootstrap_works() {
188            let dex = "junoswap";
189            let assets = vec!["uusd".to_string(), "uust".to_string()];
190            let actual = PoolMetadata::liquidity_bootstrap(dex, assets.clone());
191
192            let expected = PoolMetadata {
193                dex: dex.to_string(),
194                pool_type: PoolType::LiquidityBootstrap,
195                assets: assets.into_iter().map(|a| a.into()).collect(),
196            };
197            assert_that!(actual).is_equal_to(expected);
198        }
199    }
200
201    #[test]
202    fn test_pool_metadata_from_str() {
203        let pool_metadata_str = "junoswap/uusd,uust:stable";
204        let pool_metadata = PoolMetadata::from_str(pool_metadata_str).unwrap();
205
206        assert_eq!(pool_metadata.dex, "junoswap");
207        assert_eq!(
208            pool_metadata.assets,
209            vec!["uusd", "uust"]
210                .into_iter()
211                .map(AssetEntry::from)
212                .collect::<Vec<AssetEntry>>()
213        );
214        assert_eq!(pool_metadata.pool_type, PoolType::Stable);
215
216        // Wrong formatting
217        let pool_metadata_str = "junoswap:uusd,uust/stable";
218        let err = PoolMetadata::from_str(pool_metadata_str).unwrap_err();
219
220        assert_eq!(err, StdError::generic_err(format!(
221            "invalid pool metadata format `{pool_metadata_str}`; must be in format `{{dex}}{TYPE_DELIMITER}{{asset1}},{{asset2}}{ATTRIBUTE_DELIMITER}{{pool_type}}...`"
222        )));
223    }
224
225    #[test]
226    fn test_pool_metadata_to_string() {
227        let pool_metadata_str = "junoswap/uusd,uust:weighted";
228        let pool_metadata = PoolMetadata::from_str(pool_metadata_str).unwrap();
229
230        assert_eq!(pool_metadata.to_string(), pool_metadata_str);
231    }
232}