mod impls;
use crate::store::key::{Identity, ToKey};
use crate::{env, IntoStorageKey};
use borsh::BorshSerialize;
use std::borrow::Borrow;
use std::fmt;
use std::marker::PhantomData;
use unc_sdk_macros::unc;
#[unc(inside_uncsdk)]
pub struct LookupSet<T, H = Identity>
where
T: BorshSerialize,
H: ToKey,
{
prefix: Box<[u8]>,
#[borsh(skip)]
hasher: PhantomData<fn() -> (T, H)>,
}
impl<T, H> fmt::Debug for LookupSet<T, H>
where
T: BorshSerialize,
H: ToKey,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LookupSet").field("prefix", &self.prefix).finish()
}
}
impl<T> LookupSet<T, Identity>
where
T: BorshSerialize,
{
#[inline]
pub fn new<S>(prefix: S) -> Self
where
S: IntoStorageKey,
{
Self::with_hasher(prefix)
}
}
impl<T, H> LookupSet<T, H>
where
T: BorshSerialize,
H: ToKey,
{
pub fn with_hasher<S>(prefix: S) -> Self
where
S: IntoStorageKey,
{
Self { prefix: prefix.into_storage_key().into_boxed_slice(), hasher: Default::default() }
}
pub fn contains<Q: ?Sized>(&self, value: &Q) -> bool
where
T: Borrow<Q>,
Q: BorshSerialize,
{
let lookup_key = H::to_key(&self.prefix, value, &mut Vec::new());
env::storage_has_key(lookup_key.as_ref())
}
pub fn insert(&mut self, value: T) -> bool {
let lookup_key = H::to_key(&self.prefix, &value, &mut Vec::new());
!env::storage_write(lookup_key.as_ref(), &[])
}
pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> bool
where
T: Borrow<Q>,
Q: BorshSerialize,
{
let lookup_key = H::to_key(&self.prefix, value, &mut Vec::new());
env::storage_remove(lookup_key.as_ref())
}
}
#[cfg(not(target_arch = "wasm32"))]
#[cfg(test)]
mod tests {
use super::LookupSet;
use crate::store::key::{Identity, Keccak256, ToKey};
use crate::test_utils::test_env::setup_free;
use arbitrary::{Arbitrary, Unstructured};
use rand::seq::SliceRandom;
use rand::RngCore;
use rand::{Rng, SeedableRng};
use std::collections::HashSet;
#[test]
fn test_insert_contains() {
let mut set = LookupSet::new(b"m");
let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(0);
let mut baseline = HashSet::new();
for _ in 0..100 {
let value = rng.gen::<u64>();
set.insert(value);
baseline.insert(value);
}
for _ in 0..100 {
let value = rng.gen::<u64>();
assert_eq!(set.contains(&value), baseline.contains(&value));
}
for value in baseline.iter() {
assert!(set.contains(value));
}
}
#[test]
fn test_insert_remove() {
let mut set = LookupSet::new(b"m");
let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(1);
let mut values = vec![];
for _ in 0..100 {
let value = rng.gen::<u64>();
values.push(value);
set.insert(value);
}
values.shuffle(&mut rng);
for value in values {
assert!(set.remove(&value));
}
}
#[test]
fn test_remove_last_reinsert() {
let mut set = LookupSet::new(b"m");
let value1 = 2u64;
set.insert(value1);
let value2 = 4u64;
set.insert(value2);
assert!(set.remove(&value2));
assert!(set.insert(value2));
}
#[test]
fn identity_compat_v1() {
use crate::collections::LookupSet as LS1;
let mut ls1 = LS1::new(b"m");
ls1.insert(&8u8);
ls1.insert(&0);
assert!(ls1.contains(&8));
let mut ls2 = LookupSet::<u8, _>::new(b"m");
assert!(ls2.contains(&8u8));
assert!(ls2.remove(&0));
assert!(!ls1.contains(&0));
}
#[test]
fn test_extend() {
let mut set = LookupSet::new(b"m");
let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(4);
let mut values = vec![];
for _ in 0..100 {
let value = rng.gen::<u64>();
values.push(value);
set.insert(value);
}
for _ in 0..10 {
let mut tmp = vec![];
for _ in 0..=(rng.gen::<u64>() % 20 + 1) {
let value = rng.gen::<u64>();
tmp.push(value);
}
values.extend(tmp.iter().cloned());
set.extend(tmp.iter().cloned());
}
for value in values {
assert!(set.contains(&value));
}
}
#[test]
fn test_debug() {
let set = LookupSet::<u8>::new(b"m");
assert_eq!(format!("{:?}", set), "LookupSet { prefix: [109] }")
}
#[test]
fn test_flush_on_drop() {
let mut set = LookupSet::<_, Keccak256>::with_hasher(b"m");
set.insert(5u8);
assert!(set.contains(&5u8));
drop(set);
let dup_set = LookupSet::<u8, Keccak256>::with_hasher(b"m");
assert!(dup_set.contains(&5u8));
}
#[test]
fn test_contains_all_states() {
let mut set = LookupSet::new(b"m");
assert!(!set.contains(&8));
assert!(!set.contains(&8));
set.insert(8u8);
assert!(set.contains(&8));
set.remove(&8);
assert!(!set.contains(&8));
set.insert(8);
drop(set);
let dup_set = LookupSet::<u8, _>::new(b"m");
assert!(dup_set.contains(&8));
assert!(dup_set.contains(&8));
}
#[test]
fn test_insert_all_states() {
let mut set = LookupSet::new(b"m");
assert!(set.insert(8u8));
assert!(!set.insert(8));
set.remove(&8);
assert!(set.insert(8));
{
let mut dup_set = LookupSet::new(b"m");
dup_set.insert(8u8);
assert!(dup_set.contains(&8));
}
{
let mut dup_set = LookupSet::new(b"m");
assert!(dup_set.contains(&8));
dup_set.insert(8u8);
assert!(dup_set.contains(&8));
}
}
#[test]
fn test_remove_all_states() {
let mut set = LookupSet::new(b"m");
assert!(!set.remove(&8));
assert!(!set.remove(&8));
set.insert(8);
assert!(set.remove(&8));
assert!(!set.remove(&8));
set.insert(8u8);
drop(set);
{
let mut dup_set = LookupSet::new(b"m");
assert!(dup_set.remove(&8));
dup_set.insert(8u8);
}
{
let mut dup_set = LookupSet::<u8, _>::new(b"m");
assert!(dup_set.contains(&8));
assert!(dup_set.remove(&8));
}
}
#[test]
fn test_remove_present_after_insert() {
let lookup_key = Identity::to_key(b"m", &8u8, &mut Vec::new());
{
let mut set = LookupSet::new(b"m");
set.insert(8u8);
}
assert!(crate::env::storage_has_key(&lookup_key));
{
let mut set = LookupSet::new(b"m");
set.insert(8u8);
set.remove(&8);
}
assert!(!crate::env::storage_has_key(&lookup_key));
{
let set = LookupSet::<u8, _>::new(b"m");
assert!(!set.contains(&8u8));
}
}
#[derive(Arbitrary, Debug)]
enum Op {
Insert(u8),
Remove(u8),
Restore,
Contains(u8),
}
#[test]
fn test_arbitrary() {
setup_free();
let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(0);
let mut buf = vec![0; 4096];
for _ in 0..512 {
crate::mock::with_mocked_blockchain(|b| b.take_storage());
rng.fill_bytes(&mut buf);
let mut ls = LookupSet::new(b"l");
let mut hs = HashSet::new();
let u = Unstructured::new(&buf);
if let Ok(ops) = Vec::<Op>::arbitrary_take_rest(u) {
for op in ops {
match op {
Op::Insert(v) => {
let r1 = ls.insert(v);
let r2 = hs.insert(v);
assert_eq!(r1, r2)
}
Op::Remove(v) => {
let r1 = ls.remove(&v);
let r2 = hs.remove(&v);
assert_eq!(r1, r2)
}
Op::Restore => {
ls = LookupSet::new(b"l");
}
Op::Contains(v) => {
let r1 = ls.contains(&v);
let r2 = hs.contains(&v);
assert_eq!(r1, r2)
}
}
}
}
}
}
}