near_sdk/collections/
lookup_set.rs1use 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#[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 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 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 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 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 pub fn contains(&self, element: &T) -> bool {
92 self.contains_raw(&Self::serialize_element(element))
93 }
94
95 pub fn remove(&mut self, element: &T) -> bool {
109 self.remove_raw(&Self::serialize_element(element))
110 }
111
112 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}