Skip to main content

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}