abstract_os/objects/
lp_token.rs1use crate::{
2 constants::{ASSET_DELIMITER, TYPE_DELIMITER},
3 objects::{AssetEntry, PoolMetadata},
4};
5use cosmwasm_std::StdError;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::fmt::Display;
9
10pub type DexName = String;
11
12#[derive(
15 Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema, PartialOrd, Ord, Default,
16)]
17pub struct LpToken {
18 pub dex: DexName,
19 pub assets: Vec<AssetEntry>,
20}
21
22impl LpToken {
23 pub fn new<T: ToString, U: Into<AssetEntry> + Clone>(dex_name: T, assets: Vec<U>) -> Self {
24 Self {
25 dex: dex_name.to_string(),
26 assets: assets.into_iter().map(|a| Into::into(a)).collect(),
27 }
28 }
29}
30
31impl TryFrom<AssetEntry> for LpToken {
33 type Error = StdError;
34
35 fn try_from(asset: AssetEntry) -> Result<Self, Self::Error> {
36 let segments = asset.as_str().split(TYPE_DELIMITER).collect::<Vec<_>>();
37
38 if segments.len() != 2 {
39 return Err(StdError::generic_err(format!(
40 "Invalid asset entry: {asset}"
41 )));
42 }
43
44 let dex_name = segments[0].to_string();
46
47 let assets: Vec<AssetEntry> = segments[1]
49 .split(ASSET_DELIMITER)
50 .map(AssetEntry::from)
51 .collect();
52
53 if assets.len() < 2 {
54 return Err(StdError::generic_err(format!(
55 "Must be at least 2 assets in an LP token: {asset}"
56 )));
57 }
58
59 Ok(Self {
60 dex: dex_name,
61 assets,
62 })
63 }
64}
65
66impl Display for LpToken {
68 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69 let assets = self
70 .assets
71 .iter()
72 .map(|a| a.as_str())
73 .collect::<Vec<&str>>()
74 .join(ASSET_DELIMITER);
75
76 write!(f, "{}{}{}", self.dex, TYPE_DELIMITER, assets)
77 }
78}
79
80impl From<LpToken> for AssetEntry {
81 fn from(lp_token: LpToken) -> Self {
82 AssetEntry::from(lp_token.to_string())
83 }
84}
85
86impl From<PoolMetadata> for LpToken {
88 fn from(pool: PoolMetadata) -> Self {
89 Self {
90 dex: pool.dex,
91 assets: pool.assets,
92 }
93 }
94}
95
96#[cfg(test)]
97mod test {
98 use super::*;
99 use speculoos::prelude::*;
100
101 mod implementation {
102 use super::*;
103
104 #[test]
105 fn new_works() {
106 let dex_name = "junoswap";
107 let assets = vec![AssetEntry::from("crab"), AssetEntry::from("junox")];
108 let actual = LpToken::new(dex_name, assets.clone());
109
110 let expected = LpToken {
111 dex: dex_name.to_string(),
112 assets,
113 };
114 assert_that!(actual).is_equal_to(expected);
115 }
116
117 #[test]
118 fn assets_returns_asset_entries() {
119 let dex_name = "junoswap";
120 let assets = vec![AssetEntry::from("crab"), AssetEntry::from("junox")];
121 let lp_token = LpToken::new(dex_name, assets);
122 let expected = vec![AssetEntry::from("crab"), AssetEntry::from("junox")];
123
124 assert_that!(lp_token.assets).is_equal_to(expected);
125 }
126 }
127
128 mod from_asset_entry {
129 use super::*;
130
131 #[test]
132 fn test_from_asset_entry() {
133 let lp_token = LpToken::try_from(AssetEntry::new("junoswap/crab,junox")).unwrap();
134 assert_that!(lp_token.dex).is_equal_to("junoswap".to_string());
135 assert_that!(lp_token.assets)
136 .is_equal_to(vec![AssetEntry::from("crab"), AssetEntry::from("junox")]);
137 }
138
139 #[test]
140 fn test_from_invalid_asset_entry() {
141 let lp_token = LpToken::try_from(AssetEntry::new("junoswap/"));
142 assert_that!(&lp_token).is_err();
143 }
144
145 #[test]
146 fn test_fewer_than_two_assets() {
147 let lp_token = LpToken::try_from(AssetEntry::new("junoswap/crab"));
148 assert_that!(&lp_token).is_err();
149 }
150 }
151
152 mod into_asset_entry {
153 use super::*;
154
155 #[test]
156 fn into_asset_entry_works() {
157 let lp_token = LpToken::new("junoswap", vec!["crab".to_string(), "junox".to_string()]);
158 let expected = AssetEntry::new("junoswap/crab,junox");
159
160 assert_that!(lp_token.into()).is_equal_to(expected);
161 }
162 }
163
164 mod from_pool_metadata {
165 use super::*;
166 use crate::objects::PoolType;
167
168 #[test]
169 fn test_from_pool_metadata() {
170 let assets: Vec<AssetEntry> = vec!["crab".into(), "junox".into()];
171 let dex = "junoswap".to_string();
172
173 let pool = PoolMetadata {
174 dex: dex.clone(),
175 pool_type: PoolType::Stable,
176 assets: assets.clone(),
177 };
178 let lp_token = LpToken::from(pool);
179 assert_that!(lp_token.dex).is_equal_to(dex);
180 assert_that!(lp_token.assets).is_equal_to(assets);
181 }
182 }
183}