use sp_core::offchain::StorageKind;
pub type StorageValue = StorageValueRef<'static>;
pub struct StorageValueRef<'a> {
key: &'a [u8],
kind: StorageKind,
}
#[derive(Debug, PartialEq, Eq)]
pub enum StorageRetrievalError {
Undecodable,
}
#[derive(Debug, PartialEq, Eq)]
pub enum MutateStorageError<T, E> {
ConcurrentModification(T),
ValueFunctionFailed(E),
}
impl<'a> StorageValueRef<'a> {
pub fn persistent(key: &'a [u8]) -> Self {
Self { key, kind: StorageKind::PERSISTENT }
}
pub fn local(key: &'a [u8]) -> Self {
Self { key, kind: StorageKind::LOCAL }
}
pub fn set(&self, value: &impl codec::Encode) {
value.using_encoded(|val| sp_io::offchain::local_storage_set(self.kind, self.key, val))
}
pub fn clear(&mut self) {
sp_io::offchain::local_storage_clear(self.kind, self.key)
}
pub fn get<T: codec::Decode>(&self) -> Result<Option<T>, StorageRetrievalError> {
sp_io::offchain::local_storage_get(self.kind, self.key)
.map(|val| T::decode(&mut &*val).map_err(|_| StorageRetrievalError::Undecodable))
.transpose()
}
pub fn mutate<T, E, F>(&self, mutate_val: F) -> Result<T, MutateStorageError<T, E>>
where
T: codec::Codec,
F: FnOnce(Result<Option<T>, StorageRetrievalError>) -> Result<T, E>,
{
let value = sp_io::offchain::local_storage_get(self.kind, self.key);
let decoded = value
.as_deref()
.map(|mut bytes| T::decode(&mut bytes).map_err(|_| StorageRetrievalError::Undecodable))
.transpose();
let val =
mutate_val(decoded).map_err(|err| MutateStorageError::ValueFunctionFailed(err))?;
let set = val.using_encoded(|new_val| {
sp_io::offchain::local_storage_compare_and_set(self.kind, self.key, value, new_val)
});
if set {
Ok(val)
} else {
Err(MutateStorageError::ConcurrentModification(val))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sp_core::offchain::{testing, OffchainDbExt};
use sp_io::TestExternalities;
#[test]
fn should_set_and_get() {
let (offchain, state) = testing::TestOffchainExt::new();
let mut t = TestExternalities::default();
t.register_extension(OffchainDbExt::new(offchain));
t.execute_with(|| {
let val = StorageValue::persistent(b"testval");
assert_eq!(val.get::<u32>(), Ok(None));
val.set(&15_u32);
assert_eq!(val.get::<u32>(), Ok(Some(15_u32)));
assert_eq!(val.get::<Vec<u8>>(), Err(StorageRetrievalError::Undecodable));
assert_eq!(state.read().persistent_storage.get(b"testval"), Some(vec![15_u8, 0, 0, 0]));
})
}
#[test]
fn should_mutate() {
let (offchain, state) = testing::TestOffchainExt::new();
let mut t = TestExternalities::default();
t.register_extension(OffchainDbExt::new(offchain));
t.execute_with(|| {
let val = StorageValue::persistent(b"testval");
let result = val.mutate::<u32, (), _>(|val| {
assert_eq!(val, Ok(None));
Ok(16_u32)
});
assert_eq!(result, Ok(16_u32));
assert_eq!(val.get::<u32>(), Ok(Some(16_u32)));
assert_eq!(state.read().persistent_storage.get(b"testval"), Some(vec![16_u8, 0, 0, 0]));
let res = val.mutate::<u32, (), _>(|val| {
assert_eq!(val, Ok(Some(16_u32)));
Err(())
});
assert_eq!(res, Err(MutateStorageError::ValueFunctionFailed(())));
})
}
}