diff --git a/src/components/member_manager.cairo b/src/components/member_manager.cairo index 3c9c518..7cefa4d 100644 --- a/src/components/member_manager.cairo +++ b/src/components/member_manager.cairo @@ -388,7 +388,6 @@ pub mod MemberManagerComponent { fn get_core_org_address(self: @ComponentState) -> ContractAddress { self.core_org.read() } - fn is_admin( self: @ComponentState, member_address: ContractAddress, ) -> bool { diff --git a/src/components/organization.cairo b/src/components/organization.cairo index 0cdc162..f019d35 100644 --- a/src/components/organization.cairo +++ b/src/components/organization.cairo @@ -8,9 +8,7 @@ /// - Ownership transfers. #[starknet::component] pub mod OrganizationComponent { - use starknet::storage::{ - Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess, - }; + use starknet::storage::{Map, StoragePathEntry,StoragePointerReadAccess, StoragePointerWriteAccess}; use starknet::{ContractAddress, get_block_timestamp, get_caller_address}; // use core::ec::stark_curve; // use core::ecdsa; @@ -21,7 +19,6 @@ pub mod OrganizationComponent { }; use super::super::member_manager::MemberManagerComponent; - /// Defines the storage layout for the `OrganizationComponent`. #[storage] pub struct Storage { @@ -79,6 +76,8 @@ pub mod OrganizationComponent { add: Array, subtract: Array, ) { // any one subtracted, power would be taken down to zero. + + } /// Updates the organization's configuration. @@ -205,6 +204,7 @@ pub mod OrganizationComponent { self.contracts.entry(contract_id).read() } } + /// # InternalImpl /// diff --git a/src/components/permission_manager.cairo b/src/components/permission_manager.cairo new file mode 100644 index 0000000..2b6f1b0 --- /dev/null +++ b/src/components/permission_manager.cairo @@ -0,0 +1,143 @@ +/// ## A Starknet component for managing granular permissions. +/// +/// This component assigns specific permissions to accounts using a bitmask. +#[starknet::component] +pub mod PermissionManagerComponent { + use starknet::storage::{Map, StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; + use core::array::{ArrayTrait, array, SpanTrait}; + use core::option::OptionTrait; + use core::integer::u256; + + /// Permissions in the organization, represented as a bitmask. + #[derive(Copy, Drop, Serde, PartialEq)] + pub enum Permission { + ADD_MEMBER = 1, + REMOVE_MEMBER = 2, + SEND_INVITES = 4, + SET_SALARIES = 8, + SET_DISBURSEMENT = 16, + ADD_VAULT_TOKENS = 32, + VAULT_FUNCTIONS = 64, + GRANT_ADMIN = 128, + REVOKE_ADMIN = 256, + } + + /// Defines the storage layout for the `PermissionManagerComponent`. + #[storage] + pub struct Storage { + /// Maps an address to its bitmask of permissions. + pub permissions: Map, + } + + /// Events emitted by the `PermissionManagerComponent`. + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + PermissionGranted: PermissionGranted, + PermissionRevoked: PermissionRevoked, + AllPermissionsGranted: AllPermissionsGranted, + AllPermissionsRevoked: AllPermissionsRevoked, + } + + #[derive(Drop, starknet::Event)] + pub struct PermissionGranted { + #[key] + pub account: ContractAddress, + pub permission: Permission, + } + + #[derive(Drop, starknet::Event)] + pub struct PermissionRevoked { + #[key] + pub account: ContractAddress, + pub permission: Permission, + } + + #[derive(Drop, starknet::Event)] + pub struct AllPermissionsGranted { + #[key] + pub account: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + pub struct AllPermissionsRevoked { + #[key] + pub account: ContractAddress, + } + + /// # PermissionManagerComponent + /// + /// Public-facing API for permission management. + #[embeddable_as(PermissionManager)] + pub impl Permission< + TContractState, +HasComponent, +Drop, + > of crate::interfaces::ipermission::IPermission> { + /// Grants a specific permission to an account. + fn grant_permission(ref self: ComponentState, account: ContractAddress, permission: Permission) { + let caller = get_caller_address(); + let has_permission = self.internal._has_permission(caller, Permission::GRANT_ADMIN); + assert(has_permission, 'Perm: caller not admin'); + + let mut current_permissions = self.permissions.read(account); + current_permissions = current_permissions | (permission.into()); + self.permissions.write(account, current_permissions); + self.emit(Event::PermissionGranted(PermissionGranted { account, permission })); + } + + /// Revokes a specific permission from an account. + fn revoke_permission(ref self: ComponentState, account: ContractAddress, permission: Permission) { + let caller = get_caller_address(); + let has_permission = self.internal._has_permission(caller, Permission::REVOKE_ADMIN); + assert(has_permission, 'Perm: caller not admin'); + + let mut current_permissions = self.permissions.read(account); + current_permissions = current_permissions & (!(permission.into())); + self.permissions.write(account, current_permissions); + self.emit(Event::PermissionRevoked(PermissionRevoked { account, permission })); + } + + /// Grants all permissions to an account. + fn grant_all_permissions(ref self: ComponentState, account: ContractAddress) { + let caller = get_caller_address(); + let has_permission = self.internal._has_permission(caller, Permission::GRANT_ADMIN); + assert(has_permission, 'Perm: caller not admin'); + + // Set all bits to 1 (full permissions) + let all_perms = 511; // Sum of all enum values + self.permissions.write(account, all_perms); + self.emit(Event::AllPermissionsGranted(AllPermissionsGranted { account })); + } + + /// Revokes all permissions from an account. + fn revoke_all_permissions(ref self: ComponentState, account: ContractAddress) { + let caller = get_caller_address(); + let has_permission = self.internal._has_permission(caller, Permission::REVOKE_ADMIN); + assert(has_permission, 'Perm: caller not admin'); + + self.permissions.write(account, 0); + self.emit(Event::AllPermissionsRevoked(AllPermissionsRevoked { account })); + } + + /// Checks if an account has a specific permission. + fn has_permission(self: @ComponentState, account: ContractAddress, permission: Permission) -> bool { + self.internal._has_permission(account, permission) + } + } + + /// # InternalImpl + #[generate_trait] + pub impl InternalImpl, > of PermissionInternalTrait { + /// Initializes the permission manager with an owner who has all permissions. + fn _init(ref self: ComponentState, owner: ContractAddress) { + let all_perms = 511; // Sum of all enum values + self.permissions.write(owner, all_perms); + } + + /// Internal function to check if an account has a specific permission. + fn _has_permission(self: @ComponentState, account: ContractAddress, permission: Permission) -> bool { + let account_perms = self.permissions.read(account); + (account_perms & (permission.into())) != 0 + } + } +} \ No newline at end of file diff --git a/src/contracts/core.cairo b/src/contracts/core.cairo index 24c3a56..240683c 100644 --- a/src/contracts/core.cairo +++ b/src/contracts/core.cairo @@ -258,12 +258,19 @@ mod Core { } for i in 0..no_of_members { let current_member_response = *members.at(i); + // let pseudo_current_member = Member { + // id: current_member_response.id, + // address: current_member_response.address, + // status: current_member_response.status, + // role: current_member_response.role, + // // base_pay: current_member_response.base_pay, + // }; let timestamp = get_block_timestamp(); let amount = self .disbursement .compute_renumeration(current_member_response, total_bonus, total_weight); vault_dispatcher.pay_member(token, current_member_response.address, amount); - self.member.record_member_payment(current_member_response.id, amount, timestamp) + self.member.record_member_payment(current_member_response.id, amount, timestamp); } self.disbursement.update_current_schedule_last_execution(now); diff --git a/src/interfaces/iorganization.cairo b/src/interfaces/iorganization.cairo index 850e9e5..ed80ab7 100644 --- a/src/interfaces/iorganization.cairo +++ b/src/interfaces/iorganization.cairo @@ -55,7 +55,6 @@ pub trait IOrganization { /// /// An `OrganizationInfo` struct containing the organization's details. fn get_organization_details(self: @TContractState) -> OrganizationInfo; - // fn create_contract( // ref self: TContractState, // contract_type: ContractType, diff --git a/src/structs/organization.cairo b/src/structs/organization.cairo index a872809..c7b90b8 100644 --- a/src/structs/organization.cairo +++ b/src/structs/organization.cairo @@ -40,7 +40,6 @@ pub struct OrganizationConfig { pub struct OrganizationConfigNode { pub additional_data: Vec, } - #[allow(starknet::store_no_default_variant)] #[derive(Copy, Drop, Serde, starknet::Store)] pub enum ContractType { @@ -98,3 +97,4 @@ impl ContractImpl of ContractTrait { default_contract } } +