abstract_std/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
121    use super::*;
122
123    mod implementation {
124        use super::*;
125
126        #[coverage_helper::test]
127        fn new_works() {
128            let dex = "junoswap";
129            let pool_type = PoolType::Stable;
130            let mut assets = vec!["uust".to_string(), "uusd".to_string()];
131            let actual = PoolMetadata::new(dex, pool_type, assets.clone());
132            // sort the asset names
133            assets.sort();
134            let expected = PoolMetadata {
135                dex: dex.to_string(),
136                pool_type,
137                assets: assets.into_iter().map(|a| a.into()).collect(),
138            };
139            assert_eq!(actual, expected);
140            assert_eq!(actual.to_string(), "junoswap/uusd,uust:stable".to_string());
141        }
142
143        #[coverage_helper::test]
144        fn stable_works() {
145            let dex = "junoswap";
146            let assets = vec!["uusd".to_string(), "uust".to_string()];
147            let actual = PoolMetadata::stable(dex, assets.clone());
148
149            let expected = PoolMetadata {
150                dex: dex.to_string(),
151                pool_type: PoolType::Stable,
152                assets: assets.into_iter().map(|a| a.into()).collect(),
153            };
154            assert_eq!(actual, expected);
155        }
156
157        #[coverage_helper::test]
158        fn weighted_works() {
159            let dex = "junoswap";
160            let assets = vec!["uusd".to_string(), "uust".to_string()];
161            let actual = PoolMetadata::weighted(dex, assets.clone());
162
163            let expected = PoolMetadata {
164                dex: dex.to_string(),
165                pool_type: PoolType::Weighted,
166                assets: assets.into_iter().map(|a| a.into()).collect(),
167            };
168            assert_eq!(actual, expected);
169        }
170
171        #[coverage_helper::test]
172        fn constant_product_works() {
173            let dex = "junoswap";
174            let assets = vec!["uusd".to_string(), "uust".to_string()];
175            let actual = PoolMetadata::constant_product(dex, assets.clone());
176
177            let expected = PoolMetadata {
178                dex: dex.to_string(),
179                pool_type: PoolType::ConstantProduct,
180                assets: assets.into_iter().map(|a| a.into()).collect(),
181            };
182            assert_eq!(actual, expected);
183        }
184
185        #[coverage_helper::test]
186        fn liquidity_bootstrap_works() {
187            let dex = "junoswap";
188            let assets = vec!["uusd".to_string(), "uust".to_string()];
189            let actual = PoolMetadata::liquidity_bootstrap(dex, assets.clone());
190
191            let expected = PoolMetadata {
192                dex: dex.to_string(),
193                pool_type: PoolType::LiquidityBootstrap,
194                assets: assets.into_iter().map(|a| a.into()).collect(),
195            };
196            assert_eq!(actual, expected);
197        }
198    }
199
200    #[coverage_helper::test]
201    fn test_pool_metadata_from_str() {
202        let pool_metadata_str = "junoswap/uusd,uust:stable";
203        let pool_metadata = PoolMetadata::from_str(pool_metadata_str).unwrap();
204
205        assert_eq!(pool_metadata.dex, "junoswap");
206        assert_eq!(
207            pool_metadata.assets,
208            vec!["uusd", "uust"]
209                .into_iter()
210                .map(AssetEntry::from)
211                .collect::<Vec<AssetEntry>>()
212        );
213        assert_eq!(pool_metadata.pool_type, PoolType::Stable);
214
215        // Wrong formatting
216        let pool_metadata_str = "junoswap:uusd,uust/stable";
217        let err = PoolMetadata::from_str(pool_metadata_str).unwrap_err();
218
219        assert_eq!(err, StdError::generic_err(format!(
220            "invalid pool metadata format `{pool_metadata_str}`; must be in format `{{dex}}{TYPE_DELIMITER}{{asset1}},{{asset2}}{ATTRIBUTE_DELIMITER}{{pool_type}}...`"
221        )));
222    }
223
224    #[coverage_helper::test]
225    fn test_pool_metadata_to_string() {
226        let pool_metadata_str = "junoswap/uusd,uust:weighted";
227        let pool_metadata = PoolMetadata::from_str(pool_metadata_str).unwrap();
228
229        assert_eq!(pool_metadata.to_string(), pool_metadata_str);
230    }
231}