use serde::{de::DeserializeOwned, ser::Serialize};
use std::marker::PhantomData;
use cosmwasm_std::{storage_keys::to_length_prefixed, to_json_vec, StdError, StdResult, Storage};
use crate::type_helpers::{may_deserialize, must_deserialize};
#[deprecated(
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."
)]
pub fn singleton<'a, T>(storage: &'a mut dyn Storage, key: &[u8]) -> Singleton<'a, T>
where
T: Serialize + DeserializeOwned,
{
Singleton::new(storage, key)
}
#[deprecated(
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."
)]
pub fn singleton_read<'a, T>(storage: &'a dyn Storage, key: &[u8]) -> ReadonlySingleton<'a, T>
where
T: Serialize + DeserializeOwned,
{
ReadonlySingleton::new(storage, key)
}
#[deprecated(
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."
)]
pub struct Singleton<'a, T>
where
T: Serialize + DeserializeOwned,
{
storage: &'a mut dyn Storage,
key: Vec<u8>,
data: PhantomData<T>,
}
impl<'a, T> Singleton<'a, T>
where
T: Serialize + DeserializeOwned,
{
pub fn new(storage: &'a mut dyn Storage, key: &[u8]) -> Self {
Singleton {
storage,
key: to_length_prefixed(key),
data: PhantomData,
}
}
pub fn save(&mut self, data: &T) -> StdResult<()> {
self.storage.set(&self.key, &to_json_vec(data)?);
Ok(())
}
pub fn remove(&mut self) {
self.storage.remove(&self.key)
}
pub fn load(&self) -> StdResult<T> {
let value = self.storage.get(&self.key);
must_deserialize(&value)
}
pub fn may_load(&self) -> StdResult<Option<T>> {
let value = self.storage.get(&self.key);
may_deserialize(&value)
}
pub fn update<A, E>(&mut self, action: A) -> Result<T, E>
where
A: FnOnce(T) -> Result<T, E>,
E: From<StdError>,
{
let input = self.load()?;
let output = action(input)?;
self.save(&output)?;
Ok(output)
}
}
#[deprecated(
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."
)]
pub struct ReadonlySingleton<'a, T>
where
T: Serialize + DeserializeOwned,
{
storage: &'a dyn Storage,
key: Vec<u8>,
data: PhantomData<T>,
}
impl<'a, T> ReadonlySingleton<'a, T>
where
T: Serialize + DeserializeOwned,
{
pub fn new(storage: &'a dyn Storage, key: &[u8]) -> Self {
ReadonlySingleton {
storage,
key: to_length_prefixed(key),
data: PhantomData,
}
}
pub fn load(&self) -> StdResult<T> {
let value = self.storage.get(&self.key);
must_deserialize(&value)
}
pub fn may_load(&self) -> StdResult<Option<T>> {
let value = self.storage.get(&self.key);
may_deserialize(&value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::MockStorage;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{OverflowError, OverflowOperation, StdError};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Config {
pub owner: String,
pub max_tokens: i32,
}
#[test]
fn save_and_load() {
let mut store = MockStorage::new();
let mut single = Singleton::<Config>::new(&mut store, b"config");
assert!(single.load().is_err());
assert_eq!(single.may_load().unwrap(), None);
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
single.save(&cfg).unwrap();
assert_eq!(cfg, single.load().unwrap());
}
#[test]
fn remove_works() {
let mut store = MockStorage::new();
let mut single = Singleton::<Config>::new(&mut store, b"config");
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
single.save(&cfg).unwrap();
assert_eq!(cfg, single.load().unwrap());
single.remove();
assert_eq!(None, single.may_load().unwrap());
single.remove();
assert_eq!(None, single.may_load().unwrap());
}
#[test]
fn isolated_reads() {
let mut store = MockStorage::new();
let mut writer = singleton::<Config>(&mut store, b"config");
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
writer.save(&cfg).unwrap();
let reader = singleton_read::<Config>(&store, b"config");
assert_eq!(cfg, reader.load().unwrap());
let other_reader = singleton_read::<Config>(&store, b"config2");
assert_eq!(other_reader.may_load().unwrap(), None);
}
#[test]
fn update_success() {
let mut store = MockStorage::new();
let mut writer = singleton::<Config>(&mut store, b"config");
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
writer.save(&cfg).unwrap();
let output = writer.update(|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!(writer.load().unwrap(), expected);
}
#[test]
fn update_can_change_variable_from_outer_scope() {
let mut store = MockStorage::new();
let mut writer = singleton::<Config>(&mut store, b"config");
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
writer.save(&cfg).unwrap();
let mut old_max_tokens = 0i32;
writer
.update(|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 mut writer = singleton::<Config>(&mut store, b"config");
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
writer.save(&cfg).unwrap();
let output = writer.update(|_c| {
Err(StdError::from(OverflowError::new(
OverflowOperation::Sub,
4,
7,
)))
});
match output.unwrap_err() {
StdError::Overflow { .. } => {}
err => panic!("Unexpected error: {err:?}"),
}
assert_eq!(writer.load().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 mut writer = singleton::<Config>(&mut store, b"config");
let cfg = Config {
owner: "admin".to_string(),
max_tokens: 1234,
};
writer.save(&cfg).unwrap();
let res = writer.update(|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!(writer.load().unwrap(), cfg);
}
}