abstract_core/objects/account/
account_trace.rs1use std::fmt::Display;
2
3use cosmwasm_std::{ensure, Env, StdError, StdResult};
4use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey};
5
6use crate::{constants::CHAIN_DELIMITER, objects::chain_name::ChainName, AbstractError};
7
8pub const MAX_TRACE_LENGTH: usize = 6;
9pub(crate) const LOCAL: &str = "local";
10
11#[cosmwasm_schema::cw_serde]
13pub enum AccountTrace {
14 Local,
15 Remote(Vec<ChainName>),
17}
18
19impl KeyDeserialize for &AccountTrace {
20 type Output = AccountTrace;
21 #[inline(always)]
22 fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
23 Ok(AccountTrace::from_string(String::from_vec(value)?))
24 }
25}
26
27impl<'a> PrimaryKey<'a> for AccountTrace {
28 type Prefix = ();
29 type SubPrefix = ();
30 type Suffix = Self;
31 type SuperSuffix = Self;
32
33 fn key(&self) -> Vec<cw_storage_plus::Key> {
34 match self {
35 AccountTrace::Local => LOCAL.key(),
36 AccountTrace::Remote(chain_name) => {
37 let len = chain_name.len();
38 chain_name
39 .iter()
40 .enumerate()
41 .flat_map(|(s, c)| {
42 if s == len - 1 {
43 vec![c.str_ref().key()]
44 } else {
45 vec![c.str_ref().key(), CHAIN_DELIMITER.key()]
46 }
47 })
48 .flatten()
49 .collect::<Vec<Key>>()
50 }
51 }
52 }
53}
54
55impl KeyDeserialize for AccountTrace {
56 type Output = AccountTrace;
57 #[inline(always)]
58 fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
59 Ok(AccountTrace::from_string(String::from_vec(value)?))
60 }
61}
62
63impl<'a> Prefixer<'a> for AccountTrace {
64 fn prefix(&self) -> Vec<Key> {
65 self.key()
66 }
67}
68
69impl AccountTrace {
70 pub fn verify(&self) -> Result<(), AbstractError> {
72 match self {
73 AccountTrace::Local => Ok(()),
74 AccountTrace::Remote(chain_trace) => {
75 ensure!(
77 chain_trace.len() <= MAX_TRACE_LENGTH,
78 AbstractError::FormattingError {
79 object: "chain-seq".into(),
80 expected: format!("between 1 and {MAX_TRACE_LENGTH}"),
81 actual: chain_trace.len().to_string(),
82 }
83 );
84 for chain in chain_trace {
85 chain.verify()?;
86 if chain.as_str().eq(LOCAL) {
87 return Err(AbstractError::FormattingError {
88 object: "chain-seq".into(),
89 expected: "not 'local'".into(),
90 actual: chain.to_string(),
91 });
92 }
93 }
94 Ok(())
95 }
96 }
97 }
98
99 pub fn verify_remote(&self) -> Result<(), AbstractError> {
101 if &Self::Local == self {
102 Err(AbstractError::Std(StdError::generic_err(
103 "expected remote account trace",
104 )))
105 } else {
106 self.verify()
107 }
108 }
109
110 pub fn verify_local(&self) -> Result<(), AbstractError> {
112 if let &Self::Remote(..) = self {
113 return Err(AbstractError::Std(StdError::generic_err(
114 "expected local account trace",
115 )));
116 }
117 Ok(())
118 }
119
120 pub fn push_local_chain(&mut self, env: &Env) {
122 match &self {
123 AccountTrace::Local => {
124 *self = AccountTrace::Remote(vec![ChainName::new(env)]);
125 }
126 AccountTrace::Remote(path) => {
127 let mut path = path.clone();
128 path.push(ChainName::new(env));
129 *self = AccountTrace::Remote(path);
130 }
131 }
132 }
133 pub fn push_chain(&mut self, chain_name: ChainName) {
135 match &self {
136 AccountTrace::Local => {
137 *self = AccountTrace::Remote(vec![chain_name]);
138 }
139 AccountTrace::Remote(path) => {
140 let mut path = path.clone();
141 path.push(chain_name);
142 *self = AccountTrace::Remote(path);
143 }
144 }
145 }
146
147 pub(crate) fn from_string(trace: String) -> Self {
151 let acc = if trace == LOCAL {
152 Self::Local
153 } else {
154 Self::Remote(
155 trace
156 .split(CHAIN_DELIMITER)
157 .map(ChainName::_from_str)
158 .collect(),
159 )
160 };
161 acc
162 }
163
164 #[allow(unused)]
168 pub(crate) fn from_str(trace: &str) -> Result<Self, AbstractError> {
169 let acc = if trace == LOCAL {
170 Self::Local
171 } else {
172 Self::Remote(
173 trace
174 .split(CHAIN_DELIMITER)
175 .map(ChainName::_from_str)
176 .collect(),
177 )
178 };
179 acc.verify()?;
180 Ok(acc)
181 }
182}
183
184impl TryFrom<&str> for AccountTrace {
185 type Error = AbstractError;
186
187 fn try_from(trace: &str) -> Result<Self, Self::Error> {
188 if trace == LOCAL {
189 Ok(Self::Local)
190 } else {
191 let chain_trace: Vec<ChainName> = trace
192 .split(CHAIN_DELIMITER)
193 .map(|t| ChainName::from_string(t.to_string()))
194 .collect::<Result<Vec<_>, _>>()?;
195 Ok(Self::Remote(chain_trace))
196 }
197 }
198}
199
200impl Display for AccountTrace {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 match self {
203 AccountTrace::Local => write!(f, "{}", LOCAL),
204 AccountTrace::Remote(chain_name) => write!(
205 f,
206 "{}",
207 chain_name
209 .iter()
210 .map(|name| name.as_str())
211 .collect::<Vec<&str>>()
212 .join(CHAIN_DELIMITER)
213 ),
214 }
215 }
216}
217
218#[cfg(test)]
223mod test {
224 use std::str::FromStr;
225
226 use cosmwasm_std::{testing::mock_dependencies, Addr, Order};
227 use cw_storage_plus::Map;
228
229 use super::*;
230
231 mod format {
232 use super::*;
233 use crate::objects::chain_name::MAX_CHAIN_NAME_LENGTH;
234
235 #[test]
236 fn local_works() {
237 let trace = AccountTrace::from_str(LOCAL).unwrap();
238 assert_eq!(trace, AccountTrace::Local);
239 }
240
241 #[test]
242 fn remote_works() {
243 let trace = AccountTrace::from_str("bitcoin").unwrap();
244 assert_eq!(
245 trace,
246 AccountTrace::Remote(vec![ChainName::from_str("bitcoin").unwrap()])
247 );
248 }
249
250 #[test]
251 fn remote_multi_works() {
252 let trace = AccountTrace::from_str("bitcoin>ethereum").unwrap();
253 assert_eq!(
254 trace,
255 AccountTrace::Remote(vec![
256 ChainName::from_str("bitcoin").unwrap(),
257 ChainName::from_str("ethereum").unwrap()
258 ])
259 );
260 }
261
262 #[test]
263 fn remote_multi_multi_works() {
264 let trace = AccountTrace::from_str("bitcoin>ethereum>cosmos").unwrap();
265 assert_eq!(
266 trace,
267 AccountTrace::Remote(vec![
268 ChainName::from_str("bitcoin").unwrap(),
269 ChainName::from_str("ethereum").unwrap(),
270 ChainName::from_str("cosmos").unwrap(),
271 ])
272 );
273 }
274
275 #[test]
277 fn local_empty_fails() {
278 AccountTrace::from_str("").unwrap_err();
279 }
280
281 #[test]
282 fn local_too_short_fails() {
283 AccountTrace::from_str("a").unwrap_err();
284 }
285
286 #[test]
287 fn local_too_long_fails() {
288 AccountTrace::from_str(&"a".repeat(MAX_CHAIN_NAME_LENGTH + 1)).unwrap_err();
289 }
290
291 #[test]
292 fn local_uppercase_fails() {
293 AccountTrace::from_str("AAAAA").unwrap_err();
294 }
295
296 #[test]
297 fn local_non_alphanumeric_fails() {
298 AccountTrace::from_str("a!aoeuoau").unwrap_err();
299 }
300 }
301
302 mod key {
303 use super::*;
304
305 fn mock_key() -> AccountTrace {
306 AccountTrace::Remote(vec![ChainName::from_str("bitcoin").unwrap()])
307 }
308
309 #[test]
310 fn storage_key_works() {
311 let mut deps = mock_dependencies();
312 let key = mock_key();
313 let map: Map<&AccountTrace, u64> = Map::new("map");
314
315 map.save(deps.as_mut().storage, &key, &42069).unwrap();
316
317 assert_eq!(map.load(deps.as_ref().storage, &key).unwrap(), 42069);
318
319 let items = map
320 .range(deps.as_ref().storage, None, None, Order::Ascending)
321 .map(|item| item.unwrap())
322 .collect::<Vec<_>>();
323
324 assert_eq!(items.len(), 1);
325 assert_eq!(items[0], (key, 42069));
326 }
327
328 #[test]
329 fn composite_key_works() {
330 let mut deps = mock_dependencies();
331 let key = mock_key();
332 let map: Map<(&AccountTrace, Addr), u64> = Map::new("map");
333
334 map.save(
335 deps.as_mut().storage,
336 (&key, Addr::unchecked("larry")),
337 &42069,
338 )
339 .unwrap();
340
341 map.save(
342 deps.as_mut().storage,
343 (&key, Addr::unchecked("jake")),
344 &69420,
345 )
346 .unwrap();
347
348 let items = map
349 .prefix(&key)
350 .range(deps.as_ref().storage, None, None, Order::Ascending)
351 .map(|item| item.unwrap())
352 .collect::<Vec<_>>();
353
354 assert_eq!(items.len(), 2);
355 assert_eq!(items[0], (Addr::unchecked("jake"), 69420));
356 assert_eq!(items[1], (Addr::unchecked("larry"), 42069));
357 }
358 }
359}