Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/base/errors.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@

// Error constants for permission system
pub mod permission_errors {
pub const NO_PERMISSION: felt252 = 'You do not have permission';
pub const NOT_ACCOUNT_OWNER: felt252 = 'Not the account owner';
pub const PERMISSION_NOT_FOUND: felt252 = 'Permission not found';
pub const INVALID_PERMISSION: felt252 = 'Invalid permission value';
}
25 changes: 25 additions & 0 deletions src/base/types.cairo
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use starknet::ContractAddress;

#[derive(Drop, Serde, starknet::Store)]
pub struct TokenBoundAccount {
pub id: u256,
Expand All @@ -8,7 +9,9 @@ pub struct TokenBoundAccount {
pub init_param2: felt252,
pub created_at: u64,
pub updated_at: u64,
pub owner_permissions: Permissions, // Owner's permissions
}

#[derive(Drop, Serde, starknet::Store)]
pub struct User {
pub id: u256,
Expand All @@ -20,6 +23,28 @@ pub struct User {
pub metadata: felt252,
}

// Permission flags using bit flags for flexibility
#[derive(Drop, Copy, Serde, starknet::Store, Default, PartialEq)]
pub struct Permissions {
pub value: u64, // Using u64 to store permission bits
}

// Permission constants
pub mod permission_flags {
// Basic permissions
pub const NONE: u64 = 0x0;
pub const FULL: u64 = 0xFFFFFFFFFFFFFFFF;

// Specific permissions - using powers of 2 for bit flags
pub const READ: u64 = 0x1; // Can read account data
pub const WRITE: u64 = 0x2; // Can update account data
pub const TRANSFER: u64 = 0x4; // Can transfer tokens
pub const MANAGE_PERMISSIONS: u64 = 0x8; // Can update permissions
pub const EXECUTE: u64 = 0x10; // Can execute transactions
pub const MANAGE_OPERATORS: u64 = 0x20; // Can add/remove operators
pub const UPGRADE: u64 = 0x40; // Can upgrade the account
pub const DELETE: u64 = 0x80; // Can delete the account
}

#[derive(Drop, Serde, starknet::Store, Clone, PartialEq)]
pub enum Role {
Expand Down
160 changes: 129 additions & 31 deletions src/chainlib/ChainLib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub mod ChainLib {
};
use starknet::{ContractAddress, get_block_timestamp, get_caller_address};
use crate::interfaces::IChainLib::IChainLib;
use crate::base::types::{TokenBoundAccount, User, Role, Rank};
use crate::base::types::{TokenBoundAccount, User, Role, Rank, Permissions, permission_flags};

#[derive(Copy, Drop, Serde, starknet::Store, PartialEq, Debug)]
pub enum ContentType {
Expand Down Expand Up @@ -48,7 +48,11 @@ pub mod ChainLib {
users: Map<u256, User>,
creators_content: Map::<ContractAddress, ContentMetadata>,
content: Map::<felt252, ContentMetadata>,
content_tags: Map::<ContentMetadata, Array<felt252>>
content_tags: Map::<ContentMetadata, Array<felt252>>,
// Permission system storage
operator_permissions: Map::<
(u256, ContractAddress), Permissions
>, // Maps account_id and operator to permissions
}


Expand All @@ -63,6 +67,10 @@ pub mod ChainLib {
pub enum Event {
TokenBoundAccountCreated: TokenBoundAccountCreated,
UserCreated: UserCreated,
// Permission-related events
PermissionGranted: PermissionGranted,
PermissionRevoked: PermissionRevoked,
PermissionModified: PermissionModified,
}

#[derive(Drop, starknet::Event)]
Expand All @@ -75,15 +83,28 @@ pub mod ChainLib {
pub id: u256,
}

// Permission-related events
#[derive(Drop, starknet::Event)]
pub struct PermissionGranted {
pub account_id: u256,
pub operator: ContractAddress,
pub permissions: Permissions,
}

#[derive(Drop, starknet::Event)]
pub struct PermissionRevoked {
pub account_id: u256,
pub operator: ContractAddress,
}

#[derive(Drop, starknet::Event)]
pub struct PermissionModified {
pub account_id: u256,
pub permissions: Permissions,
}

#[abi(embed_v0)]
impl ChainLibNetImpl of IChainLib<ContractState> {
/// @notice Creates a new token-bound account.
/// @dev This function generates a unique ID, initializes the account, and emits an event.
/// @param self The contract state reference.
/// @param user_name The unique username associated with the token-bound account.
/// @param init_param1 An initialization parameter required for the account setup.
/// @param init_param2 An additional initialization parameter.
/// @return account_id The unique identifier assigned to the token-bound account.
fn create_token_account(
ref self: ContractState, user_name: felt252, init_param1: felt252, init_param2: felt252
) -> u256 {
Expand All @@ -95,16 +116,21 @@ pub mod ChainLib {

// Retrieve the current account ID before incrementing.
let account_id = self.current_account_id.read();
let caller = get_caller_address();

// Create default full permissions for the owner
let owner_permissions = Permissions { value: permission_flags::FULL };

// Create a new token-bound account with the provided parameters.
let new_token_bound_account = TokenBoundAccount {
id: account_id,
address: get_caller_address(), // Assign the caller's address.
address: caller, // Assign the caller's address.
user_name: user_name,
init_param1: init_param1,
init_param2: init_param2,
created_at: get_block_timestamp(), // Capture the creation timestamp.
updated_at: get_block_timestamp() // Set initial updated timestamp.
updated_at: get_block_timestamp(), // Set initial updated timestamp.
owner_permissions: owner_permissions, // Set owner permissions
};

// Store the new account in the accounts mapping.
Expand Down Expand Up @@ -133,16 +159,6 @@ pub mod ChainLib {
}


/// @notice Registers a new user in the system.
/// @dev This function assigns a unique ID to the user, stores their profile, and emits an
/// event.
/// @param self The contract state reference.
/// @param username The unique username of the user.
/// @param wallet_address The blockchain address of the user.
/// @param role The role of the user (READER or WRITER).
/// @param rank The rank/level of the user.
/// @param metadata Additional metadata associated with the user.
/// @return user_id The unique identifier assigned to the user.
fn register_user(
ref self: ContractState, username: felt252, role: Role, rank: Rank, metadata: felt252
) -> u256 {
Expand Down Expand Up @@ -177,11 +193,6 @@ pub mod ChainLib {
}


/// @notice Verifies a user in the system.
/// @dev Only an admin can verify a user.
/// @param self The contract state reference.
/// @param user_id The unique identifier of the user to be verified.
/// @return bool Returns true if the user is successfully verified.
fn verify_user(ref self: ContractState, user_id: u256) -> bool {
let caller = get_caller_address();
// Ensure that only an admin can verify users.
Expand All @@ -191,11 +202,6 @@ pub mod ChainLib {
self.users.write(user.id, user);
true
}
/// @notice Retrieves a user's profile from the system.
/// @dev This function fetches the user profile based on the provided user ID.
/// @param self The contract state reference.
/// @param user_id The unique identifier of the user whose profile is being retrieved.
/// @return User The user profile associated with the given user ID.
fn retrieve_user_profile(ref self: ContractState, user_id: u256) -> User {
// Read the user profile from the storage mapping.
let user = self.users.read(user_id);
Expand All @@ -214,5 +220,97 @@ pub mod ChainLib {
let admin = self.admin.read();
admin
}

// Permission system implementation

fn get_permissions(
self: @ContractState, account_id: u256, operator: ContractAddress
) -> Permissions {
let account = self.accounts.read(account_id);

// If the operator is the owner, return the owner's permissions
if operator == account.address {
return account.owner_permissions;
}

// Otherwise, return the operator's permissions
self.operator_permissions.read((account_id, operator))
}

fn set_operator_permissions(
ref self: ContractState,
account_id: u256,
operator: ContractAddress,
permissions: Permissions
) -> bool {
let caller = get_caller_address();
let account = self.accounts.read(account_id);

// Ensure that the caller is the account owner or has MANAGE_OPERATORS permission
let caller_permissions = self.get_permissions(account_id, caller);
assert(
account.address == caller
|| (caller_permissions.value & permission_flags::MANAGE_OPERATORS) != 0,
'No permission'
);

// Store the operator's permissions
self.operator_permissions.write((account_id, operator), permissions);

// Emit the permission granted event
self.emit(PermissionGranted { account_id, operator, permissions });

true
}

fn revoke_operator(
ref self: ContractState, account_id: u256, operator: ContractAddress
) -> bool {
let caller = get_caller_address();
let account = self.accounts.read(account_id);

// Ensure that the caller is the account owner or has MANAGE_OPERATORS permission
let caller_permissions = self.get_permissions(account_id, caller);
assert(
account.address == caller
|| (caller_permissions.value & permission_flags::MANAGE_OPERATORS) != 0,
'No permission'
);

// Set permissions to NONE
let none_permissions = Permissions { value: permission_flags::NONE };
self.operator_permissions.write((account_id, operator), none_permissions);

// Emit the permission revoked event
self.emit(PermissionRevoked { account_id, operator });

true
}

fn has_permission(
self: @ContractState, account_id: u256, operator: ContractAddress, permission: u64
) -> bool {
let permissions = self.get_permissions(account_id, operator);
(permissions.value & permission) != 0
}

fn modify_account_permissions(
ref self: ContractState, account_id: u256, permissions: Permissions
) -> bool {
let caller = get_caller_address();
let mut account = self.accounts.read(account_id);

// Ensure that the caller is the account owner
assert(account.address == caller, 'Not owner');

// Update the owner's permissions
account.owner_permissions = permissions;
self.accounts.write(account_id, account);

// Emit the permission modified event
self.emit(PermissionModified { account_id, permissions });

true
}
}
}
26 changes: 25 additions & 1 deletion src/interfaces/IChainLib.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use starknet::ContractAddress;
use crate::base::types::{TokenBoundAccount, User, Role, Rank};
use crate::base::types::{TokenBoundAccount, User, Role, Rank, Permissions};

#[starknet::interface]
pub trait IChainLib<TContractState> {
Expand All @@ -20,5 +20,29 @@ pub trait IChainLib<TContractState> {
fn retrieve_user_profile(ref self: TContractState, user_id: u256) -> User;
fn getAdmin(self: @TContractState) -> ContractAddress;
fn is_verified(ref self: TContractState, user_id: u256) -> bool;

// Permission system
fn get_permissions(
self: @TContractState, account_id: u256, operator: ContractAddress
) -> Permissions;

fn set_operator_permissions(
ref self: TContractState,
account_id: u256,
operator: ContractAddress,
permissions: Permissions
) -> bool;

fn revoke_operator(
ref self: TContractState, account_id: u256, operator: ContractAddress
) -> bool;

fn has_permission(
self: @TContractState, account_id: u256, operator: ContractAddress, permission: u64
) -> bool;

fn modify_account_permissions(
ref self: TContractState, account_id: u256, permissions: Permissions
) -> bool;
}

2 changes: 2 additions & 0 deletions tests/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(test)]
pub mod test_ChainLib;
#[cfg(test)]
pub mod test_permissions;

Loading
Loading