abstract_os/objects/
channel_entry.rs

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/// Key to get the Address of a connected_chain
8#[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/// Key to get the Address of a connected_chain
43/// Use [`UncheckedChannelEntry`] to construct this type.  
44#[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//--------------------------------------------------------------------------------------------------
107// Tests
108//--------------------------------------------------------------------------------------------------
109
110#[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}