abstract_std/objects/entry/
channel_entry.rs

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