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
12pub struct Item<T> {
16 storage_key: Namespace,
18 data_type: PhantomData<T>,
20}
21
22impl<T> Item<T> {
23 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 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 pub fn as_slice(&self) -> &[u8] {
48 self.storage_key.as_slice()
49 }
50
51 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 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 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 pub fn exists(&self, store: &dyn Storage) -> bool {
80 store.get(self.storage_key.as_slice()).is_some()
81 }
82
83 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 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 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 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 CONFIG.remove(&mut store);
201 assert!(!CONFIG.exists(&store));
202
203 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()); }
318 if c.max_tokens > 10 {
319 to_json_vec(&c)?; }
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 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 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 let failed = CONFIG.update(&mut store, |_| -> StdResult<_> {
357 Err(StdError::generic_err("failure mode"))
358 });
359 assert!(failed.is_err());
360
361 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 CONFIG.remove(&mut store);
371 let empty = CONFIG.may_load(&store)?;
372 assert_eq!(None, empty);
373
374 Ok(())
375 }
376}