1use cosmwasm_std::{StdError, StdResult};
2use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::{convert::TryInto, fmt::Display};
6
7#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, JsonSchema, PartialOrd, Ord)]
9pub struct UncheckedChannelEntry {
10 pub connected_chain: String,
11 pub protocol: String,
12}
13
14impl UncheckedChannelEntry {
15 pub fn new<T: ToString>(connected_chain: T, protocol: T) -> Self {
16 Self {
17 protocol: protocol.to_string(),
18 connected_chain: connected_chain.to_string(),
19 }
20 }
21 pub fn check(self) -> ChannelEntry {
22 ChannelEntry {
23 connected_chain: self.connected_chain.to_ascii_lowercase(),
24 protocol: self.protocol.to_ascii_lowercase(),
25 }
26 }
27}
28
29impl TryFrom<String> for UncheckedChannelEntry {
30 type Error = StdError;
31 fn try_from(entry: String) -> Result<Self, Self::Error> {
32 let composite: Vec<&str> = entry.split('/').collect();
33 if composite.len() != 2 {
34 return Err(StdError::generic_err(
35 "connected_chain entry should be formatted as \"connected_chain_name/protocol\".",
36 ));
37 }
38 Ok(Self::new(composite[0], composite[1]))
39 }
40}
41
42#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, JsonSchema, Eq, PartialOrd, Ord)]
45pub struct ChannelEntry {
46 pub connected_chain: String,
47 pub protocol: String,
48}
49
50impl Display for ChannelEntry {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 write!(f, "{}/{}", self.connected_chain, self.protocol)
53 }
54}
55
56impl<'a> PrimaryKey<'a> for &ChannelEntry {
57 type Prefix = String;
58
59 type SubPrefix = ();
60
61 type Suffix = String;
62
63 type SuperSuffix = Self;
64
65 fn key(&self) -> Vec<cw_storage_plus::Key> {
66 let mut keys = self.connected_chain.key();
67 keys.extend(self.protocol.key());
68 keys
69 }
70}
71
72impl<'a> Prefixer<'a> for &ChannelEntry {
73 fn prefix(&self) -> Vec<Key> {
74 let mut res = self.connected_chain.prefix();
75 res.extend(self.protocol.prefix().into_iter());
76 res
77 }
78}
79
80impl KeyDeserialize for &ChannelEntry {
81 type Output = ChannelEntry;
82
83 #[inline(always)]
84 fn from_vec(mut value: Vec<u8>) -> StdResult<Self::Output> {
85 let mut tu = value.split_off(2);
86 let t_len = parse_length(&value)?;
87 let u = tu.split_off(t_len);
88
89 Ok(ChannelEntry {
90 connected_chain: String::from_vec(tu)?,
91 protocol: String::from_vec(u)?,
92 })
93 }
94}
95
96#[inline(always)]
97fn parse_length(value: &[u8]) -> StdResult<usize> {
98 Ok(u16::from_be_bytes(
99 value
100 .try_into()
101 .map_err(|_| StdError::generic_err("Could not read 2 byte length"))?,
102 )
103 .into())
104}
105
106#[cfg(test)]
111mod test {
112 use super::*;
113 use cosmwasm_std::{testing::mock_dependencies, Addr, Order};
114 use cw_storage_plus::Map;
115
116 fn mock_key() -> ChannelEntry {
117 ChannelEntry {
118 connected_chain: "osmosis".to_string(),
119 protocol: "ics20".to_string(),
120 }
121 }
122
123 fn mock_keys() -> (ChannelEntry, ChannelEntry, ChannelEntry) {
124 (
125 ChannelEntry {
126 connected_chain: "osmosis".to_string(),
127 protocol: "ics20".to_string(),
128 },
129 ChannelEntry {
130 connected_chain: "osmosis".to_string(),
131 protocol: "ics".to_string(),
132 },
133 ChannelEntry {
134 connected_chain: "cosmos".to_string(),
135 protocol: "abstract".to_string(),
136 },
137 )
138 }
139
140 #[test]
141 fn storage_key_works() {
142 let mut deps = mock_dependencies();
143 let key = mock_key();
144 let map: Map<&ChannelEntry, u64> = Map::new("map");
145
146 map.save(deps.as_mut().storage, &key, &42069).unwrap();
147
148 assert_eq!(map.load(deps.as_ref().storage, &key).unwrap(), 42069);
149
150 let items = map
151 .range(deps.as_ref().storage, None, None, Order::Ascending)
152 .map(|item| item.unwrap())
153 .collect::<Vec<_>>();
154
155 assert_eq!(items.len(), 1);
156 assert_eq!(items[0], (key, 42069));
157 }
158
159 #[test]
160 fn composite_key_works() {
161 let mut deps = mock_dependencies();
162 let key = mock_key();
163 let map: Map<(&ChannelEntry, Addr), u64> = Map::new("map");
164
165 map.save(
166 deps.as_mut().storage,
167 (&key, Addr::unchecked("larry")),
168 &42069,
169 )
170 .unwrap();
171
172 map.save(
173 deps.as_mut().storage,
174 (&key, Addr::unchecked("jake")),
175 &69420,
176 )
177 .unwrap();
178
179 let items = map
180 .prefix(&key)
181 .range(deps.as_ref().storage, None, None, Order::Ascending)
182 .map(|item| item.unwrap())
183 .collect::<Vec<_>>();
184
185 assert_eq!(items.len(), 2);
186 assert_eq!(items[0], (Addr::unchecked("jake"), 69420));
187 assert_eq!(items[1], (Addr::unchecked("larry"), 42069));
188 }
189
190 #[test]
191 fn partial_key_works() {
192 let mut deps = mock_dependencies();
193 let (key1, key2, key3) = mock_keys();
194 let map: Map<&ChannelEntry, u64> = Map::new("map");
195
196 map.save(deps.as_mut().storage, &key1, &42069).unwrap();
197
198 map.save(deps.as_mut().storage, &key2, &69420).unwrap();
199
200 map.save(deps.as_mut().storage, &key3, &999).unwrap();
201
202 let items = map
203 .prefix("osmosis".to_string())
204 .range(deps.as_ref().storage, None, None, Order::Ascending)
205 .map(|item| item.unwrap())
206 .collect::<Vec<_>>();
207
208 assert_eq!(items.len(), 2);
209 assert_eq!(items[0], ("ics".to_string(), 69420));
210 assert_eq!(items[1], ("ics20".to_string(), 42069));
211 }
212}