use serde::{de::DeserializeOwned, ser::Serialize};
use std::marker::PhantomData;
use cosmwasm_std::{to_vec, ReadonlyStorage, StdResult, Storage};
use crate::length_prefixed::to_length_prefixed;
use crate::type_helpers::{may_deserialize, must_deserialize};
pub fn singleton<'a, S: Storage, T>(storage: &'a mut S, key: &[u8]) -> Singleton<'a, S, T>
where
T: Serialize + DeserializeOwned,
{
Singleton::new(storage, key)
}
pub fn singleton_read<'a, S: ReadonlyStorage, T>(
storage: &'a S,
key: &[u8],
) -> ReadonlySingleton<'a, S, T>
where
T: Serialize + DeserializeOwned,
{
ReadonlySingleton::new(storage, key)
}
pub struct Singleton<'a, S: Storage, T>
where
T: Serialize + DeserializeOwned,
{
storage: &'a mut S,
key: Vec<u8>,
data: PhantomData<&'a T>,
}
impl<'a, S: Storage, T> Singleton<'a, S, T>
where
T: Serialize + DeserializeOwned,
{
pub fn new(storage: &'a mut S, 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_vec(data)?)
}
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>(&mut self, action: A) -> StdResult<T>
where
A: FnOnce(T) -> StdResult<T>,
{
let input = self.load()?;
let output = action(input)?;
self.save(&output)?;
Ok(output)
}
}
pub struct ReadonlySingleton<'a, S: ReadonlyStorage, T>
where
T: Serialize + DeserializeOwned,
{
storage: &'a S,
key: Vec<u8>,
data: PhantomData<&'a T>,
}
impl<'a, S: ReadonlyStorage, T> ReadonlySingleton<'a, S, T>
where
T: Serialize + DeserializeOwned,
{
pub fn new(storage: &'a S, 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 test {
use super::*;
use cosmwasm_std::testing::MockStorage;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{unauthorized, 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 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| {
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| {
old_max_tokens = c.max_tokens;
c.max_tokens *= 2;
Ok(c)
})
.unwrap();
assert_eq!(old_max_tokens, 1234);
}
#[test]
fn update_failure() {
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(unauthorized()));
match output {
Err(StdError::Unauthorized { .. }) => {}
_ => panic!("Unexpected output: {:?}", output),
}
assert_eq!(writer.load().unwrap(), cfg);
}
}