use serde::de::DeserializeOwned;
use serde::Serialize;
use std::marker::PhantomData;
use cosmwasm_std::{
from_json, to_json_vec, Addr, CustomQuery, QuerierWrapper, StdError, StdResult, Storage,
WasmQuery,
};
use crate::{helpers::not_found_object_info, namespace::Namespace};
pub struct Item<T> {
storage_key: Namespace,
data_type: PhantomData<T>,
}
impl<T> Item<T> {
pub const fn new(storage_key: &'static str) -> Self {
Item {
storage_key: Namespace::from_static_str(storage_key),
data_type: PhantomData,
}
}
pub fn new_dyn(storage_key: impl Into<Namespace>) -> Self {
Item {
storage_key: storage_key.into(),
data_type: PhantomData,
}
}
}
impl<T> Item<T>
where
T: Serialize + DeserializeOwned,
{
pub fn as_slice(&self) -> &[u8] {
self.storage_key.as_slice()
}
pub fn save(&self, store: &mut dyn Storage, data: &T) -> StdResult<()> {
store.set(self.storage_key.as_slice(), &to_json_vec(data)?);
Ok(())
}
pub fn remove(&self, store: &mut dyn Storage) {
store.remove(self.storage_key.as_slice());
}
pub fn load(&self, store: &dyn Storage) -> StdResult<T> {
if let Some(value) = store.get(self.storage_key.as_slice()) {
from_json(value)
} else {
let object_info = not_found_object_info::<T>(self.storage_key.as_slice());
Err(StdError::not_found(object_info))
}
}
pub fn may_load(&self, store: &dyn Storage) -> StdResult<Option<T>> {
let value = store.get(self.storage_key.as_slice());
value.map(|v| from_json(v)).transpose()
}
pub fn exists(&self, store: &dyn Storage) -> bool {
store.get(self.storage_key.as_slice()).is_some()
}
pub fn update<A, E>(&self, store: &mut dyn Storage, action: A) -> Result<T, E>
where
A: FnOnce(T) -> Result<T, E>,
E: From<StdError>,
{
let input = self.load(store)?;
let output = action(input)?;
self.save(store, &output)?;
Ok(output)
}
pub fn query<Q: CustomQuery>(
&self,
querier: &QuerierWrapper<Q>,
remote_contract: Addr,
) -> StdResult<T> {
let request = WasmQuery::Raw {
contract_addr: remote_contract.into(),
key: (self.storage_key.as_slice()).into(),
};
querier.query(&request.into())
}
}
#[cfg(test)]
mod test {
use super::*;
use cosmwasm_std::testing::MockStorage;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{to_json_vec, OverflowError, OverflowOperation, StdError};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Config {
pub owner: String,
pub max_tokens: i32,
}
const CONFIG: Item<Config> = Item::new("config");
#[test]
fn save_and_load() {
let mut store = MockStorage::new();
assert!(CONFIG.load(&store).is_err());
assert_eq!(CONFIG.may_load(&store).unwrap(), None);
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg).unwrap();
assert_eq!(cfg, CONFIG.load(&store).unwrap());
}
#[test]
fn owned_key_works() {
let mut store = MockStorage::new();
for i in 0..3 {
let key = format!("key{}", i);
let item = Item::new_dyn(key);
item.save(&mut store, &i).unwrap();
}
assert_eq!(store.get(b"key0").unwrap(), b"0");
assert_eq!(store.get(b"key1").unwrap(), b"1");
assert_eq!(store.get(b"key2").unwrap(), b"2");
}
#[test]
fn exists_works() {
let mut store = MockStorage::new();
assert!(!CONFIG.exists(&store));
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg).unwrap();
assert!(CONFIG.exists(&store));
const OPTIONAL: Item<Option<u32>> = Item::new("optional");
assert!(!OPTIONAL.exists(&store));
OPTIONAL.save(&mut store, &None).unwrap();
assert!(OPTIONAL.exists(&store));
}
#[test]
fn remove_works() {
let mut store = MockStorage::new();
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg).unwrap();
assert_eq!(cfg, CONFIG.load(&store).unwrap());
CONFIG.remove(&mut store);
assert!(!CONFIG.exists(&store));
CONFIG.remove(&mut store);
assert!(!CONFIG.exists(&store));
}
#[test]
fn isolated_reads() {
let mut store = MockStorage::new();
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg).unwrap();
let reader = Item::<Config>::new("config");
assert_eq!(cfg, reader.load(&store).unwrap());
let other_reader = Item::<Config>::new("config2");
assert_eq!(other_reader.may_load(&store).unwrap(), None);
}
#[test]
fn update_success() {
let mut store = MockStorage::new();
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg).unwrap();
let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
c.max_tokens *= 2;
Ok(c)
});
let expected = Config {
owner: "admin".to_string(),
max_tokens: 2468,
};
assert_eq!(output.unwrap(), expected);
assert_eq!(CONFIG.load(&store).unwrap(), expected);
}
#[test]
fn update_can_change_variable_from_outer_scope() {
let mut store = MockStorage::new();
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg).unwrap();
let mut old_max_tokens = 0i32;
CONFIG
.update(&mut store, |mut c| -> StdResult<_> {
old_max_tokens = c.max_tokens;
c.max_tokens *= 2;
Ok(c)
})
.unwrap();
assert_eq!(old_max_tokens, 1234);
}
#[test]
fn update_does_not_change_data_on_error() {
let mut store = MockStorage::new();
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg).unwrap();
let output = CONFIG.update(&mut store, |_c| {
Err(StdError::overflow(OverflowError::new(
OverflowOperation::Sub,
)))
});
match output.unwrap_err() {
StdError::Overflow { .. } => {}
err => panic!("Unexpected error: {:?}", err),
}
assert_eq!(CONFIG.load(&store).unwrap(), cfg);
}
#[test]
fn update_supports_custom_errors() {
#[derive(Debug)]
enum MyError {
Std(StdError),
Foo,
}
impl From<StdError> for MyError {
fn from(original: StdError) -> MyError {
MyError::Std(original)
}
}
let mut store = MockStorage::new();
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg).unwrap();
let res = CONFIG.update(&mut store, |mut c| {
if c.max_tokens > 5000 {
return Err(MyError::Foo);
}
if c.max_tokens > 20 {
return Err(StdError::generic_err("broken stuff").into()); }
if c.max_tokens > 10 {
to_json_vec(&c)?; }
c.max_tokens += 20;
Ok(c)
});
match res.unwrap_err() {
MyError::Std(StdError::GenericErr { .. }) => {}
err => panic!("Unexpected error: {:?}", err),
}
assert_eq!(CONFIG.load(&store).unwrap(), cfg);
}
#[test]
fn readme_works() -> StdResult<()> {
let mut store = MockStorage::new();
let empty = CONFIG.may_load(&store)?;
assert_eq!(None, empty);
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
CONFIG.save(&mut store, &cfg)?;
let loaded = CONFIG.load(&store)?;
assert_eq!(cfg, loaded);
let output = CONFIG.update(&mut store, |mut c| -> StdResult<_> {
c.max_tokens *= 2;
Ok(c)
})?;
assert_eq!(2468, output.max_tokens);
let failed = CONFIG.update(&mut store, |_| -> StdResult<_> {
Err(StdError::generic_err("failure mode"))
});
assert!(failed.is_err());
let loaded = CONFIG.load(&store)?;
let expected = Config {
owner: "admin".to_string(),
max_tokens: 2468,
};
assert_eq!(expected, loaded);
CONFIG.remove(&mut store);
let empty = CONFIG.may_load(&store)?;
assert_eq!(None, empty);
Ok(())
}
}