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::not_found(object_info))
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 serde::{Deserialize, Serialize};
121
122    use cosmwasm_std::{to_json_vec, OverflowError, OverflowOperation, StdError};
123
124    #[derive(Serialize, Deserialize, PartialEq, Debug)]
125    struct Config {
126        pub owner: String,
127        pub max_tokens: i32,
128    }
129
130    // note const constructor rather than 2 funcs with Singleton
131    const CONFIG: Item<Config> = Item::new("config");
132
133    #[test]
134    fn save_and_load() {
135        let mut store = MockStorage::new();
136
137        assert!(CONFIG.load(&store).is_err());
138        assert_eq!(CONFIG.may_load(&store).unwrap(), None);
139
140        let cfg = Config {
141            owner: "admin".to_string(),
142            max_tokens: 1234,
143        };
144        CONFIG.save(&mut store, &cfg).unwrap();
145
146        assert_eq!(cfg, CONFIG.load(&store).unwrap());
147    }
148
149    #[test]
150    fn owned_key_works() {
151        let mut store = MockStorage::new();
152
153        for i in 0..3 {
154            let key = format!("key{}", i);
155            let item = Item::new_dyn(key);
156            item.save(&mut store, &i).unwrap();
157        }
158
159        assert_eq!(store.get(b"key0").unwrap(), b"0");
160        assert_eq!(store.get(b"key1").unwrap(), b"1");
161        assert_eq!(store.get(b"key2").unwrap(), b"2");
162    }
163
164    #[test]
165    fn exists_works() {
166        let mut store = MockStorage::new();
167
168        assert!(!CONFIG.exists(&store));
169
170        let cfg = Config {
171            owner: "admin".to_string(),
172            max_tokens: 1234,
173        };
174        CONFIG.save(&mut store, &cfg).unwrap();
175
176        assert!(CONFIG.exists(&store));
177
178        const OPTIONAL: Item<Option<u32>> = Item::new("optional");
179
180        assert!(!OPTIONAL.exists(&store));
181
182        OPTIONAL.save(&mut store, &None).unwrap();
183
184        assert!(OPTIONAL.exists(&store));
185    }
186
187    #[test]
188    fn remove_works() {
189        let mut store = MockStorage::new();
190
191        // store data
192        let cfg = Config {
193            owner: "admin".to_string(),
194            max_tokens: 1234,
195        };
196        CONFIG.save(&mut store, &cfg).unwrap();
197        assert_eq!(cfg, CONFIG.load(&store).unwrap());
198
199        // remove it and loads None
200        CONFIG.remove(&mut store);
201        assert!(!CONFIG.exists(&store));
202
203        // safe to remove 2 times
204        CONFIG.remove(&mut store);
205        assert!(!CONFIG.exists(&store));
206    }
207
208    #[test]
209    fn isolated_reads() {
210        let mut store = MockStorage::new();
211
212        let cfg = Config {
213            owner: "admin".to_string(),
214            max_tokens: 1234,
215        };
216        CONFIG.save(&mut store, &cfg).unwrap();
217
218        let reader = Item::<Config>::new("config");
219        assert_eq!(cfg, reader.load(&store).unwrap());
220
221        let other_reader = Item::<Config>::new("config2");
222        assert_eq!(other_reader.may_load(&store).unwrap(), None);
223    }
224
225    #[test]
226    fn update_success() {
227        let mut store = MockStorage::new();
228
229        let cfg = Config {
230            owner: "admin".to_string(),
231            max_tokens: 1234,
232        };
233        CONFIG.save(&mut store, &cfg).unwrap();
234
235        let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
236            c.max_tokens *= 2;
237            Ok(c)
238        });
239        let expected = Config {
240            owner: "admin".to_string(),
241            max_tokens: 2468,
242        };
243        assert_eq!(output.unwrap(), expected);
244        assert_eq!(CONFIG.load(&store).unwrap(), expected);
245    }
246
247    #[test]
248    fn update_can_change_variable_from_outer_scope() {
249        let mut store = MockStorage::new();
250        let cfg = Config {
251            owner: "admin".to_string(),
252            max_tokens: 1234,
253        };
254        CONFIG.save(&mut store, &cfg).unwrap();
255
256        let mut old_max_tokens = 0i32;
257        CONFIG
258            .update(&mut store, |mut c| -> StdResult<_> {
259                old_max_tokens = c.max_tokens;
260                c.max_tokens *= 2;
261                Ok(c)
262            })
263            .unwrap();
264        assert_eq!(old_max_tokens, 1234);
265    }
266
267    #[test]
268    fn update_does_not_change_data_on_error() {
269        let mut store = MockStorage::new();
270
271        let cfg = Config {
272            owner: "admin".to_string(),
273            max_tokens: 1234,
274        };
275        CONFIG.save(&mut store, &cfg).unwrap();
276
277        let output = CONFIG.update(&mut store, |_c| {
278            Err(StdError::overflow(OverflowError::new(
279                OverflowOperation::Sub,
280            )))
281        });
282        match output.unwrap_err() {
283            StdError::Overflow { .. } => {}
284            err => panic!("Unexpected error: {:?}", err),
285        }
286        assert_eq!(CONFIG.load(&store).unwrap(), cfg);
287    }
288
289    #[test]
290    fn update_supports_custom_errors() {
291        #[derive(Debug)]
292        enum MyError {
293            Std(StdError),
294            Foo,
295        }
296
297        impl From<StdError> for MyError {
298            fn from(original: StdError) -> MyError {
299                MyError::Std(original)
300            }
301        }
302
303        let mut store = MockStorage::new();
304
305        let cfg = Config {
306            owner: "admin".to_string(),
307            max_tokens: 1234,
308        };
309        CONFIG.save(&mut store, &cfg).unwrap();
310
311        let res = CONFIG.update(&mut store, |mut c| {
312            if c.max_tokens > 5000 {
313                return Err(MyError::Foo);
314            }
315            if c.max_tokens > 20 {
316                return Err(StdError::generic_err("broken stuff").into()); // Uses Into to convert StdError to MyError
317            }
318            if c.max_tokens > 10 {
319                to_json_vec(&c)?; // Uses From to convert StdError to MyError
320            }
321            c.max_tokens += 20;
322            Ok(c)
323        });
324        match res.unwrap_err() {
325            MyError::Std(StdError::GenericErr { .. }) => {}
326            err => panic!("Unexpected error: {:?}", err),
327        }
328        assert_eq!(CONFIG.load(&store).unwrap(), cfg);
329    }
330
331    #[test]
332    fn readme_works() -> StdResult<()> {
333        let mut store = MockStorage::new();
334
335        // may_load returns Option<T>, so None if data is missing
336        // load returns T and Err(StdError::NotFound{}) if data is missing
337        let empty = CONFIG.may_load(&store)?;
338        assert_eq!(None, empty);
339        let cfg = Config {
340            owner: "admin".to_string(),
341            max_tokens: 1234,
342        };
343        CONFIG.save(&mut store, &cfg)?;
344        let loaded = CONFIG.load(&store)?;
345        assert_eq!(cfg, loaded);
346
347        // update an item with a closure (includes read and write)
348        // returns the newly saved value
349        let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
350            c.max_tokens *= 2;
351            Ok(c)
352        })?;
353        assert_eq!(2468, output.max_tokens);
354
355        // you can error in an update and nothing is saved
356        let failed = CONFIG.update(&mut store, |_| -> StdResult<_> {
357            Err(StdError::generic_err("failure mode"))
358        });
359        assert!(failed.is_err());
360
361        // loading data will show the first update was saved
362        let loaded = CONFIG.load(&store)?;
363        let expected = Config {
364            owner: "admin".to_string(),
365            max_tokens: 2468,
366        };
367        assert_eq!(expected, loaded);
368
369        // we can remove data as well
370        CONFIG.remove(&mut store);
371        let empty = CONFIG.may_load(&store)?;
372        assert_eq!(None, empty);
373
374        Ok(())
375    }
376}