Skip to main content

multiversx_sc/storage/mappers/
whitelist_mapper.rs

1use core::marker::PhantomData;
2
3use multiversx_sc_codec::{TopDecode, TopEncode};
4
5use super::{
6    SingleValueMapper, StorageMapper, StorageMapperFromAddress,
7    source::{CurrentStorage, StorageAddress},
8};
9use crate::{
10    api::{ErrorApiImpl, StorageMapperApi},
11    codec::NestedEncode,
12    storage::StorageKey,
13    types::ManagedAddress,
14};
15
16type FlagMapper<SA, A> = SingleValueMapper<SA, bool, A>;
17
18const ITEM_NOT_WHITELISTED_ERR_MSG: &str = "Item not whitelisted";
19
20/// A non-iterable whitelist mapper optimized for fast membership testing.
21///
22/// # Storage Layout
23///
24/// The `WhitelistMapper` uses a simple direct key approach with boolean flags:
25///
26/// - `base_key + encoded_item` → `true` (if whitelisted) or empty (if not whitelisted)
27///
28/// Each whitelisted item requires exactly one storage key, making this extremely space-efficient.
29/// Uses `SingleValueMapper<bool>` internally for each item.
30///
31/// # Main Operations
32///
33/// - **Add**: `add(item)` - Adds item to whitelist. O(1) with one storage write.
34/// - **Remove**: `remove(item)` - Removes item from whitelist. O(1) with storage clear.
35/// - **Check**: `contains(item)` - Tests membership. O(1) with one storage read.
36/// - **Require**: `require_whitelisted(item)` - Checks membership or errors. O(1).
37///
38/// # Key Characteristics
39///
40/// - **Non-iterable**: Cannot iterate over whitelisted items (use `SetMapper` if iteration needed)
41/// - **Space-efficient**: One storage key per item, minimal overhead
42/// - **Fast lookups**: Direct key-based membership testing
43/// - **Boolean logic**: Uses presence/absence, since true = 1, false = empty
44///
45/// # Trade-offs
46///
47/// - **Pros**: Extremely efficient for membership testing; minimal storage overhead; simple and fast.
48/// - **Cons**: No iteration capability; no built-in counting; cannot list all whitelisted items;
49///   no bulk operations.
50///
51/// # Comparison with SetMapper/UnorderedSetMapper
52///
53/// - **WhitelistMapper**: Non-iterable; most space-efficient; fastest lookups
54/// - **SetMapper**: Iterable; maintains insertion order; higher overhead
55/// - **UnorderedSetMapper**: Iterable; no order guarantees; moderate overhead
56///
57/// # Use Cases
58///
59/// - Simple whitelists where iteration is not needed
60/// - Permission systems (allowed addresses, tokens, etc.)
61/// - Feature flags or capability checking
62/// - Any membership testing where space efficiency is critical
63/// - Large whitelists where iteration would be prohibitively expensive
64///
65/// # Example
66///
67/// ```rust
68/// # use multiversx_sc::storage::mappers::{StorageMapper, WhitelistMapper};
69/// # use multiversx_sc::types::ManagedAddress;
70/// # fn example<SA: multiversx_sc::api::StorageMapperApi>(
71/// #     admin: ManagedAddress<SA>,
72/// #     user1: ManagedAddress<SA>,
73/// #     user2: ManagedAddress<SA>
74/// # ) {
75/// # let whitelist = WhitelistMapper::<SA, ManagedAddress<SA>>::new(
76/// #     multiversx_sc::storage::StorageKey::new(&b"allowed_users"[..])
77/// # );
78/// // Add addresses to whitelist
79/// whitelist.add(&admin);
80/// whitelist.add(&user1);
81///
82/// // Check membership
83/// assert!(whitelist.contains(&admin));
84/// assert!(whitelist.contains(&user1));
85/// assert!(!whitelist.contains(&user2));
86///
87/// // Require membership (errors if not whitelisted)
88/// whitelist.require_whitelisted(&admin);  // OK
89/// // whitelist.require_whitelisted(&user2);  // Would error: "Item not whitelisted"
90///
91/// // Remove from whitelist
92/// whitelist.remove(&user1);
93/// assert!(!whitelist.contains(&user1));
94///
95/// // Use in access control
96/// fn admin_only_function<SA: multiversx_sc::api::StorageMapperApi>(
97///     whitelist: &WhitelistMapper<SA, ManagedAddress<SA>>,
98///     caller: &ManagedAddress<SA>
99/// ) {
100///     whitelist.require_whitelisted(caller);
101///     // Function logic here...
102/// }
103/// # }
104/// ```
105///
106/// # Token Whitelist Example
107///
108/// ```rust
109/// # use multiversx_sc::storage::mappers::{StorageMapper, WhitelistMapper};
110/// # use multiversx_sc::types::TokenIdentifier;
111/// # fn token_example<SA: multiversx_sc::api::StorageMapperApi>(
112/// #     token1: TokenIdentifier<SA>,
113/// #     token2: TokenIdentifier<SA>
114/// # ) {
115/// # let allowed_tokens = WhitelistMapper::<SA, TokenIdentifier<SA>>::new(
116/// #     multiversx_sc::storage::StorageKey::new(&b"allowed_tokens"[..])
117/// # );
118/// // Whitelist specific tokens
119/// allowed_tokens.add(&token1);
120///
121/// // Validate token in payment
122/// if allowed_tokens.contains(&token1) {
123///     // Process payment
124/// } else {
125///     // Reject payment
126/// }
127/// # }
128/// ```
129pub struct WhitelistMapper<SA, T, A = CurrentStorage>
130where
131    SA: StorageMapperApi,
132    A: StorageAddress<SA>,
133    T: NestedEncode + 'static,
134{
135    address: A,
136    base_key: StorageKey<SA>,
137    _phantom: PhantomData<T>,
138}
139
140impl<SA, T> StorageMapper<SA> for WhitelistMapper<SA, T, CurrentStorage>
141where
142    SA: StorageMapperApi,
143    T: NestedEncode + 'static,
144{
145    fn new(base_key: StorageKey<SA>) -> Self {
146        Self {
147            address: CurrentStorage,
148            base_key,
149            _phantom: PhantomData,
150        }
151    }
152}
153
154impl<SA, T> StorageMapperFromAddress<SA> for WhitelistMapper<SA, T, ManagedAddress<SA>>
155where
156    SA: StorageMapperApi,
157    T: NestedEncode + 'static,
158{
159    fn new_from_address(address: ManagedAddress<SA>, base_key: StorageKey<SA>) -> Self {
160        Self {
161            address,
162            base_key,
163            _phantom: PhantomData,
164        }
165    }
166}
167
168impl<SA, T> WhitelistMapper<SA, T, ManagedAddress<SA>>
169where
170    SA: StorageMapperApi,
171    T: TopDecode + TopEncode + NestedEncode + 'static,
172{
173    pub fn contains(&self, item: &T) -> bool {
174        let mapper = self.build_mapper_for_item(item);
175        !mapper.is_empty()
176    }
177
178    pub fn require_whitelisted(&self, item: &T) {
179        if !self.contains(item) {
180            SA::error_api_impl().signal_error(ITEM_NOT_WHITELISTED_ERR_MSG.as_bytes());
181        }
182    }
183
184    fn build_mapper_for_item(&self, item: &T) -> FlagMapper<SA, ManagedAddress<SA>> {
185        let mut key = self.base_key.clone();
186        key.append_item(item);
187
188        FlagMapper::<SA, ManagedAddress<SA>>::new_from_address(self.address.clone(), key)
189    }
190}
191
192impl<SA, T> WhitelistMapper<SA, T, CurrentStorage>
193where
194    SA: StorageMapperApi,
195    T: TopDecode + TopEncode + NestedEncode + 'static,
196{
197    pub fn contains(&self, item: &T) -> bool {
198        let mapper = self.build_mapper_for_item(item);
199        !mapper.is_empty()
200    }
201
202    pub fn require_whitelisted(&self, item: &T) {
203        if !self.contains(item) {
204            SA::error_api_impl().signal_error(ITEM_NOT_WHITELISTED_ERR_MSG.as_bytes());
205        }
206    }
207
208    pub fn add(&self, item: &T) {
209        let mapper = self.build_mapper_for_item(item);
210        mapper.set(true);
211    }
212
213    pub fn remove(&self, item: &T) {
214        let mapper = self.build_mapper_for_item(item);
215        mapper.clear();
216    }
217
218    fn build_mapper_for_item(&self, item: &T) -> FlagMapper<SA, CurrentStorage> {
219        let mut key = self.base_key.clone();
220        key.append_item(item);
221
222        FlagMapper::<SA, CurrentStorage>::new(key)
223    }
224}