abstract_core/objects/entry/
dex_asset_pairing.rs

1use std::{convert::TryInto, fmt::Display};
2
3use cosmwasm_std::{StdError, StdResult};
4use cw_storage_plus::{KeyDeserialize, Prefixer, PrimaryKey};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use crate::{
9    constants::{ASSET_DELIMITER, TYPE_DELIMITER},
10    objects::AssetEntry,
11};
12
13type DexName = String;
14
15/// The key for an asset pairing
16/// Consists of the two assets and the dex name
17/// TODO: what if we made keys equal based on the two assets either way?
18#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema, PartialOrd, Ord)]
19pub struct DexAssetPairing<Asset = AssetEntry>((Asset, Asset, DexName));
20
21impl<Asset> DexAssetPairing<Asset> {
22    pub fn new(asset_x: Asset, asset_y: Asset, dex_name: &str) -> Self {
23        Self((asset_x, asset_y, str::to_ascii_lowercase(dex_name)))
24    }
25
26    pub fn asset_x(&self) -> &Asset {
27        &self.0 .0
28    }
29
30    pub fn asset_y(&self) -> &Asset {
31        &self.0 .1
32    }
33
34    pub fn dex(&self) -> &str {
35        &self.0 .2
36    }
37}
38
39impl Display for DexAssetPairing {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(
42            f,
43            "{}{TYPE_DELIMITER}{}{ASSET_DELIMITER}{}",
44            self.dex(),
45            self.asset_x(),
46            self.asset_y()
47        )
48    }
49}
50
51impl<'a> PrimaryKey<'a> for &DexAssetPairing {
52    type Prefix = (&'a AssetEntry, &'a AssetEntry);
53    type SubPrefix = &'a AssetEntry;
54    type Suffix = DexName;
55    type SuperSuffix = (&'a AssetEntry, DexName);
56
57    fn key(&self) -> Vec<cw_storage_plus::Key> {
58        let mut key = self.0 .0 .0.key();
59        key.extend(self.0 .1 .0.key());
60        key.extend(self.0 .2.key());
61        key
62    }
63}
64
65impl<'a> Prefixer<'a> for &DexAssetPairing {
66    fn prefix(&self) -> Vec<cw_storage_plus::Key> {
67        let mut res = self.0 .0 .0.prefix();
68        res.extend(self.0 .1 .0.prefix());
69        res.extend(self.0 .2.prefix());
70        res
71    }
72}
73
74fn parse_length(value: &[u8]) -> StdResult<usize> {
75    Ok(u16::from_be_bytes(
76        value
77            .try_into()
78            .map_err(|_| StdError::generic_err("Could not read 2 byte length"))?,
79    )
80    .into())
81}
82
83/// @todo: use existing method for triple tuple
84impl KeyDeserialize for &DexAssetPairing {
85    type Output = DexAssetPairing;
86
87    #[inline(always)]
88    fn from_vec(mut value: Vec<u8>) -> StdResult<Self::Output> {
89        let mut tuv = value.split_off(2);
90        let t_len = parse_length(&value)?;
91        let mut len_uv = tuv.split_off(t_len);
92
93        let mut uv = len_uv.split_off(2);
94        let u_len = parse_length(&len_uv)?;
95        let v = uv.split_off(u_len);
96
97        Ok(DexAssetPairing::new(
98            String::from_vec(tuv)?.into(),
99            String::from_vec(uv)?.into(),
100            &String::from_vec(v)?,
101        ))
102    }
103}
104
105#[cfg(test)]
106mod test {
107    use cosmwasm_std::{testing::mock_dependencies, Addr, Order};
108    use cw_storage_plus::Map;
109
110    use super::*;
111    use crate::objects::{AnsEntryConvertor, LpToken, PoolReference, UniquePoolId};
112
113    fn mock_key() -> DexAssetPairing {
114        DexAssetPairing::new("juno".into(), "osmo".into(), "junoswap")
115    }
116
117    fn mock_keys() -> (DexAssetPairing, DexAssetPairing, DexAssetPairing) {
118        (
119            DexAssetPairing::new("juno".into(), "osmo".into(), "junoswap"),
120            DexAssetPairing::new("juno".into(), "osmo".into(), "osmosis"),
121            DexAssetPairing::new("osmo".into(), "usdt".into(), "osmosis"),
122        )
123    }
124
125    fn mock_pool_ref(id: u64, name: &str) -> PoolReference {
126        PoolReference {
127            unique_id: UniquePoolId::new(id),
128            pool_address: Addr::unchecked(name).into(),
129        }
130    }
131
132    #[test]
133    fn storage_key_works() {
134        let mut deps = mock_dependencies();
135        let key = mock_key();
136        let map: Map<&DexAssetPairing, u64> = Map::new("map");
137
138        map.save(deps.as_mut().storage, &key, &42069).unwrap();
139
140        assert_eq!(map.load(deps.as_ref().storage, &key).unwrap(), 42069);
141
142        let items = map
143            .range(deps.as_ref().storage, None, None, Order::Ascending)
144            .map(|item| item.unwrap())
145            .collect::<Vec<_>>();
146
147        assert_eq!(items.len(), 1);
148        assert_eq!(items[0], (key, 42069));
149    }
150
151    #[test]
152    fn composite_key_works() {
153        let mut deps = mock_dependencies();
154        let key = mock_key();
155        let map: Map<(&DexAssetPairing, Addr), Vec<PoolReference>> = Map::new("map");
156
157        let ref_1 = mock_pool_ref(1, "larry0x");
158        let ref_2 = mock_pool_ref(2, "stablechen");
159
160        map.save(
161            deps.as_mut().storage,
162            (&key, Addr::unchecked("astroport")),
163            &vec![ref_1.clone()],
164        )
165        .unwrap();
166
167        map.save(
168            deps.as_mut().storage,
169            (&key, Addr::unchecked("terraswap")),
170            &vec![ref_2.clone()],
171        )
172        .unwrap();
173
174        let items = map
175            .prefix(&key)
176            .range(deps.as_ref().storage, None, None, Order::Ascending)
177            .map(|item| item.unwrap())
178            .collect::<Vec<_>>();
179
180        assert_eq!(items.len(), 2);
181        assert_eq!(items[0], (Addr::unchecked("astroport"), vec![ref_1]));
182        assert_eq!(items[1], (Addr::unchecked("terraswap"), vec![ref_2]));
183    }
184
185    #[test]
186    fn partial_key_works() {
187        let mut deps = mock_dependencies();
188        let (key1, key2, key3) = mock_keys();
189        let map: Map<&DexAssetPairing, u64> = Map::new("map");
190
191        map.save(deps.as_mut().storage, &key1, &42069).unwrap();
192
193        map.save(deps.as_mut().storage, &key2, &69420).unwrap();
194
195        map.save(deps.as_mut().storage, &key3, &999).unwrap();
196
197        let items = map
198            .prefix((&"juno".into(), &"osmo".into()))
199            .range(deps.as_ref().storage, None, None, Order::Ascending)
200            .map(|item| item.unwrap())
201            .collect::<Vec<_>>();
202
203        assert_eq!(items.len(), 2);
204        assert_eq!(items[0], ("junoswap".to_string(), 42069));
205        assert_eq!(items[1], ("osmosis".to_string(), 69420));
206    }
207
208    #[test]
209    fn try_from_lp_token() {
210        let lp = LpToken::new("junoswap", vec!["juno".to_string(), "osmo".to_string()]);
211
212        let key = AnsEntryConvertor::new(lp).dex_asset_pairing().unwrap();
213
214        assert_eq!(
215            key,
216            DexAssetPairing::new("juno".into(), "osmo".into(), "junoswap")
217        );
218    }
219
220    #[test]
221    fn display() {
222        let key = DexAssetPairing::new("juno".into(), "osmo".into(), "junoswap");
223        assert_eq!(key.to_string(), "junoswap/juno,osmo");
224    }
225}