abstract_std/objects/account/
account_id.rs1use std::{fmt::Display, str::FromStr};
2
3use cosmwasm_std::StdResult;
4use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey};
5use deser::split_first_key;
6
7use super::{account_trace::AccountTrace, AccountSequence};
8use crate::{objects::TruncatedChainId, AbstractError};
9
10#[cosmwasm_schema::cw_serde]
13pub struct AccountId {
14 trace: AccountTrace,
19 seq: AccountSequence,
22}
23
24impl Display for AccountId {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(f, "{}-{}", self.trace, self.seq)
27 }
28}
29
30impl AccountId {
31 pub fn new(seq: AccountSequence, trace: AccountTrace) -> Result<Self, AbstractError> {
32 trace.verify()?;
33 Ok(Self { seq, trace })
34 }
35
36 pub fn local(seq: AccountSequence) -> Self {
37 Self {
38 seq,
39 trace: AccountTrace::Local,
40 }
41 }
42
43 pub fn remote(
44 seq: AccountSequence,
45 trace: Vec<TruncatedChainId>,
46 ) -> Result<Self, AbstractError> {
47 let trace = AccountTrace::Remote(trace);
48 trace.verify()?;
49 Ok(Self { seq, trace })
50 }
51
52 pub fn into_remote_account_id(
55 mut self,
56 client_chain: TruncatedChainId,
57 host_chain: TruncatedChainId,
58 ) -> Self {
59 match &mut self.trace {
60 AccountTrace::Remote(ref mut chains) => {
61 if chains.last() != Some(&host_chain) {
63 chains.push(client_chain);
64 } else {
65 chains.pop();
66 if chains.is_empty() {
68 self.trace = AccountTrace::Local;
69 }
70 }
71 }
72 AccountTrace::Local => {
73 self.trace = AccountTrace::Remote(vec![client_chain]);
74 }
75 }
76 self
77 }
78
79 pub const fn const_new(seq: AccountSequence, trace: AccountTrace) -> Self {
81 Self { seq, trace }
82 }
83
84 pub fn seq(&self) -> AccountSequence {
85 self.seq
86 }
87
88 pub fn trace(&self) -> &AccountTrace {
89 &self.trace
90 }
91
92 pub fn trace_mut(&mut self) -> &mut AccountTrace {
93 &mut self.trace
94 }
95
96 pub fn is_local(&self) -> bool {
97 matches!(self.trace, AccountTrace::Local)
98 }
99
100 pub fn is_remote(&self) -> bool {
101 !self.is_local()
102 }
103
104 pub fn push_chain(&mut self, chain: TruncatedChainId) {
106 self.trace_mut().push_chain(chain)
107 }
108
109 pub fn decompose(self) -> (AccountTrace, AccountSequence) {
110 (self.trace, self.seq)
111 }
112}
113
114impl FromStr for AccountId {
115 type Err = AbstractError;
116
117 fn from_str(value: &str) -> Result<Self, Self::Err> {
118 let (trace_str, seq_str) = value
119 .split_once('-')
120 .ok_or(AbstractError::FormattingError {
121 object: "AccountId".into(),
122 expected: "trace-999".into(),
123 actual: value.into(),
124 })?;
125 let seq: u32 = seq_str.parse().unwrap();
126 if value.starts_with(super::account_trace::LOCAL) {
127 Ok(AccountId {
128 trace: AccountTrace::Local,
129 seq,
130 })
131 } else {
132 Ok(AccountId {
133 trace: AccountTrace::from_string(trace_str.into()),
134 seq,
135 })
136 }
137 }
138}
139
140impl PrimaryKey<'_> for AccountId {
141 type Prefix = AccountTrace;
142
143 type SubPrefix = ();
144
145 type Suffix = AccountSequence;
146
147 type SuperSuffix = Self;
148
149 fn key(&self) -> Vec<cw_storage_plus::Key> {
150 let mut keys = self.trace.key();
151 keys.extend(self.seq.key());
152 keys
153 }
154}
155
156impl Prefixer<'_> for AccountId {
157 fn prefix(&self) -> Vec<Key> {
158 self.key()
159 }
160}
161
162impl KeyDeserialize for &AccountId {
163 type Output = AccountId;
164 const KEY_ELEMS: u16 = AccountTrace::KEY_ELEMS + u32::KEY_ELEMS;
165
166 #[inline(always)]
167 fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
168 let (trace, seq) = split_first_key(AccountTrace::KEY_ELEMS, value.as_ref())?;
169
170 Ok(AccountId {
171 seq: AccountSequence::from_vec(seq.to_vec())?,
172 trace: AccountTrace::from_vec(trace)?,
173 })
174 }
175}
176
177impl KeyDeserialize for AccountId {
178 type Output = AccountId;
179 const KEY_ELEMS: u16 = <&AccountId>::KEY_ELEMS;
180
181 #[inline(always)]
182 fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
183 <&AccountId>::from_vec(value)
184 }
185}
186
187pub(crate) mod deser {
191 use cosmwasm_std::{StdError, StdResult};
192
193 pub fn split_first_key(key_elems: u16, value: &[u8]) -> StdResult<(Vec<u8>, &[u8])> {
197 let mut index = 0;
198 let mut first_key = Vec::new();
199
200 for i in 0..key_elems {
202 let len_slice = &value[index..index + 2];
203 index += 2;
204 let is_last_key = i == key_elems - 1;
205
206 if !is_last_key {
207 first_key.extend_from_slice(len_slice);
208 }
209
210 let subkey_len = parse_length(len_slice)?;
211 first_key.extend_from_slice(&value[index..index + subkey_len]);
212 index += subkey_len;
213 }
214
215 let remainder = &value[index..];
216 Ok((first_key, remainder))
217 }
218
219 fn parse_length(value: &[u8]) -> StdResult<usize> {
220 Ok(u16::from_be_bytes(
221 value
222 .try_into()
223 .map_err(|_| StdError::generic_err("Could not read 2 byte length"))?,
224 )
225 .into())
226 }
227}
228#[cfg(test)]
233mod test {
234 #![allow(clippy::needless_borrows_for_generic_args)]
235 use cosmwasm_std::{testing::mock_dependencies, Addr, Order};
236 use cw_storage_plus::Map;
237
238 use super::*;
239
240 mod key {
241 use super::*;
242
243 use std::str::FromStr;
244
245 fn mock_key() -> AccountId {
246 AccountId {
247 seq: 1,
248 trace: AccountTrace::Remote(vec![TruncatedChainId::from_str("bitcoin").unwrap()]),
249 }
250 }
251
252 fn mock_local_key() -> AccountId {
253 AccountId {
254 seq: 54,
255 trace: AccountTrace::Local,
256 }
257 }
258
259 fn mock_keys() -> (AccountId, AccountId, AccountId) {
260 (
261 AccountId {
262 seq: 1,
263 trace: AccountTrace::Local,
264 },
265 AccountId {
266 seq: 1,
267 trace: AccountTrace::Remote(vec![
268 TruncatedChainId::from_str("ethereum").unwrap(),
269 TruncatedChainId::from_str("bitcoin").unwrap(),
270 ]),
271 },
272 AccountId {
273 seq: 2,
274 trace: AccountTrace::Remote(vec![
275 TruncatedChainId::from_str("ethereum").unwrap(),
276 TruncatedChainId::from_str("bitcoin").unwrap(),
277 ]),
278 },
279 )
280 }
281
282 #[coverage_helper::test]
283 fn storage_key_works() {
284 let mut deps = mock_dependencies();
285 let key = mock_key();
286 let map: Map<&AccountId, u64> = Map::new("map");
287
288 map.save(deps.as_mut().storage, &key, &42069).unwrap();
289
290 assert_eq!(map.load(deps.as_ref().storage, &key).unwrap(), 42069);
291
292 let items = map
293 .range(deps.as_ref().storage, None, None, Order::Ascending)
294 .map(|item| item.unwrap())
295 .collect::<Vec<_>>();
296
297 assert_eq!(items.len(), 1);
298 assert_eq!(items[0], (key, 42069));
299 }
300
301 #[coverage_helper::test]
302 fn storage_key_local_works() {
303 let mut deps = mock_dependencies();
304 let key = mock_local_key();
305 let map: Map<&AccountId, u64> = Map::new("map");
306
307 map.save(deps.as_mut().storage, &key, &42069).unwrap();
308
309 assert_eq!(map.load(deps.as_ref().storage, &key).unwrap(), 42069);
310
311 let items = map
312 .range(deps.as_ref().storage, None, None, Order::Ascending)
313 .map(|item| item.unwrap())
314 .collect::<Vec<_>>();
315
316 assert_eq!(items.len(), 1);
317 assert_eq!(items[0], (key, 42069));
318 }
319
320 #[coverage_helper::test]
321 fn composite_key_works() {
322 let mut deps = mock_dependencies();
323 let key = mock_key();
324 let map: Map<(&AccountId, Addr), u64> = Map::new("map");
325
326 map.save(
327 deps.as_mut().storage,
328 (&key, Addr::unchecked("larry")),
329 &42069,
330 )
331 .unwrap();
332
333 map.save(
334 deps.as_mut().storage,
335 (&key, Addr::unchecked("jake")),
336 &69420,
337 )
338 .unwrap();
339
340 let items = map
341 .prefix(&key)
342 .range(deps.as_ref().storage, None, None, Order::Ascending)
343 .map(|item| item.unwrap())
344 .collect::<Vec<_>>();
345
346 assert_eq!(items.len(), 2);
347 assert_eq!(items[0], (Addr::unchecked("jake"), 69420));
348 assert_eq!(items[1], (Addr::unchecked("larry"), 42069));
349 }
350
351 #[coverage_helper::test]
352 fn partial_key_works() {
353 let mut deps = mock_dependencies();
354 let (key1, key2, key3) = mock_keys();
355 let map: Map<&AccountId, u64> = Map::new("map");
356
357 map.save(deps.as_mut().storage, &key1, &42069).unwrap();
358
359 map.save(deps.as_mut().storage, &key2, &69420).unwrap();
360
361 map.save(deps.as_mut().storage, &key3, &999).unwrap();
362
363 let items = map
364 .prefix(AccountTrace::Remote(vec![
365 TruncatedChainId::from_str("ethereum").unwrap(),
366 TruncatedChainId::from_str("bitcoin").unwrap(),
367 ]))
368 .range(deps.as_ref().storage, None, None, Order::Ascending)
369 .map(|item| item.unwrap())
370 .collect::<Vec<_>>();
371
372 assert_eq!(items.len(), 2);
373 assert_eq!(items[0], (1, 69420));
374 assert_eq!(items[1], (2, 999));
375 }
376
377 #[coverage_helper::test]
378 fn works_as_storage_key_with_multiple_chains_in_trace() {
379 let mut deps = mock_dependencies();
380 let key = AccountId {
381 seq: 1,
382 trace: AccountTrace::Remote(vec![
383 TruncatedChainId::from_str("ethereum").unwrap(),
384 TruncatedChainId::from_str("bitcoin").unwrap(),
385 ]),
386 };
387 let map: Map<&AccountId, u64> = Map::new("map");
388
389 let value = 1;
390 map.save(deps.as_mut().storage, &key, &value).unwrap();
391
392 assert_eq!(value, map.load(deps.as_ref().storage, &key).unwrap());
393 }
394 }
395
396 mod from_str {
397 use super::*;
399
400 #[coverage_helper::test]
401 fn works_with_local() {
402 let account_id: AccountId = "local-1".parse().unwrap();
403 assert_eq!(account_id.seq, 1);
404 assert_eq!(account_id.trace, AccountTrace::Local);
405 }
406
407 #[coverage_helper::test]
408 fn works_with_remote() {
409 let account_id: AccountId = "ethereum>bitcoin-1".parse().unwrap();
410 assert_eq!(account_id.seq, 1);
411 assert_eq!(
412 account_id.trace,
413 AccountTrace::Remote(vec![
414 TruncatedChainId::_from_str("bitcoin"),
415 TruncatedChainId::_from_str("ethereum"),
416 ])
417 );
418 }
419
420 #[coverage_helper::test]
421 fn works_with_remote_with_multiple_chains() {
422 let account_id: AccountId = "ethereum>bitcoin>cosmos-1".parse().unwrap();
423 assert_eq!(account_id.seq, 1);
424 assert_eq!(
425 account_id.trace,
426 AccountTrace::Remote(vec![
427 TruncatedChainId::_from_str("cosmos"),
428 TruncatedChainId::_from_str("bitcoin"),
429 TruncatedChainId::_from_str("ethereum"),
430 ])
431 );
432 }
433 }
434}