use alloc::vec;
use alloc::vec::Vec;
use keetanetwork_account::GenericAccount;
use num_bigint::BigInt;
use num_traits::Zero;
use crate::error::BlockError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum BaseFlag {
Access = 0,
Owner = 1,
Admin = 2,
UpdateInfo = 3,
SendOnBehalf = 4,
TokenAdminCreate = 5,
TokenAdminSupply = 6,
TokenAdminModifyBalance = 7,
StorageCreate = 8,
StorageCanHold = 9,
StorageDeposit = 10,
PermissionDelegateAdd = 11,
PermissionDelegateRemove = 12,
ManageCertificate = 13,
MultisigSigner = 14,
}
const ALL_BASE_FLAGS: [BaseFlag; 15] = [
BaseFlag::Access,
BaseFlag::Owner,
BaseFlag::Admin,
BaseFlag::UpdateInfo,
BaseFlag::SendOnBehalf,
BaseFlag::TokenAdminCreate,
BaseFlag::TokenAdminSupply,
BaseFlag::TokenAdminModifyBalance,
BaseFlag::StorageCreate,
BaseFlag::StorageCanHold,
BaseFlag::StorageDeposit,
BaseFlag::PermissionDelegateAdd,
BaseFlag::PermissionDelegateRemove,
BaseFlag::ManageCertificate,
BaseFlag::MultisigSigner,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PermissionGroup {
Never,
Any,
NonIdentifier,
Network,
Token,
Storage,
NonIdentifierOrMultisig,
Multisig,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupKind {
Entity,
Principal,
Target,
}
struct FlagRule {
can_be_default: bool,
entity: PermissionGroup,
principal: PermissionGroup,
target: PermissionGroup,
}
impl FlagRule {
fn group(&self, kind: GroupKind) -> PermissionGroup {
match kind {
GroupKind::Entity => self.entity,
GroupKind::Principal => self.principal,
GroupKind::Target => self.target,
}
}
}
const fn rule_for(flag: BaseFlag) -> FlagRule {
match flag {
BaseFlag::Access => FlagRule {
can_be_default: true,
entity: PermissionGroup::Any,
principal: PermissionGroup::Any,
target: PermissionGroup::Any,
},
BaseFlag::Owner | BaseFlag::Admin | BaseFlag::UpdateInfo => FlagRule {
can_be_default: false,
entity: PermissionGroup::Any,
principal: PermissionGroup::NonIdentifier,
target: PermissionGroup::Never,
},
BaseFlag::SendOnBehalf => FlagRule {
can_be_default: false,
entity: PermissionGroup::Any,
principal: PermissionGroup::NonIdentifier,
target: PermissionGroup::Token,
},
BaseFlag::StorageCreate | BaseFlag::TokenAdminCreate => FlagRule {
can_be_default: true,
entity: PermissionGroup::Network,
principal: PermissionGroup::NonIdentifier,
target: PermissionGroup::Never,
},
BaseFlag::StorageCanHold => FlagRule {
can_be_default: true,
entity: PermissionGroup::Storage,
principal: PermissionGroup::Token,
target: PermissionGroup::Never,
},
BaseFlag::StorageDeposit => FlagRule {
can_be_default: true,
entity: PermissionGroup::Storage,
principal: PermissionGroup::Any,
target: PermissionGroup::Token,
},
BaseFlag::TokenAdminSupply => FlagRule {
can_be_default: false,
entity: PermissionGroup::Token,
principal: PermissionGroup::NonIdentifier,
target: PermissionGroup::Never,
},
BaseFlag::TokenAdminModifyBalance => FlagRule {
can_be_default: false,
entity: PermissionGroup::Token,
principal: PermissionGroup::NonIdentifier,
target: PermissionGroup::Any,
},
BaseFlag::PermissionDelegateAdd | BaseFlag::PermissionDelegateRemove => FlagRule {
can_be_default: false,
entity: PermissionGroup::Any,
principal: PermissionGroup::NonIdentifier,
target: PermissionGroup::Any,
},
BaseFlag::ManageCertificate => FlagRule {
can_be_default: true,
entity: PermissionGroup::Any,
principal: PermissionGroup::Any,
target: PermissionGroup::Never,
},
BaseFlag::MultisigSigner => FlagRule {
can_be_default: false,
entity: PermissionGroup::Multisig,
principal: PermissionGroup::NonIdentifierOrMultisig,
target: PermissionGroup::Never,
},
}
}
fn bit_set(bits: &BigInt, offset: u8) -> bool {
!((bits & (BigInt::from(1) << offset)).is_zero())
}
fn account_matches_group(group: PermissionGroup, account: &GenericAccount) -> bool {
match group {
PermissionGroup::Any => true,
PermissionGroup::Never => false,
PermissionGroup::NonIdentifier | PermissionGroup::NonIdentifierOrMultisig => {
!account.to_keypair_type().is_identifier() || matches!(account, GenericAccount::Multisig(_))
}
PermissionGroup::Network => matches!(account, GenericAccount::Network(_)),
PermissionGroup::Token => matches!(account, GenericAccount::Token(_)),
PermissionGroup::Storage => matches!(account, GenericAccount::Storage(_)),
PermissionGroup::Multisig => matches!(account, GenericAccount::Multisig(_)),
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct BaseSet {
bits: BigInt,
flags: Vec<BaseFlag>,
}
impl BaseSet {
pub fn from_flags(flags: &[BaseFlag]) -> Result<Self, BlockError> {
let mut bits = BigInt::ZERO;
for flag in flags {
bits |= BigInt::from(1) << (*flag as u8);
}
Self::try_from(bits)
}
pub fn as_bigint(&self) -> &BigInt {
&self.bits
}
pub fn flags(&self) -> &[BaseFlag] {
&self.flags
}
pub fn has_flags(&self, flags: &[BaseFlag]) -> bool {
flags.iter().all(|flag| self.flags.contains(flag))
}
pub fn is_valid_for_default(&self) -> bool {
self.flags.iter().all(|flag| rule_for(*flag).can_be_default)
}
fn flag_group(&self, kind: GroupKind) -> Result<PermissionGroup, BlockError> {
compute_flag_group(kind, &self.flags)
}
pub fn check_account_matches_group(&self, kind: GroupKind, account: Option<&GenericAccount>) -> bool {
let Ok(group) = self.flag_group(kind) else {
return false;
};
let Some(account) = account else {
if kind == GroupKind::Target {
return true;
}
return group == PermissionGroup::Never;
};
account_matches_group(group, account)
}
}
fn compute_flag_group(kind: GroupKind, flags: &[BaseFlag]) -> Result<PermissionGroup, BlockError> {
let mut final_group = PermissionGroup::Any;
for flag in flags {
let found = rule_for(*flag).group(kind);
if found == PermissionGroup::Any || found == final_group {
continue;
}
if found == PermissionGroup::Never {
return Ok(found);
}
if final_group == PermissionGroup::Any {
final_group = found;
continue;
}
return Err(BlockError::PermissionsCannotMix);
}
Ok(final_group)
}
impl TryFrom<&[BaseFlag]> for BaseSet {
type Error = BlockError;
fn try_from(flags: &[BaseFlag]) -> Result<Self, Self::Error> {
Self::from_flags(flags)
}
}
impl TryFrom<BigInt> for BaseSet {
type Error = BlockError;
fn try_from(bits: BigInt) -> Result<Self, Self::Error> {
let raw_flags: Vec<BaseFlag> = ALL_BASE_FLAGS
.iter()
.copied()
.filter(|flag| bit_set(&bits, *flag as u8))
.collect();
compute_flag_group(GroupKind::Entity, &raw_flags)?;
compute_flag_group(GroupKind::Principal, &raw_flags)?;
compute_flag_group(GroupKind::Target, &raw_flags)?;
let mut flags = raw_flags;
if flags.contains(&BaseFlag::Owner) {
flags = vec![BaseFlag::Owner];
}
if flags.contains(&BaseFlag::Admin) {
flags = vec![BaseFlag::Admin];
}
if !flags.is_empty() && !flags.contains(&BaseFlag::Access) {
flags.push(BaseFlag::Access);
}
let mut normalized = bits;
for flag in ALL_BASE_FLAGS {
let mask = BigInt::from(1) << (flag as u8);
if flags.contains(&flag) {
normalized |= mask;
} else if bit_set(&normalized, flag as u8) {
normalized ^= mask;
}
}
Ok(Self { bits: normalized, flags })
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ExternalSet {
bits: BigInt,
}
impl ExternalSet {
pub fn as_bigint(&self) -> &BigInt {
&self.bits
}
pub fn has_offset(&self, offset: u8) -> bool {
bit_set(&self.bits, offset)
}
fn size(&self) -> u64 {
self.bits.to_str_radix(2).len() as u64
}
pub fn validate(&self, max_external_offset: u64) -> Result<(), BlockError> {
let size = self.size();
if size >= max_external_offset {
return Err(BlockError::PermissionsExternalOffsetTooLarge { size, max: max_external_offset });
}
Ok(())
}
}
impl From<BigInt> for ExternalSet {
fn from(bits: BigInt) -> Self {
Self { bits }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Permissions {
base: BaseSet,
external: ExternalSet,
}
impl Permissions {
pub fn from_bigints(base: BigInt, external: BigInt) -> Result<Self, BlockError> {
Ok(Self { base: BaseSet::try_from(base)?, external: ExternalSet::from(external) })
}
pub fn from_flags(flags: &[BaseFlag], external_offsets: &[u8]) -> Result<Self, BlockError> {
let mut external = BigInt::ZERO;
for offset in external_offsets {
external |= BigInt::from(1) << *offset;
}
Ok(Self { base: BaseSet::from_flags(flags)?, external: ExternalSet::from(external) })
}
pub fn base(&self) -> &BaseSet {
&self.base
}
pub fn external(&self) -> &ExternalSet {
&self.external
}
pub fn validate(&self, max_external_offset: u64) -> Result<(), BlockError> {
self.external.validate(max_external_offset)
}
pub fn has(&self, flags: &[BaseFlag], offsets: &[u8]) -> bool {
if flags.is_empty() && offsets.is_empty() {
return true;
}
if !self.base.has_flags(&[BaseFlag::Access]) {
return false;
}
if self.base.has_flags(&[BaseFlag::Owner]) {
return true;
}
if !flags.contains(&BaseFlag::Owner) && self.base.has_flags(&[BaseFlag::Admin]) {
return true;
}
flags.iter().all(|flag| self.base.has_flags(&[*flag]))
&& offsets
.iter()
.all(|offset| self.external.has_offset(*offset))
}
pub fn can_use_delegation(&self) -> bool {
let forbidden = [BaseFlag::Admin, BaseFlag::PermissionDelegateAdd, BaseFlag::PermissionDelegateRemove];
!forbidden.iter().any(|flag| self.has(&[*flag], &[]))
}
}
impl TryFrom<(BigInt, BigInt)> for Permissions {
type Error = BlockError;
fn try_from((base, external): (BigInt, BigInt)) -> Result<Self, Self::Error> {
Self::from_bigints(base, external)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn base_set(bits: BigInt) -> BaseSet {
BaseSet::try_from(bits).expect("test base set must construct")
}
fn make_permissions(flags: &[BaseFlag], externals: &[u8]) -> Permissions {
Permissions::from_flags(flags, externals).expect("test permissions must construct")
}
fn base_set_from_flags(flags: &[BaseFlag]) -> BaseSet {
BaseSet::from_flags(flags).expect("test base set from flags must construct")
}
#[test]
fn test_owner_overrides_other_flags() {
let bits = BigInt::from((1 << 1) | (1 << 3));
let set = base_set(bits);
assert_eq!(set.flags(), &[BaseFlag::Owner, BaseFlag::Access]);
assert_eq!(*set.as_bigint(), BigInt::from((1 << 1) | 1));
}
#[test]
fn test_access_implied_by_nonempty_set() {
let set = base_set(BigInt::from(1 << 3));
assert!(set.has_flags(&[BaseFlag::Access, BaseFlag::UpdateInfo]));
}
#[test]
fn test_unknown_high_bits_preserved() {
let bits = BigInt::from(1u64 << 40);
let set = base_set(bits.clone());
assert!(set.flags().is_empty());
assert_eq!(*set.as_bigint(), bits);
}
#[test]
fn test_mixed_groups_rejected() {
let bits = BigInt::from((1 << 6) | (1 << 10));
let result = BaseSet::try_from(bits);
assert!(matches!(result, Err(BlockError::PermissionsCannotMix)));
}
#[test]
fn test_has_owner_grants_all() {
let permissions = make_permissions(&[BaseFlag::Owner], &[]);
assert!(permissions.has(&[BaseFlag::UpdateInfo], &[5]));
}
#[test]
fn test_has_admin_grants_all_but_owner() {
let permissions = make_permissions(&[BaseFlag::Admin], &[]);
assert!(permissions.has(&[BaseFlag::UpdateInfo], &[]));
assert!(!permissions.has(&[BaseFlag::Owner], &[]));
}
#[test]
fn test_has_requires_access() {
let permissions = Permissions::default();
assert!(permissions.has(&[], &[]));
assert!(!permissions.has(&[BaseFlag::Access], &[]));
}
#[test]
fn test_external_offset_validation() {
let permissions = make_permissions(&[], &[31]);
assert!(matches!(
permissions.validate(32),
Err(BlockError::PermissionsExternalOffsetTooLarge { size: 32, max: 32 })
));
let small = make_permissions(&[], &[30]);
assert!(small.validate(32).is_ok());
}
#[test]
fn test_can_use_delegation() {
let plain = make_permissions(&[BaseFlag::UpdateInfo], &[]);
assert!(plain.can_use_delegation());
let admin = make_permissions(&[BaseFlag::Admin], &[]);
assert!(!admin.can_use_delegation());
let delegate = make_permissions(&[BaseFlag::PermissionDelegateAdd], &[]);
assert!(!delegate.can_use_delegation());
}
#[test]
fn test_base_set_try_from_flags() -> Result<(), BlockError> {
let from_slice = BaseSet::try_from([BaseFlag::UpdateInfo].as_slice())?;
let from_flags = BaseSet::from_flags(&[BaseFlag::UpdateInfo])?;
assert_eq!(from_slice, from_flags);
Ok(())
}
#[test]
fn test_permissions_try_from_bigints() -> Result<(), BlockError> {
let tuple = Permissions::try_from((BigInt::from(1), BigInt::ZERO))?;
let direct = Permissions::from_bigints(BigInt::from(1), BigInt::ZERO)?;
assert_eq!(tuple, direct);
Ok(())
}
#[test]
fn test_is_valid_for_default() {
let valid = base_set_from_flags(&[BaseFlag::Access]);
assert!(valid.is_valid_for_default());
let invalid = base_set_from_flags(&[BaseFlag::UpdateInfo]);
assert!(!invalid.is_valid_for_default());
}
}