abstract_core/objects/entry/
dex_asset_pairing.rs1use 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#[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
83impl 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}