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#[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.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
90impl 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}