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}