use frame_support::storage::StorageMap;
use sp_std::marker::PhantomData;
pub trait SparseBitmap<BitMap>
where
BitMap: StorageMap<u64, u128, Query = u128>,
{
fn get(index: u64) -> bool;
fn set(index: u64);
}
pub struct SparseBitmapImpl<BitMap>(PhantomData<BitMap>);
impl<BitMap> SparseBitmapImpl<BitMap>
where
BitMap: StorageMap<u64, u128, Query = u128>,
{
fn compute_bucket_and_mask(index: u64) -> (u64, u128) {
(index >> 7, 1u128 << (index & 127))
}
}
impl<BitMap> SparseBitmap<BitMap> for SparseBitmapImpl<BitMap>
where
BitMap: StorageMap<u64, u128, Query = u128>,
{
fn get(index: u64) -> bool {
let (bucket, mask) = Self::compute_bucket_and_mask(index);
let bucket_value = BitMap::get(bucket);
bucket_value & mask != 0
}
fn set(index: u64) {
let (bucket, mask) = Self::compute_bucket_and_mask(index);
BitMap::mutate(bucket, |value| {
*value |= mask; });
}
}
#[cfg(test)]
mod tests {
use super::*;
use frame_support::{
storage::{generator::StorageMap as StorageMapHelper, storage_prefix},
Twox64Concat,
};
use sp_io::TestExternalities;
pub struct MockStorageMap;
impl StorageMapHelper<u64, u128> for MockStorageMap {
type Query = u128;
type Hasher = Twox64Concat;
fn pallet_prefix() -> &'static [u8] {
b"MyModule"
}
fn storage_prefix() -> &'static [u8] {
b"MyStorageMap"
}
fn prefix_hash() -> [u8; 32] {
storage_prefix(Self::pallet_prefix(), Self::storage_prefix())
}
fn from_optional_value_to_query(v: Option<u128>) -> Self::Query {
v.unwrap_or_default()
}
fn from_query_to_optional_value(v: Self::Query) -> Option<u128> {
Some(v)
}
}
type TestSparseBitmap = SparseBitmapImpl<MockStorageMap>;
#[test]
fn test_sparse_bitmap_set_and_get() {
TestExternalities::default().execute_with(|| {
let index = 300u64;
let (bucket, mask) = TestSparseBitmap::compute_bucket_and_mask(index);
assert_eq!(MockStorageMap::get(bucket), 0);
assert!(!TestSparseBitmap::get(index));
TestSparseBitmap::set(index);
assert_eq!(MockStorageMap::get(bucket), mask);
assert!(TestSparseBitmap::get(index));
});
}
#[test]
fn test_sparse_bitmap_multiple_sets() {
TestExternalities::default().execute_with(|| {
let index1 = 300u64;
let index2 = 305u64; let (bucket, _) = TestSparseBitmap::compute_bucket_and_mask(index1);
let (_, mask1) = TestSparseBitmap::compute_bucket_and_mask(index1);
let (_, mask2) = TestSparseBitmap::compute_bucket_and_mask(index2);
assert_eq!(MockStorageMap::get(bucket), 0);
assert!(!TestSparseBitmap::get(index1));
assert!(!TestSparseBitmap::get(index2));
TestSparseBitmap::set(index1);
assert_eq!(MockStorageMap::get(bucket), mask1);
assert!(TestSparseBitmap::get(index1));
assert!(!TestSparseBitmap::get(index2));
TestSparseBitmap::set(index2);
assert_eq!(MockStorageMap::get(bucket), mask1 | mask2); assert!(TestSparseBitmap::get(index1));
assert!(TestSparseBitmap::get(index2));
})
}
#[test]
fn test_sparse_bitmap_different_buckets() {
TestExternalities::default().execute_with(|| {
let index1 = 300u64; let index2 = 300u64 + (1 << 7); let (bucket1, _) = TestSparseBitmap::compute_bucket_and_mask(index1);
let (bucket2, _) = TestSparseBitmap::compute_bucket_and_mask(index2);
let (_, mask1) = TestSparseBitmap::compute_bucket_and_mask(index1);
let (_, mask2) = TestSparseBitmap::compute_bucket_and_mask(index2);
assert_eq!(MockStorageMap::get(bucket1), 0);
assert_eq!(MockStorageMap::get(bucket2), 0);
TestSparseBitmap::set(index1);
TestSparseBitmap::set(index2);
assert_eq!(MockStorageMap::get(bucket1), mask1); assert_eq!(MockStorageMap::get(bucket2), mask2); assert!(TestSparseBitmap::get(index1));
assert!(TestSparseBitmap::get(index2));
})
}
#[test]
fn test_sparse_bitmap_wide_range() {
TestExternalities::default().execute_with(|| {
let test_indices = [
0u64, 1u64, 127u64, 128u64, 255u64, 1000u64, 123456u64, (1u64 << 32) - 1, 1u64 << 32, (1u64 << 32) + 1, (1u64 << 40) - 1, (1u64 << 40), (1u64 << 40) + 1, u64::MAX / 2, u64::MAX - 128, u64::MAX - 1, u64::MAX, ];
for &index in &test_indices {
assert!(!TestSparseBitmap::get(index), "Index {} should initially be unset", index);
TestSparseBitmap::set(index);
assert!(
TestSparseBitmap::get(index),
"Index {} should be set after setting",
index
);
let (bucket, mask) = TestSparseBitmap::compute_bucket_and_mask(index);
let value = MockStorageMap::get(bucket);
assert!(value & mask != 0, "Storage for index {} should have bit set", index);
}
for &index in &test_indices {
assert!(TestSparseBitmap::get(index), "Index {} should still be set", index);
}
})
}
#[test]
fn test_sparse_bitmap_bucket_boundaries() {
TestExternalities::default().execute_with(|| {
let boundary_pairs = [
(127u64, 128u64), (255u64, 256u64), (1023u64, 1024u64), ];
for (i1, i2) in boundary_pairs {
let (b1, m1) = TestSparseBitmap::compute_bucket_and_mask(i1);
let (b2, m2) = TestSparseBitmap::compute_bucket_and_mask(i2);
assert_ne!(b1, b2, "Indices {} and {} should be in different buckets", i1, i2);
TestSparseBitmap::set(i1);
TestSparseBitmap::set(i2);
assert!(TestSparseBitmap::get(i1), "Boundary index {} should be set", i1);
assert!(TestSparseBitmap::get(i2), "Boundary index {} should be set", i2);
let stored_b1_value = MockStorageMap::get(b1);
let stored_b2_value = MockStorageMap::get(b2);
assert_ne!(stored_b1_value, 0, "Storage for bucket {} should not be 0", b1);
assert_ne!(stored_b2_value, 0, "Storage for bucket {} should not be 0", b2);
assert!(
stored_b1_value & m1 != 0,
"Bit for index {} should be set in bucket {}",
i1,
b1
);
assert!(
stored_b2_value & m2 != 0,
"Bit for index {} should be set in bucket {}",
i2,
b2
);
}
})
}
#[test]
fn test_sparse_bitmap_large_buckets() {
TestExternalities::default().execute_with(|| {
let large_indices = [u64::MAX - 1, u64::MAX];
for &index in &large_indices {
let (bucket, mask) = TestSparseBitmap::compute_bucket_and_mask(index);
assert_eq!(
bucket,
u64::from(index) >> 7,
"Bucket calculation incorrect for {}",
index
);
TestSparseBitmap::set(index);
assert!(TestSparseBitmap::get(index), "Large index {} should be set", index);
let stored_value = MockStorageMap::get(bucket);
assert_ne!(stored_value, 0, "Storage for bucket {} should not be 0", bucket);
assert!(
stored_value & mask != 0,
"Bit for index {} should be set in bucket {}",
index,
bucket
);
}
})
}
}