multiversx_sc/storage/mappers/user_mapper.rs
1use core::marker::PhantomData;
2
3use crate::{
4 abi::TypeAbiFrom,
5 codec::{
6 EncodeErrorHandler, TopEncodeMulti, TopEncodeMultiOutput, multi_encode_iter_or_handle_err,
7 },
8};
9
10use super::{
11 StorageMapper, StorageMapperFromAddress,
12 source::{CurrentStorage, StorageAddress},
13};
14use crate::{
15 abi::{TypeAbi, TypeName},
16 api::StorageMapperApi,
17 storage::{StorageKey, storage_set},
18 types::{ManagedAddress, ManagedType, ManagedVec, MultiValueEncoded},
19};
20
21const ADDRESS_TO_ID_SUFFIX: &str = "_address_to_id";
22const ID_TO_ADDRESS_SUFFIX: &str = "_id_to_address";
23const COUNT_SUFFIX: &str = "_count";
24
25/// A specialized bidirectional mapper for managing smart contract users with auto-incrementing IDs.
26///
27/// # Storage Layout
28///
29/// The `UserMapper` maintains three storage patterns for efficient user management:
30///
31/// 1. **Address to ID mapping**:
32/// - `base_key + "_address_to_id" + encoded_address` → user ID (usize)
33///
34/// 2. **ID to address mapping**:
35/// - `base_key + "_id_to_address" + id` → user address
36///
37/// 3. **User counter**:
38/// - `base_key + "_count"` → total number of registered users
39///
40/// # User ID Assignment
41///
42/// - IDs start from 1 and increment sequentially (never 0)
43/// - ID 0 represents "no user" or "user not found"
44/// - Once assigned, user IDs are **permanent** and never reused
45/// - No removal functionality - users cannot be deleted once registered
46///
47/// # Main Operations
48///
49/// - **Auto-register**: `get_or_create_user(address)` - Gets ID or assigns new one. O(1).
50/// - **Batch register**: `get_or_create_users(addresses, callback)` - Registers multiple users efficiently.
51/// - **Lookup ID**: `get_user_id(address)` - Returns user ID (0 if not found). O(1).
52/// - **Lookup Address**: `get_user_address(id)` - Returns address for ID. O(1).
53/// - **Count**: `get_user_count()` - Returns total number of registered users. O(1).
54/// - **Bulk Access**: `get_all_addresses()` - Returns all user addresses (expensive). O(n).
55///
56/// # Trade-offs
57///
58/// - **Pros**: Sequential IDs enable efficient iteration; permanent user registry; bidirectional lookup;
59/// auto-incrementing simplifies user management; built-in user counting.
60/// - **Cons**: No user removal; IDs never reused; `get_all_addresses()` can be expensive for large user bases;
61/// slightly higher storage overhead than simple address lists.
62///
63/// # Comparison with AddressToIdMapper
64///
65/// - **UserMapper**: Specialized for users; includes user count; batch operations; no removal
66/// - **AddressToIdMapper**: Generic address mapping; supports removal; no built-in counting
67///
68/// # Use Cases
69///
70/// - User registration and management systems
71/// - Participant tracking in contracts (staking, voting, etc.)
72/// - Whitelist management with sequential user numbering
73/// - Any scenario requiring both address-based lookup and iteration by user ID
74/// - Loyalty programs or membership systems
75///
76/// # Example
77///
78/// ```rust
79/// # use multiversx_sc::storage::mappers::{StorageMapper, UserMapper};
80/// # use multiversx_sc::types::ManagedAddress;
81/// # fn example<SA: multiversx_sc::api::StorageMapperApi>(
82/// # user1: ManagedAddress<SA>,
83/// # user2: ManagedAddress<SA>,
84/// # user3: ManagedAddress<SA>
85/// # ) {
86/// # let mapper = UserMapper::<SA>::new(
87/// # multiversx_sc::storage::StorageKey::new(&b"users"[..])
88/// # );
89/// // Register users (auto-assign IDs)
90/// let id1 = mapper.get_or_create_user(&user1); // Returns 1
91/// let id2 = mapper.get_or_create_user(&user2); // Returns 2
92/// let id1_again = mapper.get_or_create_user(&user1); // Returns 1 (existing)
93///
94/// assert_eq!(id1, 1);
95/// assert_eq!(id2, 2);
96/// assert_eq!(id1_again, 1);
97/// assert_eq!(mapper.get_user_count(), 2);
98///
99/// // Lookup by address
100/// assert_eq!(mapper.get_user_id(&user1), 1);
101/// assert_eq!(mapper.get_user_id(&user3), 0); // Not registered
102///
103/// // Lookup by ID
104/// assert_eq!(mapper.get_user_address(1), Some(user1.clone()));
105/// assert_eq!(mapper.get_user_address(999), None); // Invalid ID
106///
107/// // Safe address lookup (returns zero address if invalid)
108/// let addr = mapper.get_user_address_or_zero(1);
109/// assert_eq!(addr, user1);
110///
111/// let zero_addr = mapper.get_user_address_or_zero(999);
112/// assert!(zero_addr.is_zero());
113///
114/// // Batch registration with callback
115/// let addresses = vec![user2.clone(), user3.clone()];
116/// mapper.get_or_create_users(addresses.into_iter(), |id, is_new| {
117/// if is_new {
118/// // Handle new user registration
119/// } else {
120/// // Handle existing user
121/// }
122/// });
123///
124/// assert_eq!(mapper.get_user_count(), 3);
125///
126/// // Get all users (expensive for large lists)
127/// let all_users = mapper.get_all_addresses();
128/// assert_eq!(all_users.len(), 3);
129///
130/// // Note: No removal functionality - users are permanent
131/// # }
132/// ```
133pub struct UserMapper<SA, A = CurrentStorage>
134where
135 SA: StorageMapperApi,
136 A: StorageAddress<SA>,
137{
138 _phantom_api: PhantomData<SA>,
139 address: A,
140 base_key: StorageKey<SA>,
141}
142
143impl<SA> StorageMapper<SA> for UserMapper<SA>
144where
145 SA: StorageMapperApi,
146{
147 fn new(base_key: StorageKey<SA>) -> Self {
148 UserMapper {
149 _phantom_api: PhantomData,
150 address: CurrentStorage,
151 base_key,
152 }
153 }
154}
155
156impl<SA> StorageMapperFromAddress<SA> for UserMapper<SA, ManagedAddress<SA>>
157where
158 SA: StorageMapperApi,
159{
160 fn new_from_address(address: ManagedAddress<SA>, base_key: StorageKey<SA>) -> Self {
161 UserMapper {
162 _phantom_api: PhantomData,
163 address,
164 base_key,
165 }
166 }
167}
168
169impl<SA, A> UserMapper<SA, A>
170where
171 SA: StorageMapperApi,
172 A: StorageAddress<SA>,
173{
174 fn get_user_id_key(&self, address: &ManagedAddress<SA>) -> StorageKey<SA> {
175 let mut user_id_key = self.base_key.clone();
176 user_id_key.append_bytes(ADDRESS_TO_ID_SUFFIX.as_bytes());
177 user_id_key.append_item(address);
178 user_id_key
179 }
180
181 fn get_user_address_key(&self, id: usize) -> StorageKey<SA> {
182 let mut user_address_key = self.base_key.clone();
183 user_address_key.append_bytes(ID_TO_ADDRESS_SUFFIX.as_bytes());
184 user_address_key.append_item(&id);
185 user_address_key
186 }
187
188 fn get_user_count_key(&self) -> StorageKey<SA> {
189 let mut user_count_key = self.base_key.clone();
190 user_count_key.append_bytes(COUNT_SUFFIX.as_bytes());
191 user_count_key
192 }
193
194 /// Yields the user id for a given address.
195 /// Will return 0 if the address is not known to the contract.
196 pub fn get_user_id(&self, address: &ManagedAddress<SA>) -> usize {
197 self.address
198 .address_storage_get(self.get_user_id_key(address).as_ref())
199 }
200
201 /// Yields the user address for a given id, if the id is valid.
202 pub fn get_user_address(&self, id: usize) -> Option<ManagedAddress<SA>> {
203 let key = self.get_user_address_key(id);
204 // TODO: optimize, storage_load_managed_buffer_len is currently called twice
205
206 if self.address.address_storage_get_len(key.as_ref()) > 0 {
207 Some(self.address.address_storage_get(key.as_ref()))
208 } else {
209 None
210 }
211 }
212
213 /// Yields the user address for a given id.
214 /// Will cause a deserialization error if the id is invalid.
215 pub fn get_user_address_unchecked(&self, id: usize) -> ManagedAddress<SA> {
216 self.address
217 .address_storage_get(self.get_user_address_key(id).as_ref())
218 }
219
220 /// Yields the user address for a given id, if the id is valid.
221 /// Otherwise returns the zero address (0x000...)
222 pub fn get_user_address_or_zero(&self, id: usize) -> ManagedAddress<SA> {
223 let key = self.get_user_address_key(id);
224 // TODO: optimize, storage_load_managed_buffer_len is currently called twice
225 if self.address.address_storage_get_len(key.as_ref()) > 0 {
226 self.address.address_storage_get(key.as_ref())
227 } else {
228 ManagedAddress::zero()
229 }
230 }
231
232 /// Number of users.
233 pub fn get_user_count(&self) -> usize {
234 self.address
235 .address_storage_get(self.get_user_count_key().as_ref())
236 }
237
238 /// Loads all addresses from storage and places them in a ManagedVec.
239 /// Can easily consume a lot of gas.
240 pub fn get_all_addresses(&self) -> ManagedVec<SA, ManagedAddress<SA>> {
241 let user_count = self.get_user_count();
242 let mut result = ManagedVec::new();
243 for i in 1..=user_count {
244 result.push(self.get_user_address_or_zero(i));
245 }
246 result
247 }
248}
249
250impl<SA> UserMapper<SA, CurrentStorage>
251where
252 SA: StorageMapperApi,
253{
254 fn set_user_id(&self, address: &ManagedAddress<SA>, id: usize) {
255 storage_set(self.get_user_id_key(address).as_ref(), &id);
256 }
257
258 fn set_user_address(&self, id: usize, address: &ManagedAddress<SA>) {
259 storage_set(self.get_user_address_key(id).as_ref(), address);
260 }
261
262 fn set_user_count(&self, user_count: usize) {
263 storage_set(self.get_user_count_key().as_ref(), &user_count);
264 }
265
266 /// Tries to insert a number of addresses.
267 /// Calls a lambda function for each, with the new user id and whether of nor the user was already present.
268 pub fn get_or_create_users<AddressIter, F>(
269 &self,
270 address_iter: AddressIter,
271 mut user_id_lambda: F,
272 ) where
273 AddressIter: Iterator<Item = ManagedAddress<SA>>,
274 F: FnMut(usize, bool),
275 {
276 let mut user_count = self.get_user_count();
277 for address in address_iter {
278 let user_id = self.get_user_id(&address);
279 if user_id > 0 {
280 user_id_lambda(user_id, false);
281 } else {
282 user_count += 1;
283 let new_user_id = user_count;
284 self.set_user_id(&address, new_user_id);
285 self.set_user_address(new_user_id, &address);
286 user_id_lambda(new_user_id, true);
287 }
288 }
289 self.set_user_count(user_count);
290 }
291
292 /// Yields the user id for a given address, or creates a new user id if there isn't one.
293 /// Will safely keep the user count in sync.
294 pub fn get_or_create_user(&self, address: &ManagedAddress<SA>) -> usize {
295 let mut user_id = self.get_user_id(address);
296 if user_id == 0 {
297 let next_user_count = self.get_user_count() + 1;
298 self.set_user_count(next_user_count);
299 user_id = next_user_count;
300 self.set_user_id(address, user_id);
301 self.set_user_address(user_id, address);
302 }
303 user_id
304 }
305}
306
307/// Behaves like a MultiResultVec<Address> when an endpoint result,
308/// and lists all users addresses.
309impl<SA> TopEncodeMulti for UserMapper<SA, CurrentStorage>
310where
311 SA: StorageMapperApi,
312{
313 fn multi_encode_or_handle_err<O, H>(&self, output: &mut O, h: H) -> Result<(), H::HandledErr>
314 where
315 O: TopEncodeMultiOutput,
316 H: EncodeErrorHandler,
317 {
318 let all_addresses = self.get_all_addresses();
319 multi_encode_iter_or_handle_err(all_addresses.into_iter(), output, h)
320 }
321}
322
323impl<SA> TypeAbiFrom<UserMapper<SA, CurrentStorage>> for MultiValueEncoded<SA, ManagedAddress<SA>> where
324 SA: StorageMapperApi
325{
326}
327
328impl<SA> TypeAbiFrom<Self> for UserMapper<SA, CurrentStorage> where SA: StorageMapperApi {}
329
330/// Behaves like a MultiResultVec when an endpoint result.
331impl<SA> TypeAbi for UserMapper<SA, CurrentStorage>
332where
333 SA: StorageMapperApi,
334{
335 type Unmanaged = Self;
336
337 fn type_name() -> TypeName {
338 crate::abi::type_name_variadic::<ManagedAddress<SA>>()
339 }
340
341 fn type_name_rust() -> TypeName {
342 crate::abi::type_name_multi_value_encoded::<ManagedAddress<SA>>()
343 }
344
345 fn is_variadic() -> bool {
346 true
347 }
348}