1use crate::asset::Asset;
2use crate::asset::AssetBehavior;
3use crate::get_liquidity_pool::LiquidityPool;
4use crate::get_liquidity_pool::LiquidityPoolBehavior;
5use crate::xdr;
6const LIQUIDITY_POOL_FEE_V18: i32 = 30;
7#[derive(Debug)]
8pub struct LiquidityPoolAsset {
9 asset_a: Asset,
10 asset_b: Asset,
11 fee: i32,
12}
13
14impl From<&LiquidityPoolAsset> for xdr::TrustLineAsset {
16 fn from(value: &LiquidityPoolAsset) -> Self {
17 let pool_id = LiquidityPool::get_liquidity_pool_id(
18 "constant_product",
19 value.get_liquidity_pool_parameters().clone(),
20 )
21 .unwrap();
22 xdr::TrustLineAsset::PoolShare(xdr::PoolId(xdr::Hash(*pool_id.last_chunk::<32>().unwrap())))
23 }
24}
25impl From<LiquidityPoolAsset> for xdr::TrustLineAsset {
27 fn from(value: LiquidityPoolAsset) -> Self {
28 let pool_id = LiquidityPool::get_liquidity_pool_id(
29 "constant_product",
30 value.get_liquidity_pool_parameters().clone(),
31 )
32 .unwrap();
33 xdr::TrustLineAsset::PoolShare(xdr::PoolId(xdr::Hash(*pool_id.last_chunk::<32>().unwrap())))
34 }
35}
36impl From<&LiquidityPoolAsset> for xdr::ChangeTrustAsset {
37 fn from(value: &LiquidityPoolAsset) -> Self {
38 value.to_xdr_object()
39 }
40}
41impl From<LiquidityPoolAsset> for xdr::ChangeTrustAsset {
42 fn from(value: LiquidityPoolAsset) -> Self {
43 value.to_xdr_object()
44 }
45}
46
47pub trait LiquidityPoolAssetBehavior {
49 fn new(asset_a: Asset, asset_b: Asset, fee: i32) -> Result<Self, &'static str>
50 where
51 Self: Sized;
52 fn from_operation(ct_asset_xdr: &xdr::ChangeTrustAsset) -> Result<Self, String>
53 where
54 Self: Sized;
55 fn to_xdr_object(&self) -> xdr::ChangeTrustAsset;
56 fn get_liquidity_pool_parameters(&self) -> xdr::LiquidityPoolParameters;
57 fn equals(&self, other: &Self) -> bool;
58 fn get_asset_type(&self) -> &'static str;
59 fn to_string(&self) -> String;
60}
61
62impl LiquidityPoolAssetBehavior for LiquidityPoolAsset {
63 fn new(asset_a: Asset, asset_b: Asset, fee: i32) -> Result<Self, &'static str> {
64 if Asset::compare(&asset_a, &asset_b) != -1 {
65 return Err("Assets are not in lexicographic order");
66 }
67 if fee != LIQUIDITY_POOL_FEE_V18 {
68 return Err("fee is invalid");
69 }
70
71 Ok(LiquidityPoolAsset {
72 asset_a,
73 asset_b,
74 fee,
75 })
76 }
77
78 fn from_operation(ct_asset_xdr: &xdr::ChangeTrustAsset) -> Result<LiquidityPoolAsset, String> {
79 match ct_asset_xdr {
80 xdr::ChangeTrustAsset::PoolShare(x) => {
81 let xdr::LiquidityPoolParameters::LiquidityPoolConstantProduct(val) = x;
82
83 let asset_a = Asset::from_operation(val.asset_a.clone()).unwrap();
84 let asset_b = Asset::from_operation(val.asset_b.clone()).unwrap();
85 let fee = val.fee;
86 Ok(LiquidityPoolAsset::new(asset_a, asset_b, fee)?)
87 }
88
89 _ => Err("Invalid asset type".to_string()),
90 }
91 }
92
93 fn to_xdr_object(&self) -> xdr::ChangeTrustAsset {
94 let lp_constant_product_params_xdr = xdr::LiquidityPoolConstantProductParameters {
95 asset_a: self.asset_a.to_xdr_object(),
96 asset_b: self.asset_b.to_xdr_object(),
97 fee: self.fee,
98 };
99
100 let lp_params_xdr = xdr::LiquidityPoolParameters::LiquidityPoolConstantProduct(
101 lp_constant_product_params_xdr,
102 );
103 xdr::ChangeTrustAsset::PoolShare(lp_params_xdr)
104 }
105
106 fn get_liquidity_pool_parameters(&self) -> xdr::LiquidityPoolParameters {
107 let lp_constant_product_params_xdr = xdr::LiquidityPoolConstantProductParameters {
108 asset_a: self.asset_a.to_xdr_object(),
109 asset_b: self.asset_b.to_xdr_object(),
110 fee: self.fee,
111 };
112
113 xdr::LiquidityPoolParameters::LiquidityPoolConstantProduct(lp_constant_product_params_xdr)
114 }
115
116 fn equals(&self, other: &LiquidityPoolAsset) -> bool {
117 self.asset_a == other.asset_a && self.asset_b == other.asset_b && self.fee == other.fee
118 }
119
120 fn get_asset_type(&self) -> &'static str {
121 "liquidity_pool_shares"
122 }
123
124 fn to_string(&self) -> String {
125 let pool_id = LiquidityPool::get_liquidity_pool_id(
126 "constant_product",
127 self.get_liquidity_pool_parameters().clone(),
128 )
129 .unwrap();
130 format!("liquidity_pool:{}", hex::encode(pool_id))
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use xdr::AlphaNum4;
137
138 use super::*;
139
140 #[test]
141 fn correct_attributes_does_not_panic() {
142 const LIQUIDITY_POOL_FEE_V18: i32 = 30;
143 let asset_a = Asset::new(
144 "ARST",
145 Some("GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO"),
146 )
147 .unwrap();
148 let asset_b = Asset::new(
149 "USD",
150 Some("GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ"),
151 )
152 .unwrap();
153 let fee = LIQUIDITY_POOL_FEE_V18;
154
155 let _ = LiquidityPoolAsset::new(asset_a, asset_b, fee);
156 }
157
158 #[test]
159 fn returns_liquidity_pool_parameters_for_liquidity_pool_asset() {
160 let asset_a = Asset::new(
161 "ARST",
162 Some("GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO"),
163 )
164 .unwrap();
165 let asset_b = Asset::new(
166 "USD",
167 Some("GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ"),
168 )
169 .unwrap();
170 let fee = LIQUIDITY_POOL_FEE_V18;
171
172 let asset = LiquidityPoolAsset::new(asset_a.clone(), asset_b.clone(), fee).unwrap();
173
174 let got_pool_params = asset.get_liquidity_pool_parameters();
175 let xdr::LiquidityPoolParameters::LiquidityPoolConstantProduct(val) = got_pool_params;
176 assert_eq!(val.asset_a, asset_a.to_xdr_object());
177 assert_eq!(val.asset_b, asset_b.to_xdr_object());
178 assert_eq!(val.fee, fee);
179 }
180
181 #[test]
182 fn returns_liquidity_pool_shares_for_trustline_asset() {
183 let asset_a = Asset::new(
184 "ARST",
185 Some("GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO"),
186 )
187 .unwrap();
188 let asset_b = Asset::new(
189 "USD",
190 Some("GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ"),
191 )
192 .unwrap();
193 let fee = LIQUIDITY_POOL_FEE_V18;
194
195 let asset = LiquidityPoolAsset::new(asset_a, asset_b, fee).unwrap();
196
197 assert_eq!(asset.get_asset_type(), "liquidity_pool_shares");
198 }
199
200 #[test]
201 fn to_xdr_object_parses_liquidity_pool_trustline_asset_object() {
202 let asset_a = Asset::new(
203 "ARST",
204 Some("GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO"),
205 )
206 .unwrap();
207 let asset_b = Asset::new(
208 "USD",
209 Some("GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ"),
210 )
211 .unwrap();
212 let fee = LIQUIDITY_POOL_FEE_V18;
213 let asset = LiquidityPoolAsset::new(asset_a.clone(), asset_b.clone(), fee).unwrap();
214 let xdr = asset.to_xdr_object();
215
216 let val = match xdr {
217 xdr::ChangeTrustAsset::PoolShare(x) => x,
218 _ => panic!("Expected LiquidityPool variant"),
219 };
220
221 let got_pool_params: xdr::LiquidityPoolParameters = asset.get_liquidity_pool_parameters();
222 let xdr::LiquidityPoolParameters::LiquidityPoolConstantProduct(val) = got_pool_params;
223 assert_eq!(Asset::from_operation(val.asset_a).unwrap(), asset_a);
224 assert_eq!(Asset::from_operation(val.asset_b).unwrap(), asset_b);
225 assert_eq!(val.fee, fee);
226 }
227
228 #[test]
229 fn from_operation_throws_error_for_native_asset_type() {
230 let xdr = xdr::ChangeTrustAsset::Native;
231
232 let result = LiquidityPoolAsset::from_operation(&xdr).unwrap_err();
233
234 let val = "Invalid asset type".to_string();
235
236 if val != result {
237 panic!("Expected error with message containing 'Invalid asset type: assetTypeNative'")
238 }
239 }
240
241 #[test]
242 fn test_invalid_asset_type_credit_alphanum4() {
243 let issuer = "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ";
244 let asset_code = "KHL";
245
246 let asset_xdr = Asset::new("KHL", Some(issuer)).unwrap().to_xdr_object();
247 let c = match asset_xdr {
248 xdr::Asset::CreditAlphanum4(x) => x,
249 _ => panic!("Wrong Type:"),
250 };
251
252 let vval: xdr::ChangeTrustAsset = xdr::ChangeTrustAsset::CreditAlphanum4(c);
253
254 match LiquidityPoolAsset::from_operation(&vval) {
255 Ok(_) => panic!("Expected an error for assetTypeCreditAlphanum4, but got Ok"),
256 Err(e) => assert_eq!(e.to_string(), "Invalid asset type"),
257 }
258 }
259
260 #[test]
261 fn test_invalid_asset_type_credit_alphanum12() {
262 let issuer = "GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ";
263 let asset_code = "KHLTOKEN";
264
265 let asset_xdr = Asset::new(asset_code, Some(issuer))
266 .unwrap()
267 .to_xdr_object();
268 let c = match asset_xdr {
269 xdr::Asset::CreditAlphanum12(x) => x,
270 _ => panic!("Wrong Type:"),
271 };
272
273 let vval: xdr::ChangeTrustAsset = xdr::ChangeTrustAsset::CreditAlphanum12(c);
274
275 match LiquidityPoolAsset::from_operation(&vval) {
276 Ok(_) => panic!("Expected an error for assetTypeCreditAlphanum12, but got Ok"),
277 Err(e) => assert_eq!(e.to_string(), "Invalid asset type"),
278 }
279 }
280
281 #[test]
282 fn test_parses_liquidity_pool_asset_xdr() {
283 let asset_a = Asset::new(
284 "ARST",
285 Some("GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO"),
286 )
287 .unwrap();
288 let asset_b = Asset::new(
289 "USD",
290 Some("GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ"),
291 )
292 .unwrap();
293 let fee = LIQUIDITY_POOL_FEE_V18;
294
295 let lp_constant_product_params_xdr = xdr::LiquidityPoolConstantProductParameters {
296 asset_a: asset_a.to_xdr_object(),
297 asset_b: asset_b.to_xdr_object(),
298 fee,
299 };
300
301 let lp_params_xdr = xdr::LiquidityPoolParameters::LiquidityPoolConstantProduct(
302 lp_constant_product_params_xdr,
303 );
304 let xdr = xdr::ChangeTrustAsset::PoolShare(lp_params_xdr);
305
306 let asset = LiquidityPoolAsset::from_operation(&xdr).expect("Expected successful parsing");
307 let got_pool_params = asset.get_liquidity_pool_parameters();
308 let xdr::LiquidityPoolParameters::LiquidityPoolConstantProduct(x) = got_pool_params;
309 assert_eq!(x.asset_a, asset_a.to_xdr_object());
310 assert_eq!(x.asset_b, asset_b.to_xdr_object());
311 assert_eq!(x.fee, fee);
312 }
313
314 #[test]
315 fn test_assets_are_different() {
316 let asset_a = Asset::new(
317 "ARST",
318 Some("GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO"),
319 )
320 .unwrap();
321 let asset_b = Asset::new(
322 "USD",
323 Some("GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ"),
324 )
325 .unwrap();
326 let fee = LIQUIDITY_POOL_FEE_V18;
327
328 let lp_asset1 = LiquidityPoolAsset::new(asset_a.clone(), asset_b.clone(), fee).unwrap();
329
330 let asset_a2 = Asset::new(
331 "ARS2",
332 Some("GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO"),
333 )
334 .unwrap();
335 let asset_b2 = Asset::new(
336 "USD2",
337 Some("GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ"),
338 )
339 .unwrap();
340
341 let mut lp_asset2 =
342 LiquidityPoolAsset::new(asset_a2, asset_b2.clone(), LIQUIDITY_POOL_FEE_V18).unwrap();
343 assert!(!lp_asset1.equals(&lp_asset2));
344
345 lp_asset2 = LiquidityPoolAsset::new(asset_a, asset_b2, fee).unwrap();
346 assert!(!lp_asset1.equals(&lp_asset2));
347 }
348
349 #[test]
350 fn test_to_string() {
351 let asset_a = Asset::new(
352 "ARST",
353 Some("GB7TAYRUZGE6TVT7NHP5SMIZRNQA6PLM423EYISAOAP3MKYIQMVYP2JO"),
354 )
355 .unwrap();
356 let asset_b = Asset::new(
357 "USD",
358 Some("GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ"),
359 )
360 .unwrap();
361 let fee = LIQUIDITY_POOL_FEE_V18;
362
363 let asset = LiquidityPoolAsset::new(asset_a, asset_b, fee).unwrap();
364 assert_eq!(
365 asset.to_string(),
366 "liquidity_pool:dd7b1ab831c273310ddbec6f97870aa83c2fbd78ce22aded37ecbf4f3380fac7"
367 );
368 }
369}