abstract_std/objects/entry/
lp_token.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
use std::fmt::Display;

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::{
    constants::{ASSET_DELIMITER, TYPE_DELIMITER},
    objects::AssetEntry,
};

pub type DexName = String;

/// A key for the token that represents Liquidity Pool shares on a dex
/// Will be formatted as "dex_name/asset1,asset2" when serialized
#[derive(
    Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema, PartialOrd, Ord, Default,
)]
pub struct LpToken {
    pub dex: DexName,
    pub assets: Vec<AssetEntry>,
}

impl LpToken {
    pub fn new<T: ToString, U: Into<AssetEntry> + Clone>(dex_name: T, assets: Vec<U>) -> Self {
        let mut assets = assets
            .into_iter()
            .map(|a| a.into())
            .collect::<Vec<AssetEntry>>();
        // sort the asset name
        assets.sort_unstable();
        Self {
            dex: dex_name.to_string(),
            assets,
        }
    }
}

/// Transform into a string formatted as "dex_name/asset1,asset2"
impl Display for LpToken {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let assets = self
            .assets
            .iter()
            .map(|a| a.as_str())
            .collect::<Vec<&str>>()
            .join(ASSET_DELIMITER);

        write!(f, "{}{}{}", self.dex, TYPE_DELIMITER, assets)
    }
}

#[cfg(test)]
mod test {
    #![allow(clippy::needless_borrows_for_generic_args)]

    use super::*;

    mod implementation {
        use super::*;

        #[coverage_helper::test]
        fn new_works() {
            let dex_name = "junoswap";
            let mut assets = vec![AssetEntry::from("junox"), AssetEntry::from("crab")];
            let actual = LpToken::new(dex_name, assets.clone());
            assets.sort();
            let expected = LpToken {
                dex: dex_name.to_string(),
                assets,
            };
            assert_eq!(actual, expected);
        }

        #[coverage_helper::test]
        fn assets_returns_asset_entries() {
            let dex_name = "junoswap";
            let assets = vec![AssetEntry::from("crab"), AssetEntry::from("junox")];
            let lp_token = LpToken::new(dex_name, assets);
            let expected = vec![AssetEntry::from("crab"), AssetEntry::from("junox")];

            assert_eq!(lp_token.assets, expected);
        }
    }

    mod from_asset_entry {
        use super::*;
        use crate::objects::AnsEntryConvertor;

        #[coverage_helper::test]
        fn test_from_asset_entry() {
            let asset = AssetEntry::new("junoswap/crab,junox");
            let lp_token = AnsEntryConvertor::new(asset).lp_token().unwrap();
            assert_eq!(lp_token.dex, "junoswap".to_string());
            assert_eq!(
                lp_token.assets,
                vec![AssetEntry::from("crab"), AssetEntry::from("junox")]
            );
        }

        #[coverage_helper::test]
        fn test_from_invalid_asset_entry() {
            let asset = AssetEntry::new("junoswap/");
            let lp_token = AnsEntryConvertor::new(asset).lp_token();
            assert!(lp_token.is_err());
        }

        #[coverage_helper::test]
        fn test_fewer_than_two_assets() {
            let asset = AssetEntry::new("junoswap/crab");
            let lp_token = AnsEntryConvertor::new(asset).lp_token();
            assert!(lp_token.is_err());
        }
    }

    mod into_asset_entry {
        use super::*;
        use crate::objects::AnsEntryConvertor;

        #[coverage_helper::test]
        fn into_asset_entry_works() {
            let lp_token = LpToken::new("junoswap", vec!["crab".to_string(), "junox".to_string()]);
            let expected = AssetEntry::new("junoswap/crab,junox");

            assert_eq!(AnsEntryConvertor::new(lp_token).asset_entry(), expected);
        }
    }

    mod from_pool_metadata {
        use super::*;
        use crate::objects::{AnsEntryConvertor, PoolMetadata, PoolType};

        #[coverage_helper::test]
        fn test_from_pool_metadata() {
            let assets: Vec<AssetEntry> = vec!["crab".into(), "junox".into()];
            let dex = "junoswap".to_string();

            let pool = PoolMetadata {
                dex: dex.clone(),
                pool_type: PoolType::Stable,
                assets: assets.clone(),
            };

            let lp_token = AnsEntryConvertor::new(pool).lp_token();
            assert_eq!(lp_token.dex, dex);
            assert_eq!(lp_token.assets, assets);
        }
    }
}