unc_sdk/store/lookup_set/
mod.rs1mod impls;
2
3use crate::store::key::{Identity, ToKey};
4use crate::{env, IntoStorageKey};
5use borsh::BorshSerialize;
6use std::borrow::Borrow;
7use std::fmt;
8use std::marker::PhantomData;
9
10use unc_sdk_macros::unc;
11
12#[unc(inside_uncsdk)]
50pub struct LookupSet<T, H = Identity>
51where
52 T: BorshSerialize,
53 H: ToKey,
54{
55 prefix: Box<[u8]>,
56
57 #[borsh(skip)]
58 hasher: PhantomData<fn() -> (T, H)>,
59}
60
61impl<T, H> fmt::Debug for LookupSet<T, H>
62where
63 T: BorshSerialize,
64 H: ToKey,
65{
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 f.debug_struct("LookupSet").field("prefix", &self.prefix).finish()
68 }
69}
70
71impl<T> LookupSet<T, Identity>
72where
73 T: BorshSerialize,
74{
75 #[inline]
80 pub fn new<S>(prefix: S) -> Self
81 where
82 S: IntoStorageKey,
83 {
84 Self::with_hasher(prefix)
85 }
86}
87
88impl<T, H> LookupSet<T, H>
89where
90 T: BorshSerialize,
91 H: ToKey,
92{
93 pub fn with_hasher<S>(prefix: S) -> Self
102 where
103 S: IntoStorageKey,
104 {
105 Self { prefix: prefix.into_storage_key().into_boxed_slice(), hasher: Default::default() }
106 }
107
108 pub fn contains<Q: ?Sized>(&self, value: &Q) -> bool
113 where
114 T: Borrow<Q>,
115 Q: BorshSerialize,
116 {
117 let lookup_key = H::to_key(&self.prefix, value, &mut Vec::new());
118 env::storage_has_key(lookup_key.as_ref())
119 }
120
121 pub fn insert(&mut self, value: T) -> bool {
127 let lookup_key = H::to_key(&self.prefix, &value, &mut Vec::new());
128 !env::storage_write(lookup_key.as_ref(), &[])
129 }
130
131 pub fn remove<Q: ?Sized>(&mut self, value: &Q) -> bool
136 where
137 T: Borrow<Q>,
138 Q: BorshSerialize,
139 {
140 let lookup_key = H::to_key(&self.prefix, value, &mut Vec::new());
141 env::storage_remove(lookup_key.as_ref())
142 }
143}
144
145#[cfg(not(target_arch = "wasm32"))]
146#[cfg(test)]
147mod tests {
148 use super::LookupSet;
149 use crate::store::key::{Identity, Keccak256, ToKey};
150 use crate::test_utils::test_env::setup_free;
151 use arbitrary::{Arbitrary, Unstructured};
152 use rand::seq::SliceRandom;
153 use rand::RngCore;
154 use rand::{Rng, SeedableRng};
155 use std::collections::HashSet;
156
157 #[test]
158 fn test_insert_contains() {
159 let mut set = LookupSet::new(b"m");
160 let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(0);
161 let mut baseline = HashSet::new();
162 for _ in 0..100 {
163 let value = rng.gen::<u64>();
164 set.insert(value);
165 baseline.insert(value);
166 }
167 for _ in 0..100 {
169 let value = rng.gen::<u64>();
170 assert_eq!(set.contains(&value), baseline.contains(&value));
171 }
172 for value in baseline.iter() {
174 assert!(set.contains(value));
175 }
176 }
177
178 #[test]
179 fn test_insert_remove() {
180 let mut set = LookupSet::new(b"m");
181 let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(1);
182 let mut values = vec![];
183 for _ in 0..100 {
184 let value = rng.gen::<u64>();
185 values.push(value);
186 set.insert(value);
187 }
188 values.shuffle(&mut rng);
189 for value in values {
190 assert!(set.remove(&value));
191 }
192 }
193
194 #[test]
195 fn test_remove_last_reinsert() {
196 let mut set = LookupSet::new(b"m");
197 let value1 = 2u64;
198 set.insert(value1);
199 let value2 = 4u64;
200 set.insert(value2);
201
202 assert!(set.remove(&value2));
203 assert!(set.insert(value2));
204 }
205
206 #[test]
207 fn identity_compat_v1() {
208 use crate::collections::LookupSet as LS1;
209
210 let mut ls1 = LS1::new(b"m");
211 ls1.insert(&8u8);
212 ls1.insert(&0);
213 assert!(ls1.contains(&8));
214
215 let mut ls2 = LookupSet::<u8, _>::new(b"m");
216 assert!(ls2.contains(&8u8));
217 assert!(ls2.remove(&0));
218
219 assert!(!ls1.contains(&0));
220 }
221
222 #[test]
223 fn test_extend() {
224 let mut set = LookupSet::new(b"m");
225 let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(4);
226 let mut values = vec![];
227 for _ in 0..100 {
228 let value = rng.gen::<u64>();
229 values.push(value);
230 set.insert(value);
231 }
232 for _ in 0..10 {
233 let mut tmp = vec![];
234 for _ in 0..=(rng.gen::<u64>() % 20 + 1) {
235 let value = rng.gen::<u64>();
236 tmp.push(value);
237 }
238 values.extend(tmp.iter().cloned());
239 set.extend(tmp.iter().cloned());
240 }
241
242 for value in values {
243 assert!(set.contains(&value));
244 }
245 }
246
247 #[test]
248 fn test_debug() {
249 let set = LookupSet::<u8>::new(b"m");
250
251 assert_eq!(format!("{:?}", set), "LookupSet { prefix: [109] }")
252 }
253
254 #[test]
255 fn test_flush_on_drop() {
256 let mut set = LookupSet::<_, Keccak256>::with_hasher(b"m");
257
258 set.insert(5u8);
260 assert!(set.contains(&5u8));
261
262 drop(set);
264
265 let dup_set = LookupSet::<u8, Keccak256>::with_hasher(b"m");
267
268 assert!(dup_set.contains(&5u8));
270 }
271
272 #[test]
273 fn test_contains_all_states() {
274 let mut set = LookupSet::new(b"m");
275 assert!(!set.contains(&8));
277 assert!(!set.contains(&8));
279 set.insert(8u8);
280 assert!(set.contains(&8));
282 set.remove(&8);
283 assert!(!set.contains(&8));
285 set.insert(8);
286
287 drop(set);
289
290 let dup_set = LookupSet::<u8, _>::new(b"m");
291 assert!(dup_set.contains(&8));
293 assert!(dup_set.contains(&8));
295 }
296
297 #[test]
298 fn test_insert_all_states() {
299 let mut set = LookupSet::new(b"m");
300 assert!(set.insert(8u8));
302 assert!(!set.insert(8));
304 set.remove(&8);
305 assert!(set.insert(8));
307
308 {
309 let mut dup_set = LookupSet::new(b"m");
310 dup_set.insert(8u8);
312 assert!(dup_set.contains(&8));
313 }
314
315 {
316 let mut dup_set = LookupSet::new(b"m");
317 assert!(dup_set.contains(&8));
318 dup_set.insert(8u8);
320 assert!(dup_set.contains(&8));
321 }
322 }
323
324 #[test]
325 fn test_remove_all_states() {
326 let mut set = LookupSet::new(b"m");
327 assert!(!set.remove(&8));
329 assert!(!set.remove(&8));
331 set.insert(8);
332 assert!(set.remove(&8));
334 assert!(!set.remove(&8));
336
337 set.insert(8u8);
339 drop(set);
340
341 {
342 let mut dup_set = LookupSet::new(b"m");
343 assert!(dup_set.remove(&8));
345 dup_set.insert(8u8);
346 }
347
348 {
349 let mut dup_set = LookupSet::<u8, _>::new(b"m");
350 assert!(dup_set.contains(&8));
351 assert!(dup_set.remove(&8));
353 }
354 }
355
356 #[test]
357 fn test_remove_present_after_insert() {
358 let lookup_key = Identity::to_key(b"m", &8u8, &mut Vec::new());
359 {
360 let mut set = LookupSet::new(b"m");
362 set.insert(8u8);
363 }
364 assert!(crate::env::storage_has_key(&lookup_key));
365 {
366 let mut set = LookupSet::new(b"m");
367 set.insert(8u8);
368 set.remove(&8);
369 }
370 assert!(!crate::env::storage_has_key(&lookup_key));
371 {
372 let set = LookupSet::<u8, _>::new(b"m");
373 assert!(!set.contains(&8u8));
374 }
375 }
376
377 #[derive(Arbitrary, Debug)]
378 enum Op {
379 Insert(u8),
380 Remove(u8),
381 Restore,
382 Contains(u8),
383 }
384
385 #[test]
386 fn test_arbitrary() {
387 setup_free();
388
389 let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(0);
390 let mut buf = vec![0; 4096];
391 for _ in 0..512 {
392 crate::mock::with_mocked_blockchain(|b| b.take_storage());
394 rng.fill_bytes(&mut buf);
395
396 let mut ls = LookupSet::new(b"l");
397 let mut hs = HashSet::new();
398 let u = Unstructured::new(&buf);
399 if let Ok(ops) = Vec::<Op>::arbitrary_take_rest(u) {
400 for op in ops {
401 match op {
402 Op::Insert(v) => {
403 let r1 = ls.insert(v);
404 let r2 = hs.insert(v);
405 assert_eq!(r1, r2)
406 }
407 Op::Remove(v) => {
408 let r1 = ls.remove(&v);
409 let r2 = hs.remove(&v);
410 assert_eq!(r1, r2)
411 }
412 Op::Restore => {
413 ls = LookupSet::new(b"l");
414 }
415 Op::Contains(v) => {
416 let r1 = ls.contains(&v);
417 let r2 = hs.contains(&v);
418 assert_eq!(r1, r2)
419 }
420 }
421 }
422 }
423 }
424 }
425}