use crate::traits::{
AutoKey,
Packed,
StorableHint,
StorageKey,
};
use core::marker::PhantomData;
use ink_primitives::Key;
use ink_storage_traits::Storable;
use scale::{
Encode,
Error,
Input,
Output,
};
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub struct Mapping<K, V: Packed, KeyType: StorageKey = AutoKey> {
#[allow(clippy::type_complexity)]
_marker: PhantomData<fn() -> (K, V, KeyType)>,
}
impl<K, V, KeyType> Default for Mapping<K, V, KeyType>
where
V: Packed,
KeyType: StorageKey,
{
fn default() -> Self {
Self::new()
}
}
impl<K, V, KeyType> Mapping<K, V, KeyType>
where
V: Packed,
KeyType: StorageKey,
{
pub const fn new() -> Self {
Self {
_marker: PhantomData,
}
}
}
impl<K, V, KeyType> ::core::fmt::Debug for Mapping<K, V, KeyType>
where
V: Packed,
KeyType: StorageKey,
{
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("Mapping")
.field("key", &KeyType::KEY)
.finish()
}
}
impl<K, V, KeyType> Mapping<K, V, KeyType>
where
K: Encode,
V: Packed,
KeyType: StorageKey,
{
#[inline]
pub fn insert<Q, R>(&mut self, key: Q, value: &R) -> Option<u32>
where
Q: scale::EncodeLike<K>,
R: Storable + scale::EncodeLike<V>,
{
ink_env::set_contract_storage(&(&KeyType::KEY, key), value)
}
#[inline]
pub fn try_insert<Q, R>(&mut self, key: Q, value: &R) -> ink_env::Result<Option<u32>>
where
Q: scale::EncodeLike<K>,
R: Storable + scale::EncodeLike<V>,
{
let key_size = <Q as Encode>::encoded_size(&key);
if key_size > ink_env::BUFFER_SIZE {
return Err(ink_env::Error::BufferTooSmall)
}
let value_size = <R as Storable>::encoded_size(value);
if key_size.saturating_add(value_size) > ink_env::BUFFER_SIZE {
return Err(ink_env::Error::BufferTooSmall)
}
Ok(self.insert(key, value))
}
#[inline]
pub fn get<Q>(&self, key: Q) -> Option<V>
where
Q: scale::EncodeLike<K>,
{
ink_env::get_contract_storage(&(&KeyType::KEY, key))
.unwrap_or_else(|error| panic!("Failed to get value in Mapping: {error:?}"))
}
#[inline]
pub fn try_get<Q>(&self, key: Q) -> Option<ink_env::Result<V>>
where
Q: scale::EncodeLike<K>,
{
let key_size = <Q as Encode>::encoded_size(&key);
if key_size > ink_env::BUFFER_SIZE {
return Some(Err(ink_env::Error::BufferTooSmall))
}
let value_size: usize =
ink_env::contains_contract_storage(&(&KeyType::KEY, &key))?
.try_into()
.expect("targets of less than 32bit pointer size are not supported; qed");
if key_size.saturating_add(value_size) > ink_env::BUFFER_SIZE {
return Some(Err(ink_env::Error::BufferTooSmall))
}
self.get(key).map(Ok)
}
#[inline]
pub fn take<Q>(&self, key: Q) -> Option<V>
where
Q: scale::EncodeLike<K>,
{
ink_env::take_contract_storage(&(&KeyType::KEY, key))
.unwrap_or_else(|error| panic!("Failed to take value in Mapping: {error:?}"))
}
#[inline]
pub fn try_take<Q>(&self, key: Q) -> Option<ink_env::Result<V>>
where
Q: scale::EncodeLike<K>,
{
let key_size = <Q as Encode>::encoded_size(&key);
if key_size > ink_env::BUFFER_SIZE {
return Some(Err(ink_env::Error::BufferTooSmall))
}
let value_size: usize =
ink_env::contains_contract_storage(&(&KeyType::KEY, &key))?
.try_into()
.expect("targets of less than 32bit pointer size are not supported; qed");
if key_size.saturating_add(value_size) > ink_env::BUFFER_SIZE {
return Some(Err(ink_env::Error::BufferTooSmall))
}
self.take(key).map(Ok)
}
#[inline]
pub fn size<Q>(&self, key: Q) -> Option<u32>
where
Q: scale::EncodeLike<K>,
{
ink_env::contains_contract_storage(&(&KeyType::KEY, key))
}
#[inline]
pub fn contains<Q>(&self, key: Q) -> bool
where
Q: scale::EncodeLike<K>,
{
ink_env::contains_contract_storage(&(&KeyType::KEY, key)).is_some()
}
#[inline]
pub fn remove<Q>(&self, key: Q)
where
Q: scale::EncodeLike<K>,
{
ink_env::clear_contract_storage(&(&KeyType::KEY, key));
}
}
impl<K, V, KeyType> Storable for Mapping<K, V, KeyType>
where
V: Packed,
KeyType: StorageKey,
{
#[inline]
fn encode<T: Output + ?Sized>(&self, _dest: &mut T) {}
#[inline]
fn decode<I: Input>(_input: &mut I) -> Result<Self, Error> {
Ok(Default::default())
}
#[inline]
fn encoded_size(&self) -> usize {
0
}
}
impl<K, V, Key, InnerKey> StorableHint<Key> for Mapping<K, V, InnerKey>
where
V: Packed,
Key: StorageKey,
InnerKey: StorageKey,
{
type Type = Mapping<K, V, Key>;
type PreferredKey = InnerKey;
}
impl<K, V, KeyType> StorageKey for Mapping<K, V, KeyType>
where
V: Packed,
KeyType: StorageKey,
{
const KEY: Key = KeyType::KEY;
}
#[cfg(feature = "std")]
const _: () = {
use crate::traits::StorageLayout;
use ink_metadata::layout::{
Layout,
LayoutKey,
RootLayout,
};
impl<K, V, KeyType> StorageLayout for Mapping<K, V, KeyType>
where
K: scale_info::TypeInfo + 'static,
V: Packed + StorageLayout + scale_info::TypeInfo + 'static,
KeyType: StorageKey + scale_info::TypeInfo + 'static,
{
fn layout(_: &Key) -> Layout {
Layout::Root(RootLayout::new(
LayoutKey::from(&KeyType::KEY),
<V as StorageLayout>::layout(&KeyType::KEY),
scale_info::meta_type::<Self>(),
))
}
}
};
#[cfg(test)]
mod tests {
use super::*;
use crate::traits::ManualKey;
#[test]
fn insert_and_get_work() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut mapping: Mapping<u8, _> = Mapping::new();
mapping.insert(1, &2);
assert_eq!(mapping.get(1), Some(2));
Ok(())
})
.unwrap()
}
#[test]
fn insert_and_get_work_for_two_mapping_with_same_manual_key() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut mapping: Mapping<u8, u8, ManualKey<123>> = Mapping::new();
mapping.insert(1, &2);
let mapping2: Mapping<u8, u8, ManualKey<123>> = Mapping::new();
assert_eq!(mapping2.get(1), Some(2));
Ok(())
})
.unwrap()
}
#[test]
fn gets_default_if_no_key_set() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mapping: Mapping<u8, u8> = Mapping::new();
assert_eq!(mapping.get(1), None);
Ok(())
})
.unwrap()
}
#[test]
fn insert_and_take_work() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut mapping: Mapping<u8, _> = Mapping::new();
mapping.insert(1, &2);
assert_eq!(mapping.take(1), Some(2));
assert!(mapping.get(1).is_none());
Ok(())
})
.unwrap()
}
#[test]
fn take_empty_value_work() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mapping: Mapping<u8, u8> = Mapping::new();
assert_eq!(mapping.take(1), None);
Ok(())
})
.unwrap()
}
#[test]
fn can_clear_entries() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut mapping: Mapping<u8, u8> = Mapping::new();
mapping.insert(1, &2);
assert_eq!(mapping.get(1), Some(2));
mapping.remove(1);
assert_eq!(mapping.get(1), None);
Ok(())
})
.unwrap()
}
#[test]
fn can_clear_unexistent_entries() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mapping: Mapping<u8, u8> = Mapping::new();
mapping.remove(1);
assert_eq!(mapping.get(1), None);
Ok(())
})
.unwrap()
}
#[test]
fn fallible_storage_works_for_fitting_data() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut mapping: Mapping<u8, [u8; ink_env::BUFFER_SIZE - 1]> = Mapping::new();
let key = 0;
let value = [0u8; ink_env::BUFFER_SIZE - 1];
assert_eq!(mapping.try_insert(key, &value), Ok(None));
assert_eq!(mapping.try_get(key), Some(Ok(value)));
assert_eq!(mapping.try_take(key), Some(Ok(value)));
assert_eq!(mapping.try_get(key), None);
Ok(())
})
.unwrap()
}
#[test]
fn fallible_storage_fails_gracefully_for_overgrown_data() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut mapping: Mapping<u8, [u8; ink_env::BUFFER_SIZE]> = Mapping::new();
let key = 0;
let value = [0u8; ink_env::BUFFER_SIZE];
assert_eq!(mapping.try_get(0), None);
assert_eq!(
mapping.try_insert(key, &value),
Err(ink_env::Error::BufferTooSmall)
);
ink_env::set_contract_storage(&(&mapping.key(), key), &value);
assert_eq!(
mapping.try_get(key),
Some(Err(ink_env::Error::BufferTooSmall))
);
assert_eq!(
mapping.try_take(key),
Some(Err(ink_env::Error::BufferTooSmall))
);
Ok(())
})
.unwrap()
}
#[test]
fn fallible_storage_considers_key_size() {
ink_env::test::run_test::<ink_env::DefaultEnvironment, _>(|_| {
let mut mapping: Mapping<[u8; ink_env::BUFFER_SIZE + 1], u8> = Mapping::new();
let key = [0u8; ink_env::BUFFER_SIZE + 1];
let value = 0;
assert_eq!(
mapping.try_insert(key, &value),
Err(ink_env::Error::BufferTooSmall)
);
ink_env::set_contract_storage(&(&mapping.key(), key), &value);
assert_eq!(
mapping.try_get(key),
Some(Err(ink_env::Error::BufferTooSmall))
);
assert_eq!(
mapping.try_take(key),
Some(Err(ink_env::Error::BufferTooSmall))
);
Ok(())
})
.unwrap()
}
}