Skip to main content

cw_storage_plus/
item.rs

1use serde::de::DeserializeOwned;
2use serde::Serialize;
3use std::marker::PhantomData;
4
5use cosmwasm_std::{
6    from_json, to_json_vec, Addr, CustomQuery, QuerierWrapper, StdError, StdResult, Storage,
7    WasmQuery,
8};
9
10use crate::{helpers::not_found_object_info, namespace::Namespace};
11
12/// Item stores one typed item at the given key.
13/// This is an analog of Singleton.
14/// It functions the same way as Path does but doesn't use a Vec and thus has a const fn constructor.
15pub struct Item<T> {
16    // this is full key - no need to length-prefix it, we only store one item
17    storage_key: Namespace,
18    // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed
19    data_type: PhantomData<T>,
20}
21
22impl<T> Item<T> {
23    /// Creates a new [`Item`] with the given storage key. This is a const fn only suitable
24    /// when you have a static string slice.
25    pub const fn new(storage_key: &'static str) -> Self {
26        Item {
27            storage_key: Namespace::from_static_str(storage_key),
28            data_type: PhantomData,
29        }
30    }
31
32    /// Creates a new [`Item`] with the given storage key. Use this if you might need to handle
33    /// a dynamic string. Otherwise, you might prefer [`Item::new`].
34    pub fn new_dyn(storage_key: impl Into<Namespace>) -> Self {
35        Item {
36            storage_key: storage_key.into(),
37            data_type: PhantomData,
38        }
39    }
40}
41
42impl<T> Item<T>
43where
44    T: Serialize + DeserializeOwned,
45{
46    // this gets the path of the data to use elsewhere
47    pub fn as_slice(&self) -> &[u8] {
48        self.storage_key.as_slice()
49    }
50
51    /// save will serialize the model and store, returns an error on serialization issues
52    pub fn save(&self, store: &mut dyn Storage, data: &T) -> StdResult<()> {
53        store.set(self.storage_key.as_slice(), &to_json_vec(data)?);
54        Ok(())
55    }
56
57    pub fn remove(&self, store: &mut dyn Storage) {
58        store.remove(self.storage_key.as_slice());
59    }
60
61    /// load will return an error if no data is set at the given key, or on parse error
62    pub fn load(&self, store: &dyn Storage) -> StdResult<T> {
63        if let Some(value) = store.get(self.storage_key.as_slice()) {
64            from_json(value)
65        } else {
66            let object_info = not_found_object_info::<T>(self.storage_key.as_slice());
67            Err(StdError::msg(format!("{object_info} not found")))
68        }
69    }
70
71    /// may_load will parse the data stored at the key if present, returns `Ok(None)` if no data there.
72    /// returns an error on issues parsing
73    pub fn may_load(&self, store: &dyn Storage) -> StdResult<Option<T>> {
74        let value = store.get(self.storage_key.as_slice());
75        value.map(|v| from_json(v)).transpose()
76    }
77
78    /// Returns `true` if data is stored at the key, `false` otherwise.
79    pub fn exists(&self, store: &dyn Storage) -> bool {
80        store.get(self.storage_key.as_slice()).is_some()
81    }
82
83    /// Loads the data, perform the specified action, and store the result
84    /// in the database. This is shorthand for some common sequences, which may be useful.
85    ///
86    /// It assumes, that data was initialized before, and if it doesn't exist, `Err(StdError::NotFound)`
87    /// is returned.
88    pub fn update<A, E>(&self, store: &mut dyn Storage, action: A) -> Result<T, E>
89    where
90        A: FnOnce(T) -> Result<T, E>,
91        E: From<StdError>,
92    {
93        let input = self.load(store)?;
94        let output = action(input)?;
95        self.save(store, &output)?;
96        Ok(output)
97    }
98
99    /// If you import the proper Item from the remote contract, this will let you read the data
100    /// from a remote contract in a type-safe way using WasmQuery::RawQuery.
101    ///
102    /// Note that we expect an Item to be set, and error if there is no data there
103    pub fn query<Q: CustomQuery>(
104        &self,
105        querier: &QuerierWrapper<Q>,
106        remote_contract: Addr,
107    ) -> StdResult<T> {
108        let request = WasmQuery::Raw {
109            contract_addr: remote_contract.into(),
110            key: (self.storage_key.as_slice()).into(),
111        };
112        querier.query(&request.into())
113    }
114}
115
116#[cfg(test)]
117mod test {
118    use super::*;
119    use cosmwasm_std::testing::MockStorage;
120    use cosmwasm_std::{to_json_vec, StdError};
121    use serde::{Deserialize, Serialize};
122
123    #[derive(Serialize, Deserialize, PartialEq, Debug)]
124    struct Config {
125        pub owner: String,
126        pub max_tokens: i32,
127    }
128
129    // note const constructor rather than 2 funcs with Singleton
130    const CONFIG: Item<Config> = Item::new("config");
131
132    #[test]
133    fn save_and_load() {
134        let mut store = MockStorage::new();
135
136        assert!(CONFIG.load(&store).is_err());
137        assert_eq!(CONFIG.may_load(&store).unwrap(), None);
138
139        let cfg = Config {
140            owner: "admin".to_string(),
141            max_tokens: 1234,
142        };
143        CONFIG.save(&mut store, &cfg).unwrap();
144
145        assert_eq!(cfg, CONFIG.load(&store).unwrap());
146    }
147
148    #[test]
149    fn owned_key_works() {
150        let mut store = MockStorage::new();
151
152        for i in 0..3 {
153            let key = format!("key{i}");
154            let item = Item::new_dyn(key);
155            item.save(&mut store, &i).unwrap();
156        }
157
158        assert_eq!(store.get(b"key0").unwrap(), b"0");
159        assert_eq!(store.get(b"key1").unwrap(), b"1");
160        assert_eq!(store.get(b"key2").unwrap(), b"2");
161    }
162
163    #[test]
164    fn exists_works() {
165        let mut store = MockStorage::new();
166
167        assert!(!CONFIG.exists(&store));
168
169        let cfg = Config {
170            owner: "admin".to_string(),
171            max_tokens: 1234,
172        };
173        CONFIG.save(&mut store, &cfg).unwrap();
174
175        assert!(CONFIG.exists(&store));
176
177        const OPTIONAL: Item<Option<u32>> = Item::new("optional");
178
179        assert!(!OPTIONAL.exists(&store));
180
181        OPTIONAL.save(&mut store, &None).unwrap();
182
183        assert!(OPTIONAL.exists(&store));
184    }
185
186    #[test]
187    fn remove_works() {
188        let mut store = MockStorage::new();
189
190        // store data
191        let cfg = Config {
192            owner: "admin".to_string(),
193            max_tokens: 1234,
194        };
195        CONFIG.save(&mut store, &cfg).unwrap();
196        assert_eq!(cfg, CONFIG.load(&store).unwrap());
197
198        // remove it and loads None
199        CONFIG.remove(&mut store);
200        assert!(!CONFIG.exists(&store));
201
202        // safe to remove 2 times
203        CONFIG.remove(&mut store);
204        assert!(!CONFIG.exists(&store));
205    }
206
207    #[test]
208    fn isolated_reads() {
209        let mut store = MockStorage::new();
210
211        let cfg = Config {
212            owner: "admin".to_string(),
213            max_tokens: 1234,
214        };
215        CONFIG.save(&mut store, &cfg).unwrap();
216
217        let reader = Item::<Config>::new("config");
218        assert_eq!(cfg, reader.load(&store).unwrap());
219
220        let other_reader = Item::<Config>::new("config2");
221        assert_eq!(other_reader.may_load(&store).unwrap(), None);
222    }
223
224    #[test]
225    fn update_success() {
226        let mut store = MockStorage::new();
227
228        let cfg = Config {
229            owner: "admin".to_string(),
230            max_tokens: 1234,
231        };
232        CONFIG.save(&mut store, &cfg).unwrap();
233
234        let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
235            c.max_tokens *= 2;
236            Ok(c)
237        });
238        let expected = Config {
239            owner: "admin".to_string(),
240            max_tokens: 2468,
241        };
242        assert_eq!(output.unwrap(), expected);
243        assert_eq!(CONFIG.load(&store).unwrap(), expected);
244    }
245
246    #[test]
247    fn update_can_change_variable_from_outer_scope() {
248        let mut store = MockStorage::new();
249        let cfg = Config {
250            owner: "admin".to_string(),
251            max_tokens: 1234,
252        };
253        CONFIG.save(&mut store, &cfg).unwrap();
254
255        let mut old_max_tokens = 0i32;
256        CONFIG
257            .update(&mut store, |mut c| -> StdResult<_> {
258                old_max_tokens = c.max_tokens;
259                c.max_tokens *= 2;
260                Ok(c)
261            })
262            .unwrap();
263        assert_eq!(old_max_tokens, 1234);
264    }
265
266    #[test]
267    fn update_does_not_change_data_on_error() {
268        let mut store = MockStorage::new();
269
270        let cfg = Config {
271            owner: "admin".to_string(),
272            max_tokens: 1234,
273        };
274        CONFIG.save(&mut store, &cfg).unwrap();
275
276        let output = CONFIG.update(&mut store, |_c| Err(StdError::msg("overflow")));
277        assert_eq!(
278            "kind: Other, error: overflow",
279            output.unwrap_err().to_string()
280        );
281        assert_eq!(CONFIG.load(&store).unwrap(), cfg);
282    }
283
284    #[test]
285    fn update_supports_custom_errors() {
286        #[derive(Debug)]
287        enum MyError {
288            Std(StdError),
289            Foo,
290        }
291
292        impl From<StdError> for MyError {
293            fn from(original: StdError) -> MyError {
294                MyError::Std(original)
295            }
296        }
297
298        let mut store = MockStorage::new();
299
300        let cfg = Config {
301            owner: "admin".to_string(),
302            max_tokens: 1234,
303        };
304        CONFIG.save(&mut store, &cfg).unwrap();
305
306        let res = CONFIG.update(&mut store, |mut c| {
307            if c.max_tokens > 5000 {
308                return Err(MyError::Foo);
309            }
310            if c.max_tokens > 20 {
311                return Err(StdError::msg("broken stuff").into()); // Uses Into to convert StdError to MyError
312            }
313            if c.max_tokens > 10 {
314                to_json_vec(&c)?; // Uses From to convert StdError to MyError
315            }
316            c.max_tokens += 20;
317            Ok(c)
318        });
319
320        match res.unwrap_err() {
321            MyError::Std(std) if std.to_string() == "kind: Other, error: broken stuff" => {}
322            err => panic!("Unexpected error: {err:?}"),
323        }
324        assert_eq!(CONFIG.load(&store).unwrap(), cfg);
325    }
326
327    #[test]
328    fn readme_works() -> StdResult<()> {
329        let mut store = MockStorage::new();
330
331        // may_load returns Option<T>, so None if data is missing
332        // load returns T and Err(StdError::NotFound{}) if data is missing
333        let empty = CONFIG.may_load(&store)?;
334        assert_eq!(None, empty);
335        let cfg = Config {
336            owner: "admin".to_string(),
337            max_tokens: 1234,
338        };
339        CONFIG.save(&mut store, &cfg)?;
340        let loaded = CONFIG.load(&store)?;
341        assert_eq!(cfg, loaded);
342
343        // update an item with a closure (includes read and write)
344        // returns the newly saved value
345        let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
346            c.max_tokens *= 2;
347            Ok(c)
348        })?;
349        assert_eq!(2468, output.max_tokens);
350
351        // you can error in an update and nothing is saved
352        let failed = CONFIG.update(&mut store, |_| -> StdResult<_> {
353            Err(StdError::msg("failure mode"))
354        });
355        assert!(failed.is_err());
356
357        // loading data will show the first update was saved
358        let loaded = CONFIG.load(&store)?;
359        let expected = Config {
360            owner: "admin".to_string(),
361            max_tokens: 2468,
362        };
363        assert_eq!(expected, loaded);
364
365        // we can remove data as well
366        CONFIG.remove(&mut store);
367        let empty = CONFIG.may_load(&store)?;
368        assert_eq!(None, empty);
369
370        Ok(())
371    }
372}