use anchor_lang::prelude::*;
use gmsol_utils::bitmaps::Bitmap;
use crate::CoreError;
use super::InitSpace;
pub use gmsol_utils::role::{RoleKey, MAX_ROLE_NAME_LEN};
pub const MAX_ROLES: usize = 32;
pub const MAX_MEMBERS: usize = 64;
type RoleBitmap = Bitmap<MAX_ROLES>;
type RoleBitmapValue = u32;
#[zero_copy]
#[derive(Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct RoleMetadata {
name: [u8; MAX_ROLE_NAME_LEN],
enabled: u8,
index: u8,
}
impl InitSpace for RoleMetadata {
const INIT_SPACE: usize = 32 + 2;
}
#[cfg(test)]
const_assert_eq!(
std::mem::size_of::<RoleMetadata>(),
RoleMetadata::INIT_SPACE
);
impl RoleMetadata {
pub const ROLE_ENABLED: u8 = u8::MAX;
fn name_to_bytes(name: &str) -> Result<[u8; MAX_ROLE_NAME_LEN]> {
crate::utils::fixed_str::fixed_str_to_bytes(name)
}
fn bytes_to_name(bytes: &[u8; 32]) -> Result<&str> {
crate::utils::fixed_str::bytes_to_fixed_str(bytes)
}
pub fn new(name: &str, index: u8) -> Result<Self> {
Ok(Self {
name: Self::name_to_bytes(name)?,
enabled: Self::ROLE_ENABLED,
index,
})
}
pub fn name(&self) -> Result<&str> {
Self::bytes_to_name(&self.name)
}
pub fn enable(&mut self) -> Result<()> {
require!(!self.is_enabled(), CoreError::PreconditionsAreNotMet);
self.set_enable();
Ok(())
}
pub fn disable(&mut self) -> Result<()> {
require!(self.is_enabled(), CoreError::PreconditionsAreNotMet);
self.set_disable();
Ok(())
}
fn set_enable(&mut self) {
self.enabled = Self::ROLE_ENABLED;
}
fn set_disable(&mut self) {
self.enabled = 0;
}
pub fn is_enabled(&self) -> bool {
self.enabled == Self::ROLE_ENABLED
}
}
gmsol_utils::fixed_map!(RoleMap, RoleMetadata, MAX_ROLES, 0);
gmsol_utils::fixed_map!(
Members,
Pubkey,
crate::utils::pubkey::to_bytes,
u32,
MAX_MEMBERS,
0
);
#[zero_copy]
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct RoleStore {
roles: RoleMap,
members: Members,
}
impl InitSpace for RoleStore {
const INIT_SPACE: usize = std::mem::size_of::<RoleStore>();
}
impl RoleStore {
pub fn enable_role(&mut self, role: &str) -> Result<()> {
match self.roles.get_mut(role) {
Some(metadata) => {
require_eq!(metadata.name()?, role, CoreError::InvalidArgument);
metadata.enable()?;
}
None => {
let index = self
.roles
.len()
.try_into()
.map_err(|_| error!(CoreError::ExceedMaxLengthLimit))?;
self.roles
.insert_with_options(role, RoleMetadata::new(role, index)?, true)?;
}
}
Ok(())
}
pub fn disable_role(&mut self, role: &str) -> Result<()> {
if let Some(metadata) = self.roles.get_mut(role) {
require_eq!(metadata.name()?, role, CoreError::InvalidArgument);
metadata.disable()?;
}
Ok(())
}
pub fn role_index(&self, role: &str) -> Result<Option<u8>> {
if let Some(metadata) = self.roles.get(role) {
require_eq!(metadata.name()?, role, CoreError::InvalidArgument);
Ok(Some(metadata.index))
} else {
Ok(None)
}
}
pub fn enabled_role_index(&self, role: &str) -> Result<Option<u8>> {
if let Some(metadata) = self.roles.get(role) {
require_eq!(metadata.name()?, role, CoreError::InvalidArgument);
require!(metadata.is_enabled(), CoreError::PreconditionsAreNotMet);
Ok(Some(metadata.index))
} else {
Ok(None)
}
}
pub fn has_role(&self, authority: &Pubkey, role: &str) -> Result<bool> {
let Some(value) = self.members.get(authority) else {
return err!(CoreError::PermissionDenied);
};
let Some(index) = self.enabled_role_index(role)? else {
return err!(CoreError::NotFound);
};
let bitmap = RoleBitmap::from_value(*value);
Ok(bitmap.get(index as usize))
}
pub fn grant(&mut self, authority: &Pubkey, role: &str) -> Result<()> {
let Some(index) = self.enabled_role_index(role)? else {
return err!(CoreError::NotFound);
};
let index = index as usize;
match self.members.get_mut(authority) {
Some(value) => {
let mut bitmap = RoleBitmap::from_value(*value);
require!(!bitmap.get(index), CoreError::PreconditionsAreNotMet);
bitmap.set(index, true);
*value = bitmap.into_value();
}
None => {
let mut bitmap = RoleBitmap::new();
bitmap.set(index, true);
self.members
.insert_with_options(authority, bitmap.into_value(), true)?;
}
}
Ok(())
}
pub fn revoke(&mut self, authority: &Pubkey, role: &str) -> Result<()> {
let Some(index) = self.role_index(role)? else {
return err!(CoreError::NotFound);
};
let Some(value) = self.members.get_mut(authority) else {
return err!(CoreError::PermissionDenied);
};
let mut bitmap = RoleBitmap::from_value(*value);
let index = index as usize;
require!(bitmap.get(index), CoreError::PreconditionsAreNotMet);
bitmap.set(index, false);
*value = bitmap.into_value();
if bitmap.is_empty() {
self.members.remove(authority);
}
Ok(())
}
pub fn num_roles(&self) -> usize {
self.roles.len()
}
pub fn num_members(&self) -> usize {
self.members.len()
}
pub fn role_value(&self, user: &Pubkey) -> Option<RoleBitmapValue> {
self.members.get(user).copied()
}
pub fn members(&self) -> impl Iterator<Item = Pubkey> + '_ {
self.members
.entries()
.map(|(key, _)| Pubkey::new_from_array(*key))
}
pub fn roles(&self) -> impl Iterator<Item = Result<&str>> + '_ {
self.roles.entries().map(|(_, value)| value.name())
}
}
#[cfg(test)]
mod tests {
use bytemuck::Zeroable;
use super::*;
#[test]
fn grant_and_revoke_roles() {
let mut store = RoleStore::zeroed();
let authority = Pubkey::new_unique();
assert!(store.grant(&authority, RoleKey::GT_CONTROLLER).is_err());
assert!(store.has_role(&authority, RoleKey::GT_CONTROLLER).is_err());
store.enable_role(RoleKey::GT_CONTROLLER).unwrap();
store.enable_role(RoleKey::MARKET_KEEPER).unwrap();
store.grant(&authority, RoleKey::GT_CONTROLLER).unwrap();
assert_eq!(store.has_role(&authority, RoleKey::GT_CONTROLLER), Ok(true));
store.grant(&authority, RoleKey::MARKET_KEEPER).unwrap();
assert_eq!(store.has_role(&authority, RoleKey::MARKET_KEEPER), Ok(true));
assert_eq!(store.has_role(&authority, RoleKey::GT_CONTROLLER), Ok(true));
store.revoke(&authority, RoleKey::GT_CONTROLLER).unwrap();
assert_eq!(store.has_role(&authority, RoleKey::MARKET_KEEPER), Ok(true));
assert_eq!(
store.has_role(&authority, RoleKey::GT_CONTROLLER),
Ok(false)
);
store.revoke(&authority, RoleKey::MARKET_KEEPER).unwrap();
assert!(store.has_role(&authority, RoleKey::MARKET_KEEPER).is_err());
assert!(store.has_role(&authority, RoleKey::GT_CONTROLLER).is_err());
store.disable_role(RoleKey::MARKET_KEEPER).unwrap();
assert!(store.grant(&authority, RoleKey::MARKET_KEEPER).is_err());
assert!(store.has_role(&authority, RoleKey::MARKET_KEEPER).is_err());
store.enable_role(RoleKey::MARKET_KEEPER).unwrap();
store.grant(&authority, RoleKey::MARKET_KEEPER).unwrap();
assert_eq!(store.has_role(&authority, RoleKey::MARKET_KEEPER), Ok(true));
}
#[test]
fn enable_and_disable_role() {
let mut store = RoleStore::zeroed();
let authority = Pubkey::new_unique();
store.enable_role(RoleKey::GT_CONTROLLER).unwrap();
store.grant(&authority, RoleKey::GT_CONTROLLER).unwrap();
assert_eq!(store.has_role(&authority, RoleKey::GT_CONTROLLER), Ok(true));
store.disable_role(RoleKey::GT_CONTROLLER).unwrap();
assert!(store.has_role(&authority, RoleKey::GT_CONTROLLER).is_err());
store.enable_role(RoleKey::GT_CONTROLLER).unwrap();
assert_eq!(store.has_role(&authority, RoleKey::GT_CONTROLLER), Ok(true));
}
}