use soroban_sdk::{contracttype, panic_with_error, Address, Env, Symbol, Vec};
use crate::{
access_control::{
emit_admin_renounced, emit_admin_transfer_completed, emit_admin_transfer_initiated,
emit_role_admin_changed, emit_role_granted, emit_role_revoked, AccessControlError,
MAX_ROLES, ROLE_EXTEND_AMOUNT, ROLE_TTL_THRESHOLD,
},
role_transfer::{accept_transfer, has_active_pending_transfer, transfer_role},
};
#[contracttype]
pub struct RoleAccountKey {
pub role: Symbol,
pub index: u32,
}
#[contracttype]
pub enum AccessControlStorageKey {
ExistingRoles, RoleAccounts(RoleAccountKey), HasRole(Address, Symbol), RoleAccountsCount(Symbol), RoleAdmin(Symbol), Admin,
PendingAdmin,
}
pub fn has_role(e: &Env, account: &Address, role: &Symbol) -> Option<u32> {
let key = AccessControlStorageKey::HasRole(account.clone(), role.clone());
e.storage().persistent().get(&key).inspect(|_| {
e.storage().persistent().extend_ttl(&key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT)
})
}
pub fn get_admin(e: &Env) -> Option<Address> {
e.storage().instance().get(&AccessControlStorageKey::Admin)
}
pub fn get_role_member_count(e: &Env, role: &Symbol) -> u32 {
let count_key = AccessControlStorageKey::RoleAccountsCount(role.clone());
if let Some(count) = e.storage().persistent().get(&count_key) {
e.storage().persistent().extend_ttl(&count_key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT);
count
} else {
0
}
}
pub fn get_role_member(e: &Env, role: &Symbol, index: u32) -> Address {
let key = AccessControlStorageKey::RoleAccounts(RoleAccountKey { role: role.clone(), index });
if let Some(account) = e.storage().persistent().get(&key) {
e.storage().persistent().extend_ttl(&key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT);
account
} else {
panic_with_error!(e, AccessControlError::IndexOutOfBounds)
}
}
pub fn get_role_admin(e: &Env, role: &Symbol) -> Option<Symbol> {
let key = AccessControlStorageKey::RoleAdmin(role.clone());
e.storage().persistent().get(&key).inspect(|_| {
e.storage().persistent().extend_ttl(&key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT)
})
}
pub fn get_existing_roles(e: &Env) -> Vec<Symbol> {
let key = AccessControlStorageKey::ExistingRoles;
if let Some(existing_roles) = e.storage().persistent().get(&key) {
e.storage().persistent().extend_ttl(&key, ROLE_TTL_THRESHOLD, ROLE_EXTEND_AMOUNT);
existing_roles
} else {
Vec::new(e)
}
}
pub fn set_admin(e: &Env, admin: &Address) {
if e.storage().instance().has(&AccessControlStorageKey::Admin) {
panic_with_error!(e, AccessControlError::AdminAlreadySet);
}
e.storage().instance().set(&AccessControlStorageKey::Admin, &admin);
}
pub fn grant_role(e: &Env, account: &Address, role: &Symbol, caller: &Address) {
caller.require_auth();
ensure_if_admin_or_admin_role(e, role, caller);
grant_role_no_auth(e, account, role, caller);
}
pub fn grant_role_no_auth(e: &Env, account: &Address, role: &Symbol, caller: &Address) {
if has_role(e, account, role).is_some() {
return;
}
add_to_role_enumeration(e, account, role);
emit_role_granted(e, role, account, caller);
}
pub fn revoke_role(e: &Env, account: &Address, role: &Symbol, caller: &Address) {
caller.require_auth();
ensure_if_admin_or_admin_role(e, role, caller);
revoke_role_no_auth(e, account, role, caller);
}
pub fn revoke_role_no_auth(e: &Env, account: &Address, role: &Symbol, caller: &Address) {
if has_role(e, account, role).is_none() {
panic_with_error!(e, AccessControlError::RoleNotHeld);
}
remove_from_role_enumeration(e, account, role);
let key = AccessControlStorageKey::HasRole(account.clone(), role.clone());
e.storage().persistent().remove(&key);
emit_role_revoked(e, role, account, caller);
}
pub fn renounce_role(e: &Env, role: &Symbol, caller: &Address) {
caller.require_auth();
if has_role(e, caller, role).is_none() {
panic_with_error!(e, AccessControlError::RoleNotHeld);
}
remove_from_role_enumeration(e, caller, role);
let key = AccessControlStorageKey::HasRole(caller.clone(), role.clone());
e.storage().persistent().remove(&key);
emit_role_revoked(e, role, caller, caller);
}
pub fn transfer_admin_role(e: &Env, new_admin: &Address, live_until_ledger: u32) {
let admin = enforce_admin_auth(e);
transfer_role(e, new_admin, &AccessControlStorageKey::PendingAdmin, live_until_ledger);
emit_admin_transfer_initiated(e, &admin, new_admin, live_until_ledger);
}
pub fn accept_admin_transfer(e: &Env) {
let Some(previous_admin) = get_admin(e) else {
panic_with_error!(e, AccessControlError::AdminNotSet);
};
let new_admin =
accept_transfer(e, &AccessControlStorageKey::Admin, &AccessControlStorageKey::PendingAdmin);
emit_admin_transfer_completed(e, &previous_admin, &new_admin);
}
pub fn set_role_admin(e: &Env, role: &Symbol, admin_role: &Symbol) {
let Some(admin) = get_admin(e) else {
panic_with_error!(e, AccessControlError::AdminNotSet);
};
admin.require_auth();
set_role_admin_no_auth(e, role, admin_role);
}
pub fn renounce_admin(e: &Env) {
let admin = enforce_admin_auth(e);
let key = AccessControlStorageKey::PendingAdmin;
if has_active_pending_transfer(e, &key) {
panic_with_error!(e, AccessControlError::TransferInProgress);
}
e.storage().instance().remove(&AccessControlStorageKey::Admin);
emit_admin_renounced(e, &admin);
}
pub fn set_role_admin_no_auth(e: &Env, role: &Symbol, admin_role: &Symbol) {
let key = AccessControlStorageKey::RoleAdmin(role.clone());
let previous_admin_role =
e.storage().persistent().get::<_, Symbol>(&key).unwrap_or_else(|| Symbol::new(e, ""));
e.storage().persistent().set(&key, admin_role);
emit_role_admin_changed(e, role, &previous_admin_role, admin_role);
}
pub fn remove_role_admin_no_auth(e: &Env, role: &Symbol) {
let key = AccessControlStorageKey::RoleAdmin(role.clone());
if e.storage().persistent().has(&key) {
e.storage().persistent().remove(&key);
} else {
panic_with_error!(e, AccessControlError::AdminRoleNotFound);
}
}
pub fn remove_role_accounts_count_no_auth(e: &Env, role: &Symbol) {
let count_key = AccessControlStorageKey::RoleAccountsCount(role.clone());
if let Some(count) = e.storage().persistent().get::<_, u32>(&count_key) {
if count == 0 {
e.storage().persistent().remove(&count_key);
} else {
panic_with_error!(e, AccessControlError::RoleCountIsNotZero);
}
} else {
panic_with_error!(e, AccessControlError::RoleNotFound);
}
}
pub fn ensure_if_admin_or_admin_role(e: &Env, role: &Symbol, caller: &Address) {
let is_admin = match get_admin(e) {
Some(admin) => caller == &admin,
None => false,
};
let is_admin_role = match get_role_admin(e, role) {
Some(admin_role) => has_role(e, caller, &admin_role).is_some(),
None => false,
};
if !is_admin && !is_admin_role {
panic_with_error!(e, AccessControlError::Unauthorized);
}
}
pub fn ensure_role(e: &Env, role: &Symbol, caller: &Address) {
if has_role(e, caller, role).is_none() {
panic_with_error!(e, AccessControlError::Unauthorized);
}
}
pub fn enforce_admin_auth(e: &Env) -> Address {
let Some(admin) = get_admin(e) else {
panic_with_error!(e, AccessControlError::AdminNotSet);
};
admin.require_auth();
admin
}
pub fn add_to_role_enumeration(e: &Env, account: &Address, role: &Symbol) {
let count_key = AccessControlStorageKey::RoleAccountsCount(role.clone());
let count = e.storage().persistent().get(&count_key).unwrap_or(0);
if count == 0 {
let mut existing_roles = get_existing_roles(e);
if existing_roles.len() == MAX_ROLES {
panic_with_error!(e, AccessControlError::MaxRolesExceeded);
}
existing_roles.push_back(role.clone());
e.storage().persistent().set(&AccessControlStorageKey::ExistingRoles, &existing_roles);
}
let new_key =
AccessControlStorageKey::RoleAccounts(RoleAccountKey { role: role.clone(), index: count });
e.storage().persistent().set(&new_key, account);
let has_role_key = AccessControlStorageKey::HasRole(account.clone(), role.clone());
e.storage().persistent().set(&has_role_key, &count);
e.storage().persistent().set(&count_key, &(count + 1));
}
pub fn remove_from_role_enumeration(e: &Env, account: &Address, role: &Symbol) {
let count_key = AccessControlStorageKey::RoleAccountsCount(role.clone());
let count = e.storage().persistent().get(&count_key).unwrap_or(0);
if count == 0 {
panic_with_error!(e, AccessControlError::RoleIsEmpty);
}
let to_be_removed_has_role_key =
AccessControlStorageKey::HasRole(account.clone(), role.clone());
let to_be_removed_index = e
.storage()
.persistent()
.get::<_, u32>(&to_be_removed_has_role_key)
.unwrap_or_else(|| panic_with_error!(e, AccessControlError::RoleNotHeld));
let last_index = count - 1;
let last_key = AccessControlStorageKey::RoleAccounts(RoleAccountKey {
role: role.clone(),
index: last_index,
});
if to_be_removed_index != last_index {
let last_account = e
.storage()
.persistent()
.get::<_, Address>(&last_key)
.expect("we ensured count to be 1 at this point");
let to_be_removed_key = AccessControlStorageKey::RoleAccounts(RoleAccountKey {
role: role.clone(),
index: to_be_removed_index,
});
e.storage().persistent().set(&to_be_removed_key, &last_account);
let last_account_has_role_key =
AccessControlStorageKey::HasRole(last_account.clone(), role.clone());
e.storage().persistent().set(&last_account_has_role_key, &to_be_removed_index);
}
e.storage().persistent().remove(&last_key);
e.storage().persistent().remove(&to_be_removed_has_role_key);
e.storage().persistent().set(&count_key, &last_index);
if last_index == 0 {
let mut existing_roles = get_existing_roles(e);
if let Some(pos) = existing_roles.iter().position(|r| r == role.clone()) {
existing_roles.remove(pos as u32);
e.storage().persistent().set(&AccessControlStorageKey::ExistingRoles, &existing_roles);
}
}
}