abstract_std/objects/entry/
lp_token.rs

1use std::fmt::Display;
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    constants::{ASSET_DELIMITER, TYPE_DELIMITER},
8    objects::AssetEntry,
9};
10
11pub type DexName = String;
12
13/// A key for the token that represents Liquidity Pool shares on a dex
14/// Will be formatted as "dex_name/asset1,asset2" when serialized
15#[derive(
16    Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema, PartialOrd, Ord, Default,
17)]
18pub struct LpToken {
19    pub dex: DexName,
20    pub assets: Vec<AssetEntry>,
21}
22
23impl LpToken {
24    pub fn new<T: ToString, U: Into<AssetEntry> + Clone>(dex_name: T, assets: Vec<U>) -> Self {
25        let mut assets = assets
26            .into_iter()
27            .map(|a| a.into())
28            .collect::<Vec<AssetEntry>>();
29        // sort the asset name
30        assets.sort_unstable();
31        Self {
32            dex: dex_name.to_string(),
33            assets,
34        }
35    }
36}
37
38/// Transform into a string formatted as "dex_name/asset1,asset2"
39impl Display for LpToken {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        let assets = self
42            .assets
43            .iter()
44            .map(|a| a.as_str())
45            .collect::<Vec<&str>>()
46            .join(ASSET_DELIMITER);
47
48        write!(f, "{}{}{}", self.dex, TYPE_DELIMITER, assets)
49    }
50}
51
52#[cfg(test)]
53mod test {
54    #![allow(clippy::needless_borrows_for_generic_args)]
55
56    use super::*;
57
58    mod implementation {
59        use super::*;
60
61        #[coverage_helper::test]
62        fn new_works() {
63            let dex_name = "junoswap";
64            let mut assets = vec![AssetEntry::from("junox"), AssetEntry::from("crab")];
65            let actual = LpToken::new(dex_name, assets.clone());
66            assets.sort();
67            let expected = LpToken {
68                dex: dex_name.to_string(),
69                assets,
70            };
71            assert_eq!(actual, expected);
72        }
73
74        #[coverage_helper::test]
75        fn assets_returns_asset_entries() {
76            let dex_name = "junoswap";
77            let assets = vec![AssetEntry::from("crab"), AssetEntry::from("junox")];
78            let lp_token = LpToken::new(dex_name, assets);
79            let expected = vec![AssetEntry::from("crab"), AssetEntry::from("junox")];
80
81            assert_eq!(lp_token.assets, expected);
82        }
83    }
84
85    mod from_asset_entry {
86        use super::*;
87        use crate::objects::AnsEntryConvertor;
88
89        #[coverage_helper::test]
90        fn test_from_asset_entry() {
91            let asset = AssetEntry::new("junoswap/crab,junox");
92            let lp_token = AnsEntryConvertor::new(asset).lp_token().unwrap();
93            assert_eq!(lp_token.dex, "junoswap".to_string());
94            assert_eq!(
95                lp_token.assets,
96                vec![AssetEntry::from("crab"), AssetEntry::from("junox")]
97            );
98        }
99
100        #[coverage_helper::test]
101        fn test_from_invalid_asset_entry() {
102            let asset = AssetEntry::new("junoswap/");
103            let lp_token = AnsEntryConvertor::new(asset).lp_token();
104            assert!(lp_token.is_err());
105        }
106
107        #[coverage_helper::test]
108        fn test_fewer_than_two_assets() {
109            let asset = AssetEntry::new("junoswap/crab");
110            let lp_token = AnsEntryConvertor::new(asset).lp_token();
111            assert!(lp_token.is_err());
112        }
113    }
114
115    mod into_asset_entry {
116        use super::*;
117        use crate::objects::AnsEntryConvertor;
118
119        #[coverage_helper::test]
120        fn into_asset_entry_works() {
121            let lp_token = LpToken::new("junoswap", vec!["crab".to_string(), "junox".to_string()]);
122            let expected = AssetEntry::new("junoswap/crab,junox");
123
124            assert_eq!(AnsEntryConvertor::new(lp_token).asset_entry(), expected);
125        }
126    }
127
128    mod from_pool_metadata {
129        use super::*;
130        use crate::objects::{AnsEntryConvertor, PoolMetadata, PoolType};
131
132        #[coverage_helper::test]
133        fn test_from_pool_metadata() {
134            let assets: Vec<AssetEntry> = vec!["crab".into(), "junox".into()];
135            let dex = "junoswap".to_string();
136
137            let pool = PoolMetadata {
138                dex: dex.clone(),
139                pool_type: PoolType::Stable,
140                assets: assets.clone(),
141            };
142
143            let lp_token = AnsEntryConvertor::new(pool).lp_token();
144            assert_eq!(lp_token.dex, dex);
145            assert_eq!(lp_token.assets, assets);
146        }
147    }
148}