penumbra_sdk_asset/asset/
cache.rs

1use std::{collections::BTreeMap, ops::Deref, sync::Arc};
2
3use super::{denom_metadata, Id, Metadata, REGISTRY};
4use crate::asset::denom_metadata::Unit;
5
6/// On-chain data structures only record a fixed-size [`Id`], so this type
7/// allows caching known [`BaseDenom`]s.
8///
9/// The cache is backed by a [`BTreeMap`] accessed through a [`Deref`] impl.
10///
11/// For (de)serialization, [`From`] conversions are provided to a `BTreeMap<Id,
12/// String>` with the string representations of the base denominations.
13#[derive(Clone, Default, Debug)]
14pub struct Cache {
15    cache: BTreeMap<Id, Metadata>,
16    units: BTreeMap<String, Unit>,
17}
18
19impl Cache {
20    pub fn get_by_id(&self, id: Id) -> Option<Metadata> {
21        self.cache.get(&id).cloned()
22    }
23
24    pub fn get_unit(&self, raw_denom: &str) -> Option<Unit> {
25        self.units.get(raw_denom).cloned()
26    }
27
28    pub fn with_known_assets() -> Self {
29        let mut cache = Cache::default();
30
31        let known_assets = vec![
32            Metadata {
33                inner: Arc::new(denom_metadata::Inner::new(
34                    "upenumbra".to_string(),
35                    vec![
36                        denom_metadata::BareDenomUnit {
37                            exponent: 6,
38                            denom: "penumbra".to_string(),
39                        },
40                        denom_metadata::BareDenomUnit {
41                            exponent: 3,
42                            denom: "mpenumbra".to_string(),
43                        },
44                    ],
45                )),
46            },
47            Metadata {
48                inner: Arc::new(denom_metadata::Inner::new(
49                    "ugn".to_string(),
50                    vec![
51                        denom_metadata::BareDenomUnit {
52                            exponent: 6,
53                            denom: "gn".to_string(),
54                        },
55                        denom_metadata::BareDenomUnit {
56                            exponent: 3,
57                            denom: "mgn".to_string(),
58                        },
59                    ],
60                )),
61            },
62            Metadata {
63                inner: Arc::new(denom_metadata::Inner::new(
64                    "ugm".to_string(),
65                    vec![
66                        denom_metadata::BareDenomUnit {
67                            exponent: 6,
68                            denom: "gm".to_string(),
69                        },
70                        denom_metadata::BareDenomUnit {
71                            exponent: 3,
72                            denom: "mgm".to_string(),
73                        },
74                    ],
75                )),
76            },
77            Metadata {
78                inner: Arc::new(denom_metadata::Inner::new(
79                    "wtest_usd".to_string(),
80                    vec![denom_metadata::BareDenomUnit {
81                        exponent: 6,
82                        denom: "test_usd".to_string(),
83                    }],
84                )),
85            },
86            Metadata {
87                inner: Arc::new(denom_metadata::Inner::new(
88                    "test_sat".to_string(),
89                    vec![denom_metadata::BareDenomUnit {
90                        exponent: 8,
91                        denom: "test_btc".to_string(),
92                    }],
93                )),
94            },
95            Metadata {
96                inner: Arc::new(denom_metadata::Inner::new(
97                    "utest_atom".to_string(),
98                    vec![
99                        denom_metadata::BareDenomUnit {
100                            exponent: 6,
101                            denom: "test_atom".to_string(),
102                        },
103                        denom_metadata::BareDenomUnit {
104                            exponent: 3,
105                            denom: "mtest_atom".to_string(),
106                        },
107                    ],
108                )),
109            },
110            Metadata {
111                inner: Arc::new(denom_metadata::Inner::new(
112                    "utest_osmo".to_string(),
113                    vec![
114                        denom_metadata::BareDenomUnit {
115                            exponent: 6,
116                            denom: "test_osmo".to_string(),
117                        },
118                        denom_metadata::BareDenomUnit {
119                            exponent: 3,
120                            denom: "mtest_osmo".to_string(),
121                        },
122                    ],
123                )),
124            },
125            Metadata {
126                inner: Arc::new(denom_metadata::Inner::new(
127                    "uubtc".to_string(),
128                    vec![denom_metadata::BareDenomUnit {
129                        exponent: 6,
130                        denom: "ubtc".to_string(),
131                    }],
132                )),
133            },
134            Metadata {
135                inner: Arc::new(denom_metadata::Inner::new(
136                    "ucube".to_string(),
137                    vec![denom_metadata::BareDenomUnit {
138                        exponent: 1,
139                        denom: "cube".to_string(),
140                    }],
141                )),
142            },
143            Metadata {
144                inner: Arc::new(denom_metadata::Inner::new(
145                    "unala".to_string(),
146                    vec![
147                        denom_metadata::BareDenomUnit {
148                            exponent: 6,
149                            denom: "nala".to_string(),
150                        },
151                        denom_metadata::BareDenomUnit {
152                            exponent: 3,
153                            denom: "mnala".to_string(),
154                        },
155                    ],
156                )),
157            },
158        ];
159
160        cache.extend(known_assets);
161
162        cache
163    }
164}
165
166// Implementing Deref but not DerefMut means people get unlimited read access,
167// but can only write into the cache through approved methods.
168impl Deref for Cache {
169    type Target = BTreeMap<Id, Metadata>;
170
171    fn deref(&self) -> &Self::Target {
172        &self.cache
173    }
174}
175
176impl From<Cache> for BTreeMap<Id, Metadata> {
177    fn from(cache: Cache) -> Self {
178        cache
179            .cache
180            .into_iter()
181            .map(|(id, denom)| (id, denom))
182            .collect()
183    }
184}
185
186impl TryFrom<BTreeMap<Id, Metadata>> for Cache {
187    type Error = anyhow::Error;
188
189    fn try_from(map: BTreeMap<Id, Metadata>) -> Result<Self, Self::Error> {
190        let mut cache = BTreeMap::default();
191        let mut units: BTreeMap<String, Unit> = BTreeMap::default();
192        for (provided_id, denom) in map.into_iter() {
193            if let Some(denom) = REGISTRY.parse_denom(&denom.base_denom().denom) {
194                let id = denom.id();
195                if provided_id != id {
196                    anyhow::bail!(
197                        "provided id {} for denom {} does not match computed id {}",
198                        provided_id,
199                        denom,
200                        id
201                    );
202                }
203                cache.insert(id, denom.clone());
204                units.insert(denom.base_denom().denom, denom.base_unit());
205            } else {
206                anyhow::bail!("invalid base denom {}", denom.base_denom().denom);
207            }
208        }
209        Ok(Self { cache, units })
210    }
211}
212
213// BaseDenom already has a validated Id, so by implementing Extend<BaseDenom> we
214// can ensure we don't insert any invalid Ids
215impl Extend<Metadata> for Cache {
216    fn extend<T>(&mut self, iter: T)
217    where
218        T: IntoIterator<Item = Metadata>,
219    {
220        for denom in iter {
221            let id = denom.id();
222            self.cache.insert(id, denom.clone());
223
224            for unit in denom.units() {
225                self.units.insert(unit.to_string(), unit);
226            }
227        }
228    }
229}
230
231impl FromIterator<Metadata> for Cache {
232    fn from_iter<T: IntoIterator<Item = Metadata>>(iter: T) -> Self {
233        let mut cache = Cache::default();
234        cache.extend(iter);
235        cache
236    }
237}