abstract_core/objects/account/
account_id.rs1use std::fmt::Display;
2
3use cosmwasm_std::{StdError, StdResult};
4use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey};
5
6use super::{account_trace::AccountTrace, AccountSequence};
7use crate::{objects::chain_name::ChainName, AbstractError};
8
9#[cosmwasm_schema::cw_serde]
12pub struct AccountId {
13 trace: AccountTrace,
18 seq: AccountSequence,
21}
22
23impl Display for AccountId {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 write!(f, "{}-{}", self.trace, self.seq)
26 }
27}
28
29impl AccountId {
30 pub fn new(seq: AccountSequence, trace: AccountTrace) -> Result<Self, AbstractError> {
31 trace.verify()?;
32 Ok(Self { seq, trace })
33 }
34
35 pub fn local(seq: AccountSequence) -> Self {
36 Self {
37 seq,
38 trace: AccountTrace::Local,
39 }
40 }
41
42 pub fn remote(seq: AccountSequence, trace: Vec<ChainName>) -> Result<Self, AbstractError> {
43 let trace = AccountTrace::Remote(trace);
44 trace.verify()?;
45 Ok(Self { seq, trace })
46 }
47
48 pub const fn const_new(seq: AccountSequence, trace: AccountTrace) -> Self {
50 Self { seq, trace }
51 }
52
53 pub fn seq(&self) -> AccountSequence {
54 self.seq
55 }
56
57 pub fn trace(&self) -> &AccountTrace {
58 &self.trace
59 }
60
61 pub fn trace_mut(&mut self) -> &mut AccountTrace {
62 &mut self.trace
63 }
64
65 pub fn is_local(&self) -> bool {
66 matches!(self.trace, AccountTrace::Local)
67 }
68
69 pub fn is_remote(&self) -> bool {
70 !self.is_local()
71 }
72
73 pub fn decompose(self) -> (AccountTrace, AccountSequence) {
74 (self.trace, self.seq)
75 }
76}
77
78impl TryFrom<&str> for AccountId {
79 type Error = AbstractError;
80
81 fn try_from(value: &str) -> Result<Self, Self::Error> {
82 let (trace_str, seq_str) = value
83 .split_once('-')
84 .ok_or(AbstractError::FormattingError {
85 object: "AccountId".into(),
86 expected: "trace-999".into(),
87 actual: value.into(),
88 })?;
89 let seq: u32 = seq_str.parse().unwrap();
90 if value.starts_with(super::account_trace::LOCAL) {
91 Ok(AccountId {
92 trace: AccountTrace::Local,
93 seq,
94 })
95 } else {
96 Ok(AccountId {
97 trace: AccountTrace::from_string(trace_str.into()),
98 seq,
99 })
100 }
101 }
102}
103
104impl<'a> PrimaryKey<'a> for AccountId {
105 type Prefix = AccountTrace;
106
107 type SubPrefix = ();
108
109 type Suffix = AccountSequence;
110
111 type SuperSuffix = Self;
112
113 fn key(&self) -> Vec<cw_storage_plus::Key> {
114 let mut keys = self.trace.key();
115 keys.extend(self.seq.key());
116 keys
117 }
118}
119
120impl<'a> Prefixer<'a> for AccountId {
121 fn prefix(&self) -> Vec<Key> {
122 self.key()
123 }
124}
125
126impl KeyDeserialize for &AccountId {
127 type Output = AccountId;
128
129 #[inline(always)]
130 fn from_vec(mut value: Vec<u8>) -> StdResult<Self::Output> {
131 let mut tu = value.split_off(2);
132 let t_len = parse_length(&value)?;
133 let u = tu.split_off(t_len);
134
135 Ok(AccountId {
136 seq: AccountSequence::from_vec(u)?,
137 trace: AccountTrace::from_string(String::from_vec(tu)?),
138 })
139 }
140}
141
142impl KeyDeserialize for AccountId {
143 type Output = AccountId;
144
145 #[inline(always)]
146 fn from_vec(mut value: Vec<u8>) -> StdResult<Self::Output> {
147 let mut tu = value.split_off(2);
148 let t_len = parse_length(&value)?;
149 let u = tu.split_off(t_len);
150
151 Ok(AccountId {
152 seq: AccountSequence::from_vec(u)?,
153 trace: AccountTrace::from_string(String::from_vec(tu)?),
154 })
155 }
156}
157
158#[inline(always)]
159fn parse_length(value: &[u8]) -> StdResult<usize> {
160 Ok(u16::from_be_bytes(
161 value
162 .try_into()
163 .map_err(|_| StdError::generic_err("Could not read 2 byte length"))?,
164 )
165 .into())
166}
167
168#[cfg(test)]
173mod test {
174 use cosmwasm_std::{testing::mock_dependencies, Addr, Order};
175 use cw_storage_plus::Map;
176
177 use super::*;
178
179 mod key {
180 use std::str::FromStr;
181
182 use super::*;
183 use crate::objects::chain_name::ChainName;
184
185 fn mock_key() -> AccountId {
186 AccountId {
187 seq: 1,
188 trace: AccountTrace::Remote(vec![ChainName::from_str("bitcoin").unwrap()]),
189 }
190 }
191
192 fn mock_keys() -> (AccountId, AccountId, AccountId) {
193 (
194 AccountId {
195 seq: 1,
196 trace: AccountTrace::Local,
197 },
198 AccountId {
199 seq: 1,
200 trace: AccountTrace::Remote(vec![
201 ChainName::from_str("ethereum").unwrap(),
202 ChainName::from_str("bitcoin").unwrap(),
203 ]),
204 },
205 AccountId {
206 seq: 2,
207 trace: AccountTrace::Remote(vec![
208 ChainName::from_str("ethereum").unwrap(),
209 ChainName::from_str("bitcoin").unwrap(),
210 ]),
211 },
212 )
213 }
214
215 #[test]
216 fn storage_key_works() {
217 let mut deps = mock_dependencies();
218 let key = mock_key();
219 let map: Map<&AccountId, u64> = Map::new("map");
220
221 map.save(deps.as_mut().storage, &key, &42069).unwrap();
222
223 assert_eq!(map.load(deps.as_ref().storage, &key).unwrap(), 42069);
224
225 let items = map
226 .range(deps.as_ref().storage, None, None, Order::Ascending)
227 .map(|item| item.unwrap())
228 .collect::<Vec<_>>();
229
230 assert_eq!(items.len(), 1);
231 assert_eq!(items[0], (key, 42069));
232 }
233
234 #[test]
235 fn composite_key_works() {
236 let mut deps = mock_dependencies();
237 let key = mock_key();
238 let map: Map<(&AccountId, Addr), u64> = Map::new("map");
239
240 map.save(
241 deps.as_mut().storage,
242 (&key, Addr::unchecked("larry")),
243 &42069,
244 )
245 .unwrap();
246
247 map.save(
248 deps.as_mut().storage,
249 (&key, Addr::unchecked("jake")),
250 &69420,
251 )
252 .unwrap();
253
254 let items = map
255 .prefix(&key)
256 .range(deps.as_ref().storage, None, None, Order::Ascending)
257 .map(|item| item.unwrap())
258 .collect::<Vec<_>>();
259
260 assert_eq!(items.len(), 2);
261 assert_eq!(items[0], (Addr::unchecked("jake"), 69420));
262 assert_eq!(items[1], (Addr::unchecked("larry"), 42069));
263 }
264
265 #[test]
266 fn partial_key_works() {
267 let mut deps = mock_dependencies();
268 let (key1, key2, key3) = mock_keys();
269 let map: Map<&AccountId, u64> = Map::new("map");
270
271 map.save(deps.as_mut().storage, &key1, &42069).unwrap();
272
273 map.save(deps.as_mut().storage, &key2, &69420).unwrap();
274
275 map.save(deps.as_mut().storage, &key3, &999).unwrap();
276
277 let items = map
278 .prefix(AccountTrace::Remote(vec![
279 ChainName::from_str("ethereum").unwrap(),
280 ChainName::from_str("bitcoin").unwrap(),
281 ]))
282 .range(deps.as_ref().storage, None, None, Order::Ascending)
283 .map(|item| item.unwrap())
284 .collect::<Vec<_>>();
285
286 assert_eq!(items.len(), 2);
287 assert_eq!(items[0], (1, 69420));
288 assert_eq!(items[1], (2, 999));
289 }
290
291 #[test]
292 fn works_as_storage_key_with_multiple_chains_in_trace() {
293 let mut deps = mock_dependencies();
294 let key = AccountId {
295 seq: 1,
296 trace: AccountTrace::Remote(vec![
297 ChainName::from_str("ethereum").unwrap(),
298 ChainName::from_str("bitcoin").unwrap(),
299 ]),
300 };
301 let map: Map<&AccountId, u64> = Map::new("map");
302
303 let value = 1;
304 map.save(deps.as_mut().storage, &key, &value).unwrap();
305
306 assert_eq!(value, map.load(deps.as_ref().storage, &key).unwrap());
307 }
308 }
309
310 mod try_from {
311 use super::*;
313
314 #[test]
315 fn works_with_local() {
316 let account_id = AccountId::try_from("local-1").unwrap();
317 assert_eq!(account_id.seq, 1);
318 assert_eq!(account_id.trace, AccountTrace::Local);
319 }
320
321 #[test]
322 fn works_with_remote() {
323 let account_id = AccountId::try_from("ethereum>bitcoin-1").unwrap();
324 assert_eq!(account_id.seq, 1);
325 assert_eq!(
326 account_id.trace,
327 AccountTrace::Remote(vec![
328 ChainName::_from_str("ethereum"),
329 ChainName::_from_str("bitcoin"),
330 ])
331 );
332 }
333
334 #[test]
335 fn works_with_remote_with_multiple_chains() {
336 let account_id = AccountId::try_from("ethereum>bitcoin>cosmos-1").unwrap();
337 assert_eq!(account_id.seq, 1);
338 assert_eq!(
339 account_id.trace,
340 AccountTrace::Remote(vec![
341 ChainName::_from_str("ethereum"),
342 ChainName::_from_str("bitcoin"),
343 ChainName::_from_str("cosmos"),
344 ])
345 );
346 }
347 }
348}