near_sdk/collections/
lookup_set.rs

1//! A persistent set without iterators. Unlike `near_sdk::collections::LookupSet` this set
2//! doesn't store values separately in a vector, so it can't iterate over the values. But it
3//! makes this implementation more efficient in the number of reads and writes.
4use std::marker::PhantomData;
5
6use borsh::{to_vec, BorshSerialize};
7use near_sdk_macros::near;
8
9use crate::collections::append_slice;
10use crate::{env, IntoStorageKey};
11
12const ERR_ELEMENT_SERIALIZATION: &str = "Cannot serialize element with Borsh";
13
14/// A non-iterable implementation of a set that stores its content directly on the storage trie.
15///
16/// This set stores the values under a hash of the set's `prefix` and [`BorshSerialize`] of the
17/// value.
18#[near(inside_nearsdk)]
19pub struct LookupSet<T> {
20    element_prefix: Vec<u8>,
21    #[borsh(skip)]
22    el: PhantomData<T>,
23}
24
25impl<T> LookupSet<T> {
26    /// Create a new map. Use `element_prefix` as a unique prefix for trie keys.
27    ///
28    /// # Examples
29    ///
30    /// ```
31    /// use near_sdk::collections::LookupSet;
32    /// let mut set: LookupSet<u32> = LookupSet::new(b"s");
33    /// ```
34    pub fn new<S>(element_prefix: S) -> Self
35    where
36        S: IntoStorageKey,
37    {
38        Self { element_prefix: element_prefix.into_storage_key(), el: PhantomData }
39    }
40
41    fn raw_element_to_storage_key(&self, element_raw: &[u8]) -> Vec<u8> {
42        append_slice(&self.element_prefix, element_raw)
43    }
44
45    /// Returns `true` if the serialized key is present in the map.
46    fn contains_raw(&self, element_raw: &[u8]) -> bool {
47        let storage_key = self.raw_element_to_storage_key(element_raw);
48        env::storage_has_key(&storage_key)
49    }
50
51    /// Inserts a serialized element into the set.
52    /// If the set did not have this value present, `true` is returned.
53    /// If the set did have this value present, `false` is returned.
54    pub fn insert_raw(&mut self, element_raw: &[u8]) -> bool {
55        let storage_key = self.raw_element_to_storage_key(element_raw);
56        !env::storage_write(&storage_key, b"")
57    }
58
59    /// Removes a serialized element from the set.
60    /// Returns true if the element was present in the set.
61    pub fn remove_raw(&mut self, element_raw: &[u8]) -> bool {
62        let storage_key = self.raw_element_to_storage_key(element_raw);
63        env::storage_remove(&storage_key)
64    }
65}
66
67impl<T> LookupSet<T>
68where
69    T: BorshSerialize,
70{
71    fn serialize_element(element: &T) -> Vec<u8> {
72        match to_vec(element) {
73            Ok(x) => x,
74            Err(_) => env::panic_str(ERR_ELEMENT_SERIALIZATION),
75        }
76    }
77
78    /// Returns true if the set contains an element.
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use near_sdk::collections::LookupSet;
84    ///
85    /// let mut set: LookupSet<String> = LookupSet::new(b"s");
86    /// assert_eq!(set.contains(&"Element".into()), false);
87    ///
88    /// set.insert(&"Element".into());
89    /// assert_eq!(set.contains(&"Element".into()), true);
90    /// ```
91    pub fn contains(&self, element: &T) -> bool {
92        self.contains_raw(&Self::serialize_element(element))
93    }
94
95    /// Removes a value from the set. Returns whether the value was present in the set.
96    ///
97    /// # Examples
98    ///
99    /// ```
100    /// use near_sdk::collections::LookupSet;
101    ///
102    /// let mut set: LookupSet<String> = LookupSet::new(b"s");
103    /// assert_eq!(set.remove(&"Element".into()), false);
104    ///
105    /// set.insert(&"Element".into());
106    /// assert_eq!(set.remove(&"Element".into()), true);
107    /// ```
108    pub fn remove(&mut self, element: &T) -> bool {
109        self.remove_raw(&Self::serialize_element(element))
110    }
111
112    /// Adds a value to the set.
113    /// If the set did not have this value present, `true` is returned.
114    /// If the set did have this value present, `false` is returned.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use near_sdk::collections::LookupSet;
120    ///
121    /// let mut set: LookupSet<String> = LookupSet::new(b"s");
122    /// assert_eq!(set.insert(&"Element".into()), true);
123    /// assert_eq!(set.insert(&"Element".into()), false);
124    /// ```
125    pub fn insert(&mut self, element: &T) -> bool {
126        self.insert_raw(&Self::serialize_element(element))
127    }
128
129    pub fn extend<IT: IntoIterator<Item = T>>(&mut self, iter: IT) {
130        for el in iter {
131            self.insert(&el);
132        }
133    }
134}
135
136impl<T> std::fmt::Debug for LookupSet<T>
137where
138    T: std::fmt::Debug + BorshSerialize,
139{
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        f.debug_struct("LookupSet").field("element_prefix", &self.element_prefix).finish()
142    }
143}
144
145#[cfg(not(target_arch = "wasm32"))]
146#[cfg(test)]
147mod tests {
148    use crate::collections::LookupSet;
149    use rand::seq::SliceRandom;
150    use rand::{Rng, SeedableRng};
151    use std::collections::HashSet;
152
153    #[test]
154    pub fn test_insert_one() {
155        let mut map = LookupSet::new(b"m");
156        assert!(map.insert(&1));
157        assert!(!map.insert(&1));
158    }
159
160    #[test]
161    pub fn test_insert() {
162        let mut set = LookupSet::new(b"s");
163        let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(0);
164        for _ in 0..500 {
165            let key = rng.gen::<u64>();
166            set.insert(&key);
167        }
168    }
169
170    #[test]
171    pub fn test_insert_remove() {
172        let mut set = LookupSet::new(b"s");
173        let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(1);
174        let mut keys = vec![];
175        for _ in 0..100 {
176            let key = rng.gen::<u64>();
177            keys.push(key);
178            set.insert(&key);
179        }
180        keys.shuffle(&mut rng);
181        for key in keys {
182            assert!(set.remove(&key));
183        }
184    }
185
186    #[test]
187    pub fn test_remove_last_reinsert() {
188        let mut set = LookupSet::new(b"s");
189        let key1 = 1u64;
190        set.insert(&key1);
191        let key2 = 2u64;
192        set.insert(&key2);
193
194        let actual = set.remove(&key2);
195        assert!(actual);
196
197        let actual_reinsert = set.insert(&key2);
198        assert!(actual_reinsert);
199    }
200
201    #[test]
202    pub fn test_insert_override_remove() {
203        let mut set = LookupSet::new(b"s");
204        let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(2);
205        let mut keys = vec![];
206        for _ in 0..100 {
207            let key = rng.gen::<u64>();
208            keys.push(key);
209            set.insert(&key);
210        }
211        keys.shuffle(&mut rng);
212        for key in &keys {
213            assert!(!set.insert(key));
214        }
215        keys.shuffle(&mut rng);
216        for key in keys {
217            assert!(set.remove(&key));
218        }
219    }
220
221    #[test]
222    pub fn test_contains_non_existent() {
223        let mut set = LookupSet::new(b"s");
224        let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(3);
225        let mut set_tmp = HashSet::new();
226        for _ in 0..500 {
227            let key = rng.gen::<u64>() % 20_000;
228            set_tmp.insert(key);
229            set.insert(&key);
230        }
231        for _ in 0..500 {
232            let key = rng.gen::<u64>() % 20_000;
233            assert_eq!(set.contains(&key), set_tmp.contains(&key));
234        }
235    }
236
237    #[test]
238    pub fn test_extend() {
239        let mut set = LookupSet::new(b"s");
240        let mut rng = rand_xorshift::XorShiftRng::seed_from_u64(4);
241        let mut keys = HashSet::new();
242        for _ in 0..100 {
243            let key = rng.gen::<u64>();
244            keys.insert(key);
245            set.insert(&key);
246        }
247        for _ in 0..10 {
248            let mut tmp = vec![];
249            for _ in 0..=(rng.gen::<u64>() % 20 + 1) {
250                let key = rng.gen::<u64>();
251                tmp.push(key);
252            }
253            keys.extend(tmp.iter().cloned());
254            set.extend(tmp.iter().cloned());
255        }
256
257        for key in keys {
258            assert!(set.contains(&key));
259        }
260    }
261
262    #[test]
263    fn test_debug() {
264        let set: LookupSet<u64> = LookupSet::new(b"m");
265
266        assert_eq!(
267            format!("{:?}", set),
268            format!("LookupSet {{ element_prefix: {:?} }}", set.element_prefix)
269        );
270    }
271}