pub struct UserMapper<SA, A = CurrentStorage>where
SA: StorageMapperApi,
A: StorageAddress<SA>,{ /* private fields */ }Expand description
A specialized bidirectional mapper for managing smart contract users with auto-incrementing IDs.
§Storage Layout
The UserMapper maintains three storage patterns for efficient user management:
-
Address to ID mapping:
base_key + "_address_to_id" + encoded_address→ user ID (usize)
-
ID to address mapping:
base_key + "_id_to_address" + id→ user address
-
User counter:
base_key + "_count"→ total number of registered users
§User ID Assignment
- IDs start from 1 and increment sequentially (never 0)
- ID 0 represents “no user” or “user not found”
- Once assigned, user IDs are permanent and never reused
- No removal functionality - users cannot be deleted once registered
§Main Operations
- Auto-register:
get_or_create_user(address)- Gets ID or assigns new one. O(1). - Batch register:
get_or_create_users(addresses, callback)- Registers multiple users efficiently. - Lookup ID:
get_user_id(address)- Returns user ID (0 if not found). O(1). - Lookup Address:
get_user_address(id)- Returns address for ID. O(1). - Count:
get_user_count()- Returns total number of registered users. O(1). - Bulk Access:
get_all_addresses()- Returns all user addresses (expensive). O(n).
§Trade-offs
- Pros: Sequential IDs enable efficient iteration; permanent user registry; bidirectional lookup; auto-incrementing simplifies user management; built-in user counting.
- Cons: No user removal; IDs never reused;
get_all_addresses()can be expensive for large user bases; slightly higher storage overhead than simple address lists.
§Comparison with AddressToIdMapper
- UserMapper: Specialized for users; includes user count; batch operations; no removal
- AddressToIdMapper: Generic address mapping; supports removal; no built-in counting
§Use Cases
- User registration and management systems
- Participant tracking in contracts (staking, voting, etc.)
- Whitelist management with sequential user numbering
- Any scenario requiring both address-based lookup and iteration by user ID
- Loyalty programs or membership systems
§Example
// Register users (auto-assign IDs)
let id1 = mapper.get_or_create_user(&user1); // Returns 1
let id2 = mapper.get_or_create_user(&user2); // Returns 2
let id1_again = mapper.get_or_create_user(&user1); // Returns 1 (existing)
assert_eq!(id1, 1);
assert_eq!(id2, 2);
assert_eq!(id1_again, 1);
assert_eq!(mapper.get_user_count(), 2);
// Lookup by address
assert_eq!(mapper.get_user_id(&user1), 1);
assert_eq!(mapper.get_user_id(&user3), 0); // Not registered
// Lookup by ID
assert_eq!(mapper.get_user_address(1), Some(user1.clone()));
assert_eq!(mapper.get_user_address(999), None); // Invalid ID
// Safe address lookup (returns zero address if invalid)
let addr = mapper.get_user_address_or_zero(1);
assert_eq!(addr, user1);
let zero_addr = mapper.get_user_address_or_zero(999);
assert!(zero_addr.is_zero());
// Batch registration with callback
let addresses = vec![user2.clone(), user3.clone()];
mapper.get_or_create_users(addresses.into_iter(), |id, is_new| {
if is_new {
// Handle new user registration
} else {
// Handle existing user
}
});
assert_eq!(mapper.get_user_count(), 3);
// Get all users (expensive for large lists)
let all_users = mapper.get_all_addresses();
assert_eq!(all_users.len(), 3);
// Note: No removal functionality - users are permanentImplementations§
Source§impl<SA, A> UserMapper<SA, A>where
SA: StorageMapperApi,
A: StorageAddress<SA>,
impl<SA, A> UserMapper<SA, A>where
SA: StorageMapperApi,
A: StorageAddress<SA>,
Sourcepub fn get_user_id(&self, address: &ManagedAddress<SA>) -> usize
pub fn get_user_id(&self, address: &ManagedAddress<SA>) -> usize
Yields the user id for a given address. Will return 0 if the address is not known to the contract.
Sourcepub fn get_user_address(&self, id: usize) -> Option<ManagedAddress<SA>>
pub fn get_user_address(&self, id: usize) -> Option<ManagedAddress<SA>>
Yields the user address for a given id, if the id is valid.
Sourcepub fn get_user_address_unchecked(&self, id: usize) -> ManagedAddress<SA>
pub fn get_user_address_unchecked(&self, id: usize) -> ManagedAddress<SA>
Yields the user address for a given id. Will cause a deserialization error if the id is invalid.
Sourcepub fn get_user_address_or_zero(&self, id: usize) -> ManagedAddress<SA>
pub fn get_user_address_or_zero(&self, id: usize) -> ManagedAddress<SA>
Yields the user address for a given id, if the id is valid. Otherwise returns the zero address (0x000…)
Sourcepub fn get_user_count(&self) -> usize
pub fn get_user_count(&self) -> usize
Number of users.
Sourcepub fn get_all_addresses(&self) -> ManagedVec<SA, ManagedAddress<SA>>
pub fn get_all_addresses(&self) -> ManagedVec<SA, ManagedAddress<SA>>
Loads all addresses from storage and places them in a ManagedVec. Can easily consume a lot of gas.
Source§impl<SA> UserMapper<SA, CurrentStorage>where
SA: StorageMapperApi,
impl<SA> UserMapper<SA, CurrentStorage>where
SA: StorageMapperApi,
Sourcepub fn get_or_create_users<AddressIter, F>(
&self,
address_iter: AddressIter,
user_id_lambda: F,
)
pub fn get_or_create_users<AddressIter, F>( &self, address_iter: AddressIter, user_id_lambda: F, )
Tries to insert a number of addresses. Calls a lambda function for each, with the new user id and whether of nor the user was already present.
Sourcepub fn get_or_create_user(&self, address: &ManagedAddress<SA>) -> usize
pub fn get_or_create_user(&self, address: &ManagedAddress<SA>) -> usize
Yields the user id for a given address, or creates a new user id if there isn’t one. Will safely keep the user count in sync.
Trait Implementations§
Source§impl<SA> StorageMapper<SA> for UserMapper<SA>where
SA: StorageMapperApi,
impl<SA> StorageMapper<SA> for UserMapper<SA>where
SA: StorageMapperApi,
Source§fn new(base_key: StorageKey<SA>) -> Self
fn new(base_key: StorageKey<SA>) -> Self
#[storage_mapper] annotation generated code.Source§impl<SA> StorageMapperFromAddress<SA> for UserMapper<SA, ManagedAddress<SA>>where
SA: StorageMapperApi,
impl<SA> StorageMapperFromAddress<SA> for UserMapper<SA, ManagedAddress<SA>>where
SA: StorageMapperApi,
Source§fn new_from_address(
address: ManagedAddress<SA>,
base_key: StorageKey<SA>,
) -> Self
fn new_from_address( address: ManagedAddress<SA>, base_key: StorageKey<SA>, ) -> Self
#[storage_mapper_from_address]
annotation generated code.Source§impl<SA> TopEncodeMulti for UserMapper<SA, CurrentStorage>where
SA: StorageMapperApi,
Behaves like a MultiResultVec
when an endpoint result,
and lists all users addresses.
impl<SA> TopEncodeMulti for UserMapper<SA, CurrentStorage>where
SA: StorageMapperApi,
Behaves like a MultiResultVec
when an endpoint result, and lists all users addresses.Source§fn multi_encode_or_handle_err<O, H>(
&self,
output: &mut O,
h: H,
) -> Result<(), H::HandledErr>where
O: TopEncodeMultiOutput,
H: EncodeErrorHandler,
fn multi_encode_or_handle_err<O, H>(
&self,
output: &mut O,
h: H,
) -> Result<(), H::HandledErr>where
O: TopEncodeMultiOutput,
H: EncodeErrorHandler,
top_encode that can handle errors as soon as they occur.
For instance in can exit immediately and make sure that if it returns, it is a success.
By not deferring error handling, this can lead to somewhat smaller bytecode.Source§fn multi_encode<O>(&self, output: &mut O) -> Result<(), EncodeError>where
O: TopEncodeMultiOutput,
fn multi_encode<O>(&self, output: &mut O) -> Result<(), EncodeError>where
O: TopEncodeMultiOutput,
Source§impl<SA> TypeAbi for UserMapper<SA, CurrentStorage>where
SA: StorageMapperApi,
Behaves like a MultiResultVec when an endpoint result.
impl<SA> TypeAbi for UserMapper<SA, CurrentStorage>where
SA: StorageMapperApi,
Behaves like a MultiResultVec when an endpoint result.