#![allow(rustdoc::private_intra_doc_links)]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
#![deny(unsafe_code)]
use alloc::vec::Vec;
use codec::{Decode, Encode, EncodeLike, FullCodec};
use core::marker::PhantomData;
use frame_support::{
defensive,
storage::StoragePrefixedContainer,
traits::{Get, StorageInstance},
CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound,
};
use sp_runtime::traits::Saturating;
pub type PageIndex = u32;
pub type ValueIndex = u32;
pub struct StoragePagedList<Prefix, Value, ValuesPerNewPage> {
_phantom: PhantomData<(Prefix, Value, ValuesPerNewPage)>,
}
#[derive(
Encode, Decode, CloneNoBound, PartialEqNoBound, EqNoBound, DebugNoBound, DefaultNoBound,
)]
pub struct StoragePagedListMeta<Prefix, Value, ValuesPerNewPage> {
pub first_page: PageIndex,
pub first_value_offset: ValueIndex,
pub last_page: PageIndex,
pub last_page_len: ValueIndex,
_phantom: PhantomData<(Prefix, Value, ValuesPerNewPage)>,
}
impl<Prefix, Value, ValuesPerNewPage> frame_support::storage::StorageAppender<Value>
for StoragePagedListMeta<Prefix, Value, ValuesPerNewPage>
where
Prefix: StorageInstance,
Value: FullCodec,
ValuesPerNewPage: Get<u32>,
{
fn append<EncodeLikeValue>(&mut self, item: EncodeLikeValue)
where
EncodeLikeValue: EncodeLike<Value>,
{
self.append_one(item);
}
}
impl<Prefix, Value, ValuesPerNewPage> StoragePagedListMeta<Prefix, Value, ValuesPerNewPage>
where
Prefix: StorageInstance,
Value: FullCodec,
ValuesPerNewPage: Get<u32>,
{
pub fn from_storage() -> Option<Self> {
let key = Self::key();
sp_io::storage::get(&key).and_then(|raw| Self::decode(&mut &raw[..]).ok())
}
pub fn key() -> Vec<u8> {
meta_key::<Prefix>()
}
pub fn append_one<EncodeLikeValue>(&mut self, item: EncodeLikeValue)
where
EncodeLikeValue: EncodeLike<Value>,
{
if self.last_page_len >= ValuesPerNewPage::get() {
self.last_page.saturating_inc();
self.last_page_len = 0;
}
let key = page_key::<Prefix>(self.last_page);
self.last_page_len.saturating_inc();
sp_io::storage::append(&key, item.encode());
self.store();
}
pub fn store(&self) {
let key = Self::key();
self.using_encoded(|enc| sp_io::storage::set(&key, enc));
}
pub fn reset(&mut self) {
*self = Default::default();
Self::delete();
}
pub fn delete() {
sp_io::storage::clear(&Self::key());
}
}
pub struct Page<V> {
index: PageIndex,
values: core::iter::Skip<alloc::vec::IntoIter<V>>,
}
impl<V: FullCodec> Page<V> {
pub fn from_storage<Prefix: StorageInstance>(
index: PageIndex,
value_index: ValueIndex,
) -> Option<Self> {
let key = page_key::<Prefix>(index);
let values = sp_io::storage::get(&key)
.and_then(|raw| alloc::vec::Vec::<V>::decode(&mut &raw[..]).ok())?;
if values.is_empty() {
return None
}
let values = values.into_iter().skip(value_index as usize);
Some(Self { index, values })
}
pub fn is_eof(&self) -> bool {
self.values.len() == 0
}
pub fn delete<Prefix: StorageInstance>(&self) {
delete_page::<Prefix>(self.index);
}
}
pub(crate) fn delete_page<Prefix: StorageInstance>(index: PageIndex) {
let key = page_key::<Prefix>(index);
sp_io::storage::clear(&key);
}
pub(crate) fn page_key<Prefix: StorageInstance>(index: PageIndex) -> Vec<u8> {
(StoragePagedListPrefix::<Prefix>::final_prefix(), b"page", index).encode()
}
pub(crate) fn meta_key<Prefix: StorageInstance>() -> Vec<u8> {
(StoragePagedListPrefix::<Prefix>::final_prefix(), b"meta").encode()
}
impl<V> Iterator for Page<V> {
type Item = V;
fn next(&mut self) -> Option<Self::Item> {
self.values.next()
}
}
pub struct StoragePagedListIterator<Prefix, Value, ValuesPerNewPage> {
page: Option<Page<Value>>,
drain: bool,
meta: StoragePagedListMeta<Prefix, Value, ValuesPerNewPage>,
}
impl<Prefix, Value, ValuesPerNewPage> StoragePagedListIterator<Prefix, Value, ValuesPerNewPage>
where
Prefix: StorageInstance,
Value: FullCodec,
ValuesPerNewPage: Get<u32>,
{
pub fn from_meta(
meta: StoragePagedListMeta<Prefix, Value, ValuesPerNewPage>,
drain: bool,
) -> Self {
let page = Page::<Value>::from_storage::<Prefix>(meta.first_page, meta.first_value_offset);
Self { page, drain, meta }
}
}
impl<Prefix, Value, ValuesPerNewPage> Iterator
for StoragePagedListIterator<Prefix, Value, ValuesPerNewPage>
where
Prefix: StorageInstance,
Value: FullCodec,
ValuesPerNewPage: Get<u32>,
{
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
let page = self.page.as_mut()?;
let value = match page.next() {
Some(value) => value,
None => {
defensive!("There are no empty pages in storage; nuking the list");
self.meta.reset();
self.page = None;
return None
},
};
if page.is_eof() {
if self.drain {
page.delete::<Prefix>();
self.meta.first_value_offset = 0;
self.meta.first_page.saturating_inc();
}
debug_assert!(!self.drain || self.meta.first_page == page.index + 1);
self.page = Page::from_storage::<Prefix>(page.index.saturating_add(1), 0);
if self.drain {
if self.page.is_none() {
self.meta.reset();
} else {
self.meta.store();
}
}
} else {
if self.drain {
self.meta.first_value_offset.saturating_inc();
self.meta.store();
}
}
Some(value)
}
}
impl<Prefix, Value, ValuesPerNewPage> frame_support::storage::StorageList<Value>
for StoragePagedList<Prefix, Value, ValuesPerNewPage>
where
Prefix: StorageInstance,
Value: FullCodec,
ValuesPerNewPage: Get<u32>,
{
type Iterator = StoragePagedListIterator<Prefix, Value, ValuesPerNewPage>;
type Appender = StoragePagedListMeta<Prefix, Value, ValuesPerNewPage>;
fn iter() -> Self::Iterator {
StoragePagedListIterator::from_meta(Self::read_meta(), false)
}
fn drain() -> Self::Iterator {
StoragePagedListIterator::from_meta(Self::read_meta(), true)
}
fn appender() -> Self::Appender {
Self::appender()
}
}
impl<Prefix, Value, ValuesPerNewPage> StoragePagedList<Prefix, Value, ValuesPerNewPage>
where
Prefix: StorageInstance,
Value: FullCodec,
ValuesPerNewPage: Get<u32>,
{
fn read_meta() -> StoragePagedListMeta<Prefix, Value, ValuesPerNewPage> {
StoragePagedListMeta::from_storage().unwrap_or_default()
}
fn appender() -> StoragePagedListMeta<Prefix, Value, ValuesPerNewPage> {
Self::read_meta()
}
#[cfg(test)]
fn as_vec() -> Vec<Value> {
<Self as frame_support::storage::StorageList<_>>::iter().collect()
}
#[cfg(test)]
fn as_drained_vec() -> Vec<Value> {
<Self as frame_support::storage::StorageList<_>>::drain().collect()
}
}
struct StoragePagedListPrefix<Prefix>(PhantomData<Prefix>);
impl<Prefix> StoragePrefixedContainer for StoragePagedListPrefix<Prefix>
where
Prefix: StorageInstance,
{
fn pallet_prefix() -> &'static [u8] {
Prefix::pallet_prefix().as_bytes()
}
fn storage_prefix() -> &'static [u8] {
Prefix::STORAGE_PREFIX.as_bytes()
}
}
impl<Prefix, Value, ValuesPerNewPage> StoragePrefixedContainer
for StoragePagedList<Prefix, Value, ValuesPerNewPage>
where
Prefix: StorageInstance,
Value: FullCodec,
ValuesPerNewPage: Get<u32>,
{
fn pallet_prefix() -> &'static [u8] {
StoragePagedListPrefix::<Prefix>::pallet_prefix()
}
fn storage_prefix() -> &'static [u8] {
StoragePagedListPrefix::<Prefix>::storage_prefix()
}
}
#[cfg(feature = "std")]
#[allow(dead_code)]
pub(crate) mod mock {
pub use super::*;
pub use frame_support::parameter_types;
#[cfg(test)]
pub use frame_support::{storage::StorageList as _, StorageNoopGuard};
#[cfg(test)]
pub use sp_io::TestExternalities;
parameter_types! {
pub const ValuesPerNewPage: u32 = 5;
pub const MaxPages: Option<u32> = Some(20);
}
pub struct Prefix;
impl StorageInstance for Prefix {
fn pallet_prefix() -> &'static str {
"test"
}
const STORAGE_PREFIX: &'static str = "foo";
}
pub type List = StoragePagedList<Prefix, u32, ValuesPerNewPage>;
}
#[cfg(test)]
mod tests {
use super::mock::*;
#[test]
fn append_works() {
TestExternalities::default().execute_with(|| {
List::append_many(0..1000);
assert_eq!(List::as_vec(), (0..1000).collect::<Vec<_>>());
});
}
#[test]
fn simple_drain_works() {
TestExternalities::default().execute_with(|| {
let _g = StorageNoopGuard::default(); List::append_many(0..1000);
assert_eq!(List::as_drained_vec(), (0..1000).collect::<Vec<_>>());
assert_eq!(List::read_meta(), Default::default());
assert_eq!(List::as_vec(), Vec::<u32>::new());
StoragePagedListMeta::<Prefix, u32, ValuesPerNewPage>::delete();
});
}
#[test]
fn partial_drain_works() {
TestExternalities::default().execute_with(|| {
List::append_many(0..100);
let vals = List::drain().take(50).collect::<Vec<_>>();
assert_eq!(vals, (0..50).collect::<Vec<_>>());
let meta = List::read_meta();
assert_eq!((meta.first_page, meta.first_value_offset), (10, 0));
assert_eq!(List::as_vec(), (50..100).collect::<Vec<_>>());
});
}
#[test]
fn drain_append_iter_works() {
TestExternalities::default().execute_with(|| {
for r in 1..=100 {
List::append_many(0..12);
List::append_many(0..12);
let dropped = List::drain().take(12).collect::<Vec<_>>();
assert_eq!(dropped, (0..12).collect::<Vec<_>>());
assert_eq!(List::as_vec(), (0..12).cycle().take(r * 12).collect::<Vec<_>>());
}
});
}
#[test]
fn drain_eager_page_removal() {
TestExternalities::default().execute_with(|| {
List::append_many(0..9);
assert!(sp_io::storage::exists(&page_key::<Prefix>(0)));
assert!(sp_io::storage::exists(&page_key::<Prefix>(1)));
assert_eq!(List::drain().take(5).count(), 5);
assert!(!sp_io::storage::exists(&page_key::<Prefix>(0)));
assert!(sp_io::storage::exists(&page_key::<Prefix>(1)));
});
}
#[test]
fn append_storage_layout() {
TestExternalities::default().execute_with(|| {
List::append_many(0..9);
let key = page_key::<Prefix>(0);
let raw = sp_io::storage::get(&key).expect("Page should be present");
let as_vec = Vec::<u32>::decode(&mut &raw[..]).unwrap();
assert_eq!(as_vec.len(), 5, "First page contains 5");
let key = page_key::<Prefix>(1);
let raw = sp_io::storage::get(&key).expect("Page should be present");
let as_vec = Vec::<u32>::decode(&mut &raw[..]).unwrap();
assert_eq!(as_vec.len(), 4, "Second page contains 4");
let meta = sp_io::storage::get(&meta_key::<Prefix>()).expect("Meta should be present");
let meta: StoragePagedListMeta<Prefix, u32, ValuesPerNewPage> =
Decode::decode(&mut &meta[..]).unwrap();
assert_eq!(meta.first_page, 0);
assert_eq!(meta.first_value_offset, 0);
assert_eq!(meta.last_page, 1);
assert_eq!(meta.last_page_len, 4);
let page = Page::<u32>::from_storage::<Prefix>(0, 0).unwrap();
assert_eq!(page.index, 0);
assert_eq!(page.values.count(), 5);
let page = Page::<u32>::from_storage::<Prefix>(1, 0).unwrap();
assert_eq!(page.index, 1);
assert_eq!(page.values.count(), 4);
});
}
#[test]
fn page_key_correct() {
let got = page_key::<Prefix>(0);
let pallet_prefix = StoragePagedListPrefix::<Prefix>::final_prefix();
let want = (pallet_prefix, b"page", 0).encode();
assert_eq!(want.len(), 32 + 4 + 4);
assert!(want.starts_with(&pallet_prefix[..]));
assert_eq!(got, want);
}
#[test]
fn meta_key_correct() {
let got = meta_key::<Prefix>();
let pallet_prefix = StoragePagedListPrefix::<Prefix>::final_prefix();
let want = (pallet_prefix, b"meta").encode();
assert_eq!(want.len(), 32 + 4);
assert!(want.starts_with(&pallet_prefix[..]));
assert_eq!(got, want);
}
#[test]
fn peekable_drain_also_deletes() {
TestExternalities::default().execute_with(|| {
List::append_many(0..10);
let mut iter = List::drain().peekable();
assert_eq!(iter.peek(), Some(&0));
assert_eq!(List::iter().count(), 9);
});
}
}