stellar_baselib/
liquidity_pool_asset.rs

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
14// TODO: fix that
15impl 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}
25// TODO: fix that
26impl 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
47// Define a trait for LiquidityPoolAsset behavior
48pub 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}