stellar_access/access_control/storage.rs
1use soroban_sdk::{contracttype, panic_with_error, Address, Env, Symbol, Vec};
2
3use crate::{
4 access_control::{
5 emit_admin_renounced, emit_admin_transfer_completed, emit_admin_transfer_initiated,
6 emit_role_admin_changed, emit_role_granted, emit_role_revoked, AccessControlError,
7 MAX_ROLES, ROLE_EXTEND_AMOUNT, ROLE_TTL_THRESHOLD,
8 },
9 role_transfer::{accept_transfer, transfer_role},
10};
11
12/// Storage key for enumeration of accounts per role.
13#[contracttype]
14pub struct RoleAccountKey {
15 pub role: Symbol,
16 pub index: u32,
17}
18
19/// Storage keys for the data associated with the access control
20#[contracttype]
21pub enum AccessControlStorageKey {
22 ExistingRoles, // Vec<Symbol> of all existing roles
23 RoleAccounts(RoleAccountKey), // (role, index) -> Address
24 HasRole(Address, Symbol), // (account, role) -> index
25 RoleAccountsCount(Symbol), // role -> count of accounts with the role
26 RoleAdmin(Symbol), // role -> the admin of the role
27 Admin,
28 PendingAdmin,
29}
30
31// ################## QUERY STATE ##################
32
33/// Returns `Some(index)` if the account has the specified role,
34/// where `index` is the position of the account for that role,
35/// and can be used to query [`get_role_member`].
36/// Returns `None` if the account does not have the specified role.
37///
38/// # Arguments
39///
40/// * `e` - Access to Soroban environment.
41/// * `account` - The account to check.
42/// * `role` - The role to check for.
43pub fn has_role(e: &Env, account: &Address, role: &Symbol) -> Option<u32> {
44 let key = AccessControlStorageKey::HasRole(account.clone(), role.clone());
45
46 e.storage().persistent().get(&key).inspect(|_| {
47 e.storage().persistent().extend_ttl(&key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT)
48 })
49}
50
51/// Returns the admin account.
52///
53/// # Arguments
54///
55/// * `e` - Access to Soroban environment.
56pub fn get_admin(e: &Env) -> Option<Address> {
57 e.storage().instance().get(&AccessControlStorageKey::Admin)
58}
59
60/// Returns the total number of accounts that have the specified role.
61/// If the role does not exist, it returns 0.
62///
63/// # Arguments
64///
65/// * `e` - Access to Soroban environment.
66/// * `role` - The role to get the count for.
67pub fn get_role_member_count(e: &Env, role: &Symbol) -> u32 {
68 let count_key = AccessControlStorageKey::RoleAccountsCount(role.clone());
69 if let Some(count) = e.storage().persistent().get(&count_key) {
70 e.storage().persistent().extend_ttl(&count_key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT);
71 count
72 } else {
73 0
74 }
75}
76
77/// Returns the account at the specified index for a given role.
78///
79/// # Arguments
80///
81/// * `e` - Access to Soroban environment.
82/// * `role` - The role to query.
83/// * `index` - The index of the account to retrieve.
84///
85/// # Errors
86///
87/// * [`AccessControlError::IndexOutOfBounds`] - If the indexing is out of
88/// bounds.
89pub fn get_role_member(e: &Env, role: &Symbol, index: u32) -> Address {
90 let key = AccessControlStorageKey::RoleAccounts(RoleAccountKey { role: role.clone(), index });
91
92 if let Some(account) = e.storage().persistent().get(&key) {
93 e.storage().persistent().extend_ttl(&key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT);
94 account
95 } else {
96 panic_with_error!(e, AccessControlError::IndexOutOfBounds)
97 }
98}
99
100/// Returns the admin role for a specific role.
101/// If no admin role is explicitly set, returns `None`.
102///
103/// # Arguments
104///
105/// * `e` - Access to Soroban environment.
106/// * `role` - The role to query the admin role for.
107pub fn get_role_admin(e: &Env, role: &Symbol) -> Option<Symbol> {
108 let key = AccessControlStorageKey::RoleAdmin(role.clone());
109
110 e.storage().persistent().get(&key).inspect(|_| {
111 e.storage().persistent().extend_ttl(&key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT)
112 })
113}
114
115/// Returns a vector containing all existing roles.
116/// Defaults to empty vector if no roles exist.
117///
118/// # Arguments
119///
120/// * `e` - Access to Soroban environment.
121///
122/// # Notes
123///
124/// This function returns all roles that currently have at least one member.
125pub fn get_existing_roles(e: &Env) -> Vec<Symbol> {
126 let key = AccessControlStorageKey::ExistingRoles;
127 if let Some(existing_roles) = e.storage().persistent().get(&key) {
128 e.storage().persistent().extend_ttl(&key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT);
129 existing_roles
130 } else {
131 Vec::new(e)
132 }
133}
134
135// ################## CHANGE STATE ##################
136
137/// Sets the overarching admin role.
138///
139///
140/// # Arguments
141///
142/// * `e` - Access to Soroban environment.
143/// * `admin` - The account to grant the admin privilege.
144///
145/// # Errors
146///
147/// * [`AccessControlError::AdminAlreadySet`] - If the admin is already set.
148///
149/// **IMPORTANT**: this function lacks authorization checks.
150/// It is expected to call this function only in the constructor!
151pub fn set_admin(e: &Env, admin: &Address) {
152 // Check if admin is already set
153 if e.storage().instance().has(&AccessControlStorageKey::Admin) {
154 panic_with_error!(e, AccessControlError::AdminAlreadySet);
155 }
156 e.storage().instance().set(&AccessControlStorageKey::Admin, &admin);
157}
158
159/// Grants a role to an account.
160/// Creates the role if it does not exist.
161/// Returns early if the account already has the role.
162///
163/// # Arguments
164///
165/// * `e` - Access to Soroban environment.
166/// * `account` - The account to grant the role to.
167/// * `role` - The role to grant.
168/// * `caller` - The address of the caller, must be the admin or has the
169/// `AdminRole` privileges for this role.
170///
171/// # Errors
172///
173/// * refer to [`ensure_if_admin_or_admin_role`] errors.
174/// * refer to [`grant_role_no_auth`] errors.
175///
176/// # Events
177///
178/// * topics - `["role_granted", role: Symbol, account: Address]`
179/// * data - `[caller: Address]`
180///
181/// # Notes
182///
183/// * Authorization for `caller` is required.
184pub fn grant_role(e: &Env, account: &Address, role: &Symbol, caller: &Address) {
185 caller.require_auth();
186 ensure_if_admin_or_admin_role(e, role, caller);
187 grant_role_no_auth(e, account, role, caller);
188}
189
190/// Low-level function to grant a role to an account without performing
191/// authorization checks.
192/// Creates the role if it does not exist.
193/// Returns early if the account already has the role.
194///
195/// # Arguments
196///
197/// * `e` - Access to Soroban environment.
198/// * `account` - The account to grant the role to.
199/// * `role` - The role to grant.
200/// * `caller` - The address of the caller.
201///
202/// # Errors
203///
204/// * refer to [`add_to_role_enumeration`] errors.
205///
206/// # Events
207///
208/// * topics - `["role_granted", role: Symbol, account: Address]`
209/// * data - `[caller: Address]`
210///
211/// # Security Warning
212///
213/// **IMPORTANT**: This function bypasses authorization checks and should only
214/// be used:
215/// - During contract initialization/construction
216/// - In admin functions that implement their own authorization logic
217///
218/// Using this function in public-facing methods creates significant security
219/// risks as it could allow unauthorized role assignments.
220pub fn grant_role_no_auth(e: &Env, account: &Address, role: &Symbol, caller: &Address) {
221 // Return early if account already has the role
222 if has_role(e, account, role).is_some() {
223 return;
224 }
225 add_to_role_enumeration(e, account, role);
226
227 emit_role_granted(e, role, account, caller);
228}
229
230/// Revokes a role from an account.
231///
232/// # Arguments
233///
234/// * `e` - Access to Soroban environment.
235/// * `account` - The account to revoke the role from.
236/// * `role` - The role to revoke.
237/// * `caller` - The address of the caller, must be the admin or has the
238/// `AdminRole` privileges for this role.
239///
240/// # Errors
241///
242/// * refer to [`ensure_if_admin_or_admin_role`] errors.
243/// * refer to [`revoke_role_no_auth`] errors.
244///
245/// # Events
246///
247/// * topics - `["role_revoked", role: Symbol, account: Address]`
248/// * data - `[caller: Address]`
249///
250/// # Notes
251///
252/// * Authorization for `caller` is required.
253pub fn revoke_role(e: &Env, account: &Address, role: &Symbol, caller: &Address) {
254 caller.require_auth();
255 ensure_if_admin_or_admin_role(e, role, caller);
256 revoke_role_no_auth(e, account, role, caller);
257}
258
259/// Low-level function to revoke a role from an account without performing
260/// authorization checks.
261///
262/// # Arguments
263///
264/// * `e` - Access to Soroban environment.
265/// * `account` - The account to revoke the role from.
266/// * `role` - The role to revoke.
267/// * `caller` - The address of the caller.
268///
269/// # Errors
270///
271/// * [`AccessControlError::RoleNotHeld`] - If the `account` doesn't have the
272/// role.
273/// * refer to [`remove_from_role_enumeration`] errors.
274///
275/// # Events
276///
277/// * topics - `["role_revoked", role: Symbol, account: Address]`
278/// * data - `[caller: Address]`
279///
280/// # Security Warning
281///
282/// **IMPORTANT**: This function bypasses authorization checks and should only
283/// be used:
284/// - During contract initialization/construction
285/// - In admin functions that implement their own authorization logic
286///
287/// Using this function in public-facing methods creates significant security
288/// risks as it could allow unauthorized role revocations.
289pub fn revoke_role_no_auth(e: &Env, account: &Address, role: &Symbol, caller: &Address) {
290 // Check if account has the role
291 if has_role(e, account, role).is_none() {
292 panic_with_error!(e, AccessControlError::RoleNotHeld);
293 }
294
295 remove_from_role_enumeration(e, account, role);
296
297 let key = AccessControlStorageKey::HasRole(account.clone(), role.clone());
298 e.storage().persistent().remove(&key);
299
300 emit_role_revoked(e, role, account, caller);
301}
302
303/// Allows an account to renounce a role assigned to itself.
304/// Users can only renounce roles for their own account.
305///
306/// # Arguments
307///
308/// * `e` - Access to Soroban environment.
309/// * `role` - The role to renounce.
310/// * `caller` - The address of the caller, must be the account that has the
311/// role.
312///
313/// # Errors
314///
315/// * [`AccessControlError::RoleNotHeld`] - If the `caller` doesn't have the
316/// role.
317/// * refer to [`remove_from_role_enumeration`] errors.
318///
319/// # Events
320///
321/// * topics - `["role_revoked", role: Symbol, account: Address]`
322/// * data - `[caller: Address]`
323///
324/// # Notes
325///
326/// * Authorization for `caller` is required.
327pub fn renounce_role(e: &Env, role: &Symbol, caller: &Address) {
328 caller.require_auth();
329
330 // Check if account has the role
331 if has_role(e, caller, role).is_none() {
332 panic_with_error!(e, AccessControlError::RoleNotHeld);
333 }
334
335 remove_from_role_enumeration(e, caller, role);
336
337 let key = AccessControlStorageKey::HasRole(caller.clone(), role.clone());
338 e.storage().persistent().remove(&key);
339
340 emit_role_revoked(e, role, caller, caller);
341}
342
343/// Initiates admin role transfer.
344/// Admin privileges for the current admin are not revoked until the
345/// recipient accepts the transfer.
346/// Overrides the previous pending transfer if there is one.
347///
348/// # Arguments
349///
350/// * `e` - Access to Soroban environment.
351/// * `new_admin` - The account to transfer the admin privileges to.
352/// * `live_until_ledger` - The ledger number at which the pending transfer
353/// expires. If `live_until_ledger` is `0`, the pending transfer is cancelled.
354/// `live_until_ledger` argument is implicitly bounded by the maximum allowed
355/// TTL extension for a temporary storage entry and specifying a higher value
356/// will cause the code to panic.
357///
358/// # Errors
359///
360/// * [`AccessControlError::AdminNotSet`] - If admin account is not set.
361/// * refer to [`transfer_role`] errors.
362///
363/// # Events
364///
365/// * topics - `["admin_transfer_initiated", current_admin: Address]`
366/// * data - `[new_admin: Address, live_until_ledger: u32]`
367///
368/// # Notes
369///
370/// * Authorization for the current admin is required.
371pub fn transfer_admin_role(e: &Env, new_admin: &Address, live_until_ledger: u32) {
372 let admin = enforce_admin_auth(e);
373
374 transfer_role(e, new_admin, &AccessControlStorageKey::PendingAdmin, live_until_ledger);
375
376 emit_admin_transfer_initiated(e, &admin, new_admin, live_until_ledger);
377}
378
379/// Completes the 2-step admin transfer.
380///
381/// # Arguments
382///
383/// * `e` - Access to Soroban environment.
384///
385/// # Errors
386///
387/// * [`AccessControlError::AdminNotSet`] - If admin account is not set.
388/// * refer to [`accept_transfer`] errors.
389///
390/// # Events
391///
392/// * topics - `["admin_transfer_completed", new_admin: Address]`
393/// * data - `[previous_admin: Address]`
394///
395/// # Notes
396///
397/// * Authorization for the pending admin is required.
398pub fn accept_admin_transfer(e: &Env) {
399 let Some(previous_admin) = get_admin(e) else {
400 panic_with_error!(e, AccessControlError::AdminNotSet);
401 };
402
403 let new_admin =
404 accept_transfer(e, &AccessControlStorageKey::Admin, &AccessControlStorageKey::PendingAdmin);
405
406 emit_admin_transfer_completed(e, &previous_admin, &new_admin);
407}
408
409/// Sets `admin_role` as the admin role for `role`.
410/// The admin role for a role controls who can grant and revoke that role.
411///
412/// # Arguments
413///
414/// * `e` - Access to Soroban environment.
415/// * `role` - The role to set the admin for.
416/// * `admin_role` - The role that will be the admin.
417///
418/// # Events
419///
420/// * topics - `["role_admin_changed", role: Symbol]`
421/// * data - `[previous_admin_role: Symbol, new_admin_role: Symbol]`
422///
423/// # Errors
424///
425/// * [`AccessControlError::AdminNotSet`] - If admin account is not set.
426///
427/// # Notes
428///
429/// * Authorization for the current admin is required.
430pub fn set_role_admin(e: &Env, role: &Symbol, admin_role: &Symbol) {
431 let Some(admin) = get_admin(e) else {
432 panic_with_error!(e, AccessControlError::AdminNotSet);
433 };
434 admin.require_auth();
435
436 set_role_admin_no_auth(e, role, admin_role);
437}
438
439/// Allows the current admin to renounce their role, making the contract
440/// permanently admin-less. This is useful for decentralization purposes or when
441/// the admin role is no longer needed. Once the admin is renounced, it cannot
442/// be reinstated.
443///
444/// # Arguments
445///
446/// * `e` - Access to Soroban environment.
447///
448/// # Errors
449///
450/// * [`AccessControlError::AdminNotSet`] - If no admin account is set.
451/// * [`AccessControlError::TransferInProgress`] - If there is a pending admin
452/// transfer.
453///
454/// # Events
455///
456/// * topics - `["admin_renounced", admin: Address]`
457/// * data - `[]`
458///
459/// # Notes
460///
461/// * Authorization for the current admin is required.
462pub fn renounce_admin(e: &Env) {
463 let admin = enforce_admin_auth(e);
464 let key = AccessControlStorageKey::PendingAdmin;
465
466 if e.storage().temporary().get::<_, Address>(&key).is_some() {
467 panic_with_error!(e, AccessControlError::TransferInProgress);
468 }
469
470 e.storage().instance().remove(&AccessControlStorageKey::Admin);
471
472 emit_admin_renounced(e, &admin);
473}
474
475/// Low-level function to set the admin role for a specified role without
476/// performing authorization checks.
477///
478/// # Arguments
479///
480/// * `e` - Access to Soroban environment.
481/// * `role` - The role to set the admin for.
482/// * `admin_role` - The new admin role to set.
483///
484/// # Events
485///
486/// * topics - `["role_admin_changed", role: Symbol]`
487/// * data - `[previous_admin_role: Symbol, new_admin_role: Symbol]`
488///
489/// # Security Warning
490///
491/// **IMPORTANT**: This function bypasses authorization checks and should only
492/// be used:
493/// - During contract initialization/construction
494/// - In admin functions that implement their own authorization logic
495///
496/// Using this function in public-facing methods creates significant security
497/// risks as it could allow unauthorized admin role assignments.
498///
499/// # Circular Admin Warning
500///
501/// **CAUTION**: This function allows the creation of circular admin
502/// relationships between roles. For example, it's possible to assign MINT_ADMIN
503/// as the admin of MINT_ROLE while also making MINT_ROLE the admin of
504/// MINT_ADMIN. Such circular relationships can lead to unintended consequences,
505/// including:
506///
507/// - Race conditions where each role can revoke the other
508/// - Potential security vulnerabilities in role management
509/// - Confusing governance structures that are difficult to reason about
510///
511/// When designing your role hierarchy, carefully consider the relationships
512/// between roles and avoid creating circular dependencies.
513pub fn set_role_admin_no_auth(e: &Env, role: &Symbol, admin_role: &Symbol) {
514 let key = AccessControlStorageKey::RoleAdmin(role.clone());
515
516 // Get previous admin role if exists
517 let previous_admin_role =
518 e.storage().persistent().get::<_, Symbol>(&key).unwrap_or_else(|| Symbol::new(e, ""));
519
520 e.storage().persistent().set(&key, admin_role);
521
522 emit_role_admin_changed(e, role, &previous_admin_role, admin_role);
523}
524
525/// Removes the admin role for a specified role without performing authorization
526/// checks.
527///
528/// # Arguments
529///
530/// * `e` - Access to Soroban environment.
531/// * `role` - The role to remove the admin for.
532///
533/// # Security Warning
534///
535/// **IMPORTANT**: This function bypasses authorization checks and should only
536/// be used:
537/// - In admin functions that implement their own authorization logic
538/// - When cleaning up unused roles
539pub fn remove_role_admin_no_auth(e: &Env, role: &Symbol) {
540 let key = AccessControlStorageKey::RoleAdmin(role.clone());
541
542 // Check if the key exists before attempting to remove
543 if e.storage().persistent().has(&key) {
544 e.storage().persistent().remove(&key);
545 } else {
546 panic_with_error!(e, AccessControlError::AdminRoleNotFound);
547 }
548}
549
550/// Removes the role accounts count for a specified role without performing
551/// authorization checks.
552///
553/// # Arguments
554///
555/// * `e` - Access to Soroban environment.
556/// * `role` - The role to remove the accounts count for.
557///
558/// # Security Warning
559///
560/// **IMPORTANT**: This function bypasses authorization checks and should only
561/// be used:
562/// - In admin functions that implement their own authorization logic
563/// - When cleaning up unused roles with zero members
564pub fn remove_role_accounts_count_no_auth(e: &Env, role: &Symbol) {
565 let count_key = AccessControlStorageKey::RoleAccountsCount(role.clone());
566
567 // Check if the key exists and has a zero count before removing
568 if let Some(count) = e.storage().persistent().get::<_, u32>(&count_key) {
569 if count == 0 {
570 e.storage().persistent().remove(&count_key);
571 } else {
572 panic_with_error!(e, AccessControlError::RoleCountIsNotZero);
573 }
574 } else {
575 panic_with_error!(e, AccessControlError::RoleNotFound);
576 }
577}
578
579// ################## LOW-LEVEL HELPERS ##################
580
581/// Ensures that the caller is either the contract admin or has the admin role
582/// for the specified role.
583///
584/// # Arguments
585///
586/// * `e` - Access to Soroban environment.
587/// * `role` - The role to check admin privileges for.
588/// * `caller` - The address of the caller to check permissions for.
589///
590/// # Errors
591///
592/// * [`AccessControlError::Unauthorized`] - If the caller is neither the
593/// contract admin nor has the admin role.
594pub fn ensure_if_admin_or_admin_role(e: &Env, role: &Symbol, caller: &Address) {
595 // Check if caller is contract admin (if one is set)
596 let is_admin = match get_admin(e) {
597 Some(admin) => caller == &admin,
598 None => false,
599 };
600
601 // Check if caller has admin role for the specified role
602 let is_admin_role = match get_role_admin(e, role) {
603 Some(admin_role) => has_role(e, caller, &admin_role).is_some(),
604 None => false,
605 };
606
607 if !is_admin && !is_admin_role {
608 panic_with_error!(e, AccessControlError::Unauthorized);
609 }
610}
611
612/// Ensures that the caller has the specified role.
613/// This function is used to check if an account has a specific role.
614/// The main purpose of this function is to act as a helper for the
615/// `#[has_role]` macro.
616///
617/// # Arguments
618///
619/// * `e` - Access to Soroban environment.
620/// * `role` - The role to check for.
621/// * `caller` - The address of the caller to check the role for.
622///
623/// # Errors
624///
625/// * [`AccessControlError::Unauthorized`] - If the caller does not have the
626/// specified role.
627pub fn ensure_role(e: &Env, role: &Symbol, caller: &Address) {
628 if has_role(e, caller, role).is_none() {
629 panic_with_error!(e, AccessControlError::Unauthorized);
630 }
631}
632
633/// Retrieves the admin from storage, enforces authorization,
634/// and returns the admin address.
635///
636/// # Arguments
637///
638/// * `e` - Access to Soroban environment.
639///
640/// # Returns
641///
642/// The admin address if authorization is successful.
643///
644/// # Errors
645///
646/// * [`AccessControlError::AdminNotSet`] - If admin account is not set.
647pub fn enforce_admin_auth(e: &Env) -> Address {
648 let Some(admin) = get_admin(e) else {
649 panic_with_error!(e, AccessControlError::AdminNotSet);
650 };
651
652 admin.require_auth();
653 admin
654}
655
656/// Adds an account to role enumeration.
657///
658/// # Arguments
659///
660/// * `e` - Access to Soroban environment.
661/// * `account` - The account to add to the role.
662/// * `role` - The role to add the account to.
663///
664/// # Errors
665///
666/// * [`AccessControlError::MaxRolesExceeded`] - If adding a new role would
667/// exceed the maximum allowed number of roles.
668pub fn add_to_role_enumeration(e: &Env, account: &Address, role: &Symbol) {
669 // Get the current count of accounts with this role
670 let count_key = AccessControlStorageKey::RoleAccountsCount(role.clone());
671 let count = e.storage().persistent().get(&count_key).unwrap_or(0);
672
673 // If this is the first account with this role, add the role to ExistingRoles
674 if count == 0 {
675 let mut existing_roles = get_existing_roles(e);
676
677 // Check if we've reached the maximum number of roles
678 if existing_roles.len() == MAX_ROLES {
679 panic_with_error!(e, AccessControlError::MaxRolesExceeded);
680 }
681
682 existing_roles.push_back(role.clone());
683 e.storage().persistent().set(&AccessControlStorageKey::ExistingRoles, &existing_roles);
684 }
685
686 // Add the account to the enumeration
687 let new_key =
688 AccessControlStorageKey::RoleAccounts(RoleAccountKey { role: role.clone(), index: count });
689 e.storage().persistent().set(&new_key, account);
690
691 // Store the index for the account in HasRole
692 let has_role_key = AccessControlStorageKey::HasRole(account.clone(), role.clone());
693 e.storage().persistent().set(&has_role_key, &count);
694
695 // Update the count
696 e.storage().persistent().set(&count_key, &(count + 1));
697}
698
699/// Removes an account from role enumeration.
700///
701/// # Arguments
702///
703/// * `e` - Access to Soroban environment.
704/// * `account` - The account to remove from the role.
705/// * `role` - The role to remove the account from.
706///
707/// # Errors
708///
709/// * [`AccessControlError::RoleIsEmpty`] - If the role has no members.
710/// * [`AccessControlError::RoleNotHeld`] - If the `account` doesn't have the
711/// role.
712pub fn remove_from_role_enumeration(e: &Env, account: &Address, role: &Symbol) {
713 // Get the current count of accounts with this role
714 let count_key = AccessControlStorageKey::RoleAccountsCount(role.clone());
715 let count = e.storage().persistent().get(&count_key).unwrap_or(0);
716 if count == 0 {
717 panic_with_error!(e, AccessControlError::RoleIsEmpty);
718 }
719
720 // Get the index of the account to remove
721 let to_be_removed_has_role_key =
722 AccessControlStorageKey::HasRole(account.clone(), role.clone());
723 let to_be_removed_index = e
724 .storage()
725 .persistent()
726 .get::<_, u32>(&to_be_removed_has_role_key)
727 .unwrap_or_else(|| panic_with_error!(e, AccessControlError::RoleNotHeld));
728
729 // Get the index of the last account for that role
730 let last_index = count - 1;
731 let last_key = AccessControlStorageKey::RoleAccounts(RoleAccountKey {
732 role: role.clone(),
733 index: last_index,
734 });
735
736 // Swap the to be removed account with the last account, then delete the last
737 // account
738 if to_be_removed_index != last_index {
739 let last_account = e
740 .storage()
741 .persistent()
742 .get::<_, Address>(&last_key)
743 .expect("we ensured count to be 1 at this point");
744
745 // Swap
746 let to_be_removed_key = AccessControlStorageKey::RoleAccounts(RoleAccountKey {
747 role: role.clone(),
748 index: to_be_removed_index,
749 });
750 e.storage().persistent().set(&to_be_removed_key, &last_account);
751
752 // Update HasRole for the swapped account
753 let last_account_has_role_key =
754 AccessControlStorageKey::HasRole(last_account.clone(), role.clone());
755 e.storage().persistent().set(&last_account_has_role_key, &to_be_removed_index);
756 }
757
758 // Remove the last account
759 e.storage().persistent().remove(&last_key);
760 e.storage().persistent().remove(&to_be_removed_has_role_key);
761
762 // Update the count
763 e.storage().persistent().set(&count_key, &last_index);
764
765 // If this was the last account with this role, remove the role from
766 // ExistingRoles
767 if last_index == 0 {
768 let mut existing_roles = get_existing_roles(e);
769
770 // Find and remove the role
771 if let Some(pos) = existing_roles.iter().position(|r| r == role.clone()) {
772 existing_roles.remove(pos as u32);
773 e.storage().persistent().set(&AccessControlStorageKey::ExistingRoles, &existing_roles);
774 }
775 }
776}