levana_perpswap_cosmos/
storage.rs

1//! Helpers for dealing with CosmWasm storage.
2
3pub use crate::prelude::*;
4use cosmwasm_std::{from_json, Binary, Empty, QuerierWrapper};
5use cw_storage_plus::{KeyDeserialize, Prefixer, PrimaryKey};
6
7/// A multilevel [Map] where the suffix of the key monotonically increases.
8///
9/// This represents a common pattern where we want to store a data series by
10/// some key, such as a series of position events per position. The [u64] is
11/// guaranteed to monotonically increase over time per `K` value.
12pub type MonotonicMultilevelMap<'a, K, T> = Map<(K, u64), T>;
13
14/// Push a new value to a [MonotonicMultilevelMap].
15pub fn push_to_monotonic_multilevel_map<'a, K, T>(
16    store: &mut dyn Storage,
17    m: MonotonicMultilevelMap<'a, K, T>,
18    k: K,
19    t: &T,
20) -> Result<u64>
21where
22    K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize,
23    T: serde::Serialize + serde::de::DeserializeOwned,
24{
25    let next_id = m
26        .prefix(k.clone())
27        .keys(store, None, None, cosmwasm_std::Order::Descending)
28        .next()
29        .transpose()?
30        .map_or(0, |x| x + 1);
31    m.save(store, (k, next_id), t)?;
32    Ok(next_id)
33}
34
35/// Helper to paginate over [MonotonicMultilevelMap]
36pub fn collect_monotonic_multilevel_map<'a, K, T>(
37    store: &dyn Storage,
38    m: MonotonicMultilevelMap<'a, K, T>,
39    k: K,
40    after_id: Option<u64>,
41    limit: Option<u32>,
42    order: Order,
43) -> Result<Vec<(u64, T)>>
44where
45    K: PrimaryKey<'a> + Prefixer<'a> + KeyDeserialize,
46    T: serde::Serialize + serde::de::DeserializeOwned,
47{
48    let iter = m
49        .prefix(k)
50        .range(store, after_id.map(Bound::exclusive), None, order)
51        .map(|res| res.map_err(|err| err.into()));
52
53    match limit {
54        Some(limit) => iter.take(limit.try_into()?).collect(),
55        None => iter.collect(),
56    }
57}
58
59/// A [Map] where the key monotonically increases.
60///
61/// This represents a common pattern where we want to store data with unique keys
62/// The [u64] key is guaranteed to monotonically increase over time per pushed value.
63pub type MonotonicMap<'a, T> = Map<u64, T>;
64
65/// Push a new value to a [MonotonicMap].
66pub fn push_to_monotonic_map<T>(store: &mut dyn Storage, m: MonotonicMap<T>, t: &T) -> Result<u64>
67where
68    T: serde::Serialize + serde::de::DeserializeOwned,
69{
70    let next_id = m
71        .keys(store, None, None, cosmwasm_std::Order::Descending)
72        .next()
73        .transpose()?
74        .map_or(0, |x| x + 1);
75    m.save(store, next_id, t)?;
76    Ok(next_id)
77}
78
79/// Helper to paginate over [MonotonicMap]
80pub fn collect_monotonic_map<T>(
81    store: &dyn Storage,
82    m: MonotonicMap<T>,
83    after_id: Option<u64>,
84    limit: Option<u32>,
85    order: Order,
86) -> Result<Vec<(u64, T)>>
87where
88    T: serde::Serialize + serde::de::DeserializeOwned,
89{
90    let iter = m
91        .range(store, after_id.map(Bound::exclusive), None, order)
92        .map(|res| res.map_err(|err| err.into()));
93
94    match limit {
95        Some(limit) => iter.take(limit.try_into()?).collect(),
96        None => iter.collect(),
97    }
98}
99
100/// Load an [cw_storage_plus::Item] stored in an external contract
101pub fn load_external_item<T: serde::de::DeserializeOwned>(
102    querier: &QuerierWrapper<Empty>,
103    contract_addr: impl Into<String>,
104    key: impl Into<Binary>,
105) -> anyhow::Result<T> {
106    // only deserialize for extra context if in debug mode
107    // because we must pass the key in as an owned value
108    // and so we have to extract the name in the happy path too
109    let key: Binary = key.into();
110    let debug_key_name = if cfg!(debug_assertions) {
111        from_json::<String>(&key).ok()
112    } else {
113        None
114    };
115
116    external_helper(querier, contract_addr, key, || {
117        anyhow!(PerpError::new(
118            ErrorId::Any,
119            ErrorDomain::Default,
120            format!(
121                "unable to load external item {}",
122                debug_key_name.unwrap_or_default()
123            )
124        ))
125    })
126}
127
128/// Load a value from a [cw_storage_plus::Map] stored in an external contract
129pub fn load_external_map<'a, T: serde::de::DeserializeOwned>(
130    querier: &QuerierWrapper<Empty>,
131    contract_addr: impl Into<String>,
132    namespace: &str,
133    key: &impl PrimaryKey<'a>,
134) -> anyhow::Result<T> {
135    external_helper(querier, contract_addr, map_key(namespace, key), || {
136        anyhow!(PerpError::new(
137            ErrorId::Any,
138            ErrorDomain::Default,
139            format!("unable to load external map {}", namespace)
140        ))
141    })
142}
143
144/// Check if a [cw_storage_plus::Map] in an external contract has a specific key
145pub fn external_map_has<'a>(
146    querier: &QuerierWrapper<Empty>,
147    contract_addr: impl Into<String>,
148    namespace: &str,
149    key: &impl PrimaryKey<'a>,
150) -> anyhow::Result<bool> {
151    querier
152        .query_wasm_raw(contract_addr, map_key(namespace, key))
153        .map(|x| x.is_some())
154        .map_err(|e| e.into())
155}
156
157fn external_helper<T: serde::de::DeserializeOwned>(
158    querier: &QuerierWrapper<Empty>,
159    contract_addr: impl Into<String>,
160    key: impl Into<Binary>,
161    mk_error_message: impl FnOnce() -> anyhow::Error,
162) -> anyhow::Result<T> {
163    let res = querier
164        .query_wasm_raw(contract_addr, key)?
165        .ok_or_else(mk_error_message)?;
166    serde_json_wasm::from_slice(&res).map_err(|err| err.into())
167}
168/// Generate a storage key for a value in a [cw_storage_plus::Map].
169pub fn map_key<'a, K: PrimaryKey<'a>>(namespace: &str, key: &K) -> Vec<u8> {
170    // Taken from https://github.com/CosmWasm/cw-storage-plus/blob/69300779519d8ba956fb53725e44e2b59c317b1c/src/helpers.rs#L57
171    // If only that was exposed...
172
173    let mut size = namespace.len();
174    let key = key.key();
175    assert!(!key.is_empty());
176
177    for x in &key {
178        size += x.as_ref().len() + 2;
179    }
180
181    let mut out = Vec::<u8>::with_capacity(size);
182
183    for prefix in std::iter::once(namespace.as_bytes())
184        .chain(key.iter().take(key.len() - 1).map(|key| key.as_ref()))
185    {
186        out.extend_from_slice(&encode_length(prefix));
187        out.extend_from_slice(prefix);
188    }
189
190    if let Some(last) = key.last() {
191        out.extend_from_slice(last.as_ref());
192    }
193
194    out
195}
196
197fn encode_length(bytes: &[u8]) -> [u8; 2] {
198    if let Ok(len) = u16::try_from(bytes.len()) {
199        len.to_be_bytes()
200    } else {
201        panic!("only supports namespaces up to length 0xFFFF")
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use cosmwasm_std::testing::mock_dependencies;
209    use cw_storage_plus::Map;
210
211    #[test]
212    fn simple_keys_work() {
213        let mut deps = mock_dependencies();
214        let m = Map::<&str, String>::new("foobarbazbin");
215        m.save(&mut deps.storage, "somekey", &"somevalue".to_owned())
216            .unwrap();
217        let key = map_key("foobarbazbin", &"somekey");
218        assert_eq!(deps.as_ref().storage.get(&key).unwrap(), b"\"somevalue\"");
219    }
220
221    #[test]
222    fn complex_keys_work() {
223        const NAMESPACE: &str = "👋";
224        let mut deps = mock_dependencies();
225        let m = Map::<ComplexKey, String>::new(NAMESPACE);
226        let key = (
227            ("level1".to_owned(), "level™️💪".to_owned()),
228            "level-".to_owned(),
229        );
230        let storage_key = map_key(NAMESPACE, &key);
231        m.save(&mut deps.storage, key, &"somevalue".to_owned())
232            .unwrap();
233        assert_eq!(
234            deps.as_ref().storage.get(&storage_key).unwrap(),
235            b"\"somevalue\""
236        );
237    }
238
239    type ComplexKey = ((String, String), String);
240}