cosmwasm_storage/
singleton.rs

1use serde::{de::DeserializeOwned, ser::Serialize};
2use std::marker::PhantomData;
3
4use cosmwasm_std::{storage_keys::to_length_prefixed, to_json_vec, StdError, StdResult, Storage};
5
6use crate::type_helpers::{may_deserialize, must_deserialize};
7
8/// An alias of Singleton::new for less verbose usage
9#[deprecated(
10    note = "The crate cosmwasm-storage is unmaintained and will be removed in CosmWasm 2.0. Please consider migrating to cw-storage-plus or simple cosmwasm-std storage calls."
11)]
12pub fn singleton<'a, T>(storage: &'a mut dyn Storage, key: &[u8]) -> Singleton<'a, T>
13where
14    T: Serialize + DeserializeOwned,
15{
16    Singleton::new(storage, key)
17}
18
19/// An alias of ReadonlySingleton::new for less verbose usage
20#[deprecated(
21    note = "The crate cosmwasm-storage is unmaintained and will be removed in CosmWasm 2.0. Please consider migrating to cw-storage-plus or simple cosmwasm-std storage calls."
22)]
23pub fn singleton_read<'a, T>(storage: &'a dyn Storage, key: &[u8]) -> ReadonlySingleton<'a, T>
24where
25    T: Serialize + DeserializeOwned,
26{
27    ReadonlySingleton::new(storage, key)
28}
29
30/// Singleton effectively combines PrefixedStorage with TypedStorage to
31/// work on a single storage key. It performs the to_length_prefixed transformation
32/// on the given name to ensure no collisions, and then provides the standard
33/// TypedStorage accessors, without requiring a key (which is defined in the constructor)
34#[deprecated(
35    note = "The crate cosmwasm-storage is unmaintained and will be removed in CosmWasm 2.0. Please consider migrating to cw-storage-plus or simple cosmwasm-std storage calls."
36)]
37pub struct Singleton<'a, T>
38where
39    T: Serialize + DeserializeOwned,
40{
41    storage: &'a mut dyn Storage,
42    key: Vec<u8>,
43    // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed
44    data: PhantomData<T>,
45}
46
47impl<'a, T> Singleton<'a, T>
48where
49    T: Serialize + DeserializeOwned,
50{
51    pub fn new(storage: &'a mut dyn Storage, key: &[u8]) -> Self {
52        Singleton {
53            storage,
54            key: to_length_prefixed(key),
55            data: PhantomData,
56        }
57    }
58
59    /// save will serialize the model and store, returns an error on serialization issues
60    pub fn save(&mut self, data: &T) -> StdResult<()> {
61        self.storage.set(&self.key, &to_json_vec(data)?);
62        Ok(())
63    }
64
65    pub fn remove(&mut self) {
66        self.storage.remove(&self.key)
67    }
68
69    /// load will return an error if no data is set at the given key, or on parse error
70    pub fn load(&self) -> StdResult<T> {
71        let value = self.storage.get(&self.key);
72        must_deserialize(&value)
73    }
74
75    /// may_load will parse the data stored at the key if present, returns Ok(None) if no data there.
76    /// returns an error on issues parsing
77    pub fn may_load(&self) -> StdResult<Option<T>> {
78        let value = self.storage.get(&self.key);
79        may_deserialize(&value)
80    }
81
82    /// update will load the data, perform the specified action, and store the result
83    /// in the database. This is shorthand for some common sequences, which may be useful
84    ///
85    /// This is the least stable of the APIs, and definitely needs some usage
86    pub fn update<A, E>(&mut self, action: A) -> Result<T, E>
87    where
88        A: FnOnce(T) -> Result<T, E>,
89        E: From<StdError>,
90    {
91        let input = self.load()?;
92        let output = action(input)?;
93        self.save(&output)?;
94        Ok(output)
95    }
96}
97
98/// ReadonlySingleton only requires a Storage and exposes only the
99/// methods of Singleton that don't modify state.
100#[deprecated(
101    note = "The crate cosmwasm-storage is unmaintained and will be removed in CosmWasm 2.0. Please consider migrating to cw-storage-plus or simple cosmwasm-std storage calls."
102)]
103pub struct ReadonlySingleton<'a, T>
104where
105    T: Serialize + DeserializeOwned,
106{
107    storage: &'a dyn Storage,
108    key: Vec<u8>,
109    // see https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-type-parameters for why this is needed
110    data: PhantomData<T>,
111}
112
113impl<'a, T> ReadonlySingleton<'a, T>
114where
115    T: Serialize + DeserializeOwned,
116{
117    pub fn new(storage: &'a dyn Storage, key: &[u8]) -> Self {
118        ReadonlySingleton {
119            storage,
120            key: to_length_prefixed(key),
121            data: PhantomData,
122        }
123    }
124
125    /// load will return an error if no data is set at the given key, or on parse error
126    pub fn load(&self) -> StdResult<T> {
127        let value = self.storage.get(&self.key);
128        must_deserialize(&value)
129    }
130
131    /// may_load will parse the data stored at the key if present, returns Ok(None) if no data there.
132    /// returns an error on issues parsing
133    pub fn may_load(&self) -> StdResult<Option<T>> {
134        let value = self.storage.get(&self.key);
135        may_deserialize(&value)
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use cosmwasm_std::testing::MockStorage;
143    use serde::{Deserialize, Serialize};
144
145    use cosmwasm_std::{OverflowError, OverflowOperation, StdError};
146
147    #[derive(Serialize, Deserialize, PartialEq, Debug)]
148    struct Config {
149        pub owner: String,
150        pub max_tokens: i32,
151    }
152
153    #[test]
154    fn save_and_load() {
155        let mut store = MockStorage::new();
156        let mut single = Singleton::<Config>::new(&mut store, b"config");
157
158        assert!(single.load().is_err());
159        assert_eq!(single.may_load().unwrap(), None);
160
161        let cfg = Config {
162            owner: "admin".to_string(),
163            max_tokens: 1234,
164        };
165        single.save(&cfg).unwrap();
166
167        assert_eq!(cfg, single.load().unwrap());
168    }
169
170    #[test]
171    fn remove_works() {
172        let mut store = MockStorage::new();
173        let mut single = Singleton::<Config>::new(&mut store, b"config");
174
175        // store data
176        let cfg = Config {
177            owner: "admin".to_string(),
178            max_tokens: 1234,
179        };
180        single.save(&cfg).unwrap();
181        assert_eq!(cfg, single.load().unwrap());
182
183        // remove it and loads None
184        single.remove();
185        assert_eq!(None, single.may_load().unwrap());
186
187        // safe to remove 2 times
188        single.remove();
189        assert_eq!(None, single.may_load().unwrap());
190    }
191
192    #[test]
193    fn isolated_reads() {
194        let mut store = MockStorage::new();
195        let mut writer = singleton::<Config>(&mut store, b"config");
196
197        let cfg = Config {
198            owner: "admin".to_string(),
199            max_tokens: 1234,
200        };
201        writer.save(&cfg).unwrap();
202
203        let reader = singleton_read::<Config>(&store, b"config");
204        assert_eq!(cfg, reader.load().unwrap());
205
206        let other_reader = singleton_read::<Config>(&store, b"config2");
207        assert_eq!(other_reader.may_load().unwrap(), None);
208    }
209
210    #[test]
211    fn update_success() {
212        let mut store = MockStorage::new();
213        let mut writer = singleton::<Config>(&mut store, b"config");
214
215        let cfg = Config {
216            owner: "admin".to_string(),
217            max_tokens: 1234,
218        };
219        writer.save(&cfg).unwrap();
220
221        let output = writer.update(|mut c| -> StdResult<_> {
222            c.max_tokens *= 2;
223            Ok(c)
224        });
225        let expected = Config {
226            owner: "admin".to_string(),
227            max_tokens: 2468,
228        };
229        assert_eq!(output.unwrap(), expected);
230        assert_eq!(writer.load().unwrap(), expected);
231    }
232
233    #[test]
234    fn update_can_change_variable_from_outer_scope() {
235        let mut store = MockStorage::new();
236        let mut writer = singleton::<Config>(&mut store, b"config");
237        let cfg = Config {
238            owner: "admin".to_string(),
239            max_tokens: 1234,
240        };
241        writer.save(&cfg).unwrap();
242
243        let mut old_max_tokens = 0i32;
244        writer
245            .update(|mut c| -> StdResult<_> {
246                old_max_tokens = c.max_tokens;
247                c.max_tokens *= 2;
248                Ok(c)
249            })
250            .unwrap();
251        assert_eq!(old_max_tokens, 1234);
252    }
253
254    #[test]
255    fn update_does_not_change_data_on_error() {
256        let mut store = MockStorage::new();
257        let mut writer = singleton::<Config>(&mut store, b"config");
258
259        let cfg = Config {
260            owner: "admin".to_string(),
261            max_tokens: 1234,
262        };
263        writer.save(&cfg).unwrap();
264
265        let output = writer.update(|_c| {
266            Err(StdError::from(OverflowError::new(
267                OverflowOperation::Sub,
268                4,
269                7,
270            )))
271        });
272        match output.unwrap_err() {
273            StdError::Overflow { .. } => {}
274            err => panic!("Unexpected error: {err:?}"),
275        }
276        assert_eq!(writer.load().unwrap(), cfg);
277    }
278
279    #[test]
280    fn update_supports_custom_errors() {
281        #[derive(Debug)]
282        enum MyError {
283            Std(StdError),
284            Foo,
285        }
286
287        impl From<StdError> for MyError {
288            fn from(original: StdError) -> MyError {
289                MyError::Std(original)
290            }
291        }
292
293        let mut store = MockStorage::new();
294        let mut writer = singleton::<Config>(&mut store, b"config");
295
296        let cfg = Config {
297            owner: "admin".to_string(),
298            max_tokens: 1234,
299        };
300        writer.save(&cfg).unwrap();
301
302        let res = writer.update(|mut c| {
303            if c.max_tokens > 5000 {
304                return Err(MyError::Foo);
305            }
306            if c.max_tokens > 20 {
307                return Err(StdError::generic_err("broken stuff").into()); // Uses Into to convert StdError to MyError
308            }
309            if c.max_tokens > 10 {
310                to_json_vec(&c)?; // Uses From to convert StdError to MyError
311            }
312            c.max_tokens += 20;
313            Ok(c)
314        });
315        match res.unwrap_err() {
316            MyError::Std(StdError::GenericErr { .. }) => {}
317            err => panic!("Unexpected error: {err:?}"),
318        }
319        assert_eq!(writer.load().unwrap(), cfg);
320    }
321}