abstract_os/objects/
dex_asset_pairing.rs

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