From 16de0a92de2c8526877fcd7968f9e87a76d3449a Mon Sep 17 00:00:00 2001 From: CodesByMidnyt <100563026+3th-Enjay@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:09:39 +0100 Subject: [PATCH 1/8] Update UserRegistry.cairo --- src/chainlib/UserRegistry.cairo | 59 +++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/chainlib/UserRegistry.cairo b/src/chainlib/UserRegistry.cairo index 8b13789..e3507ab 100644 --- a/src/chainlib/UserRegistry.cairo +++ b/src/chainlib/UserRegistry.cairo @@ -1 +1,60 @@ +use starknet::ContractAddress; +use starknet::get_caller_address; +use starknet::syscalls::emit_event; + +#[derive(Drop, Serde, Clone, Copy, PartialEq)] +struct UserProfile { + name: felt252, + email: felt252, + is_active: bool, +} + +#[starknet::interface] +trait IUserRegistry { + fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); + fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); + fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool); + fn deactivate_user(self: @ContractState, user: ContractAddress); +} + +#[starknet::contract] +mod UserRegistry { + use super::*; + + struct Storage { + users: LegacyMap, + } + + #[external] + fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { + self.users.write(user, UserProfile { name, email, is_active: true }); + emit_event(("UserCreated", user, name, email)); + } + + #[external] + fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { + assert!(self.users.contains(user), "User not found"); + let mut profile = self.users.read(user); + profile.name = name; + profile.email = email; + self.users.write(user, profile); + emit_event(("UserUpdated", user, name, email)); + } + + #[external] + fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool) { + assert!(self.users.contains(user), "User not found"); + let profile = self.users.read(user); + (profile.name, profile.email, profile.is_active) + } + + #[external] + fn deactivate_user(self: @ContractState, user: ContractAddress) { + assert!(self.users.contains(user), "User not found"); + let mut profile = self.users.read(user); + profile.is_active = false; + self.users.write(user, profile); + emit_event(("UserDeactivated", user)); + } +} From c80c26bd85326221cc0a9f9c621994fc505a5b47 Mon Sep 17 00:00:00 2001 From: CodesByMidnyt <100563026+3th-Enjay@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:23:19 +0100 Subject: [PATCH 2/8] Update IContentRegistry.cairo --- src/interfaces/IContentRegistry.cairo | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/interfaces/IContentRegistry.cairo b/src/interfaces/IContentRegistry.cairo index 8b13789..6965566 100644 --- a/src/interfaces/IContentRegistry.cairo +++ b/src/interfaces/IContentRegistry.cairo @@ -1 +1,8 @@ +#[starknet::interface] +trait IUserRegistry { + fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); + fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); + fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool); + fn deactivate_user(self: @ContractState, user: ContractAddress); +} From 0517e38e4de6a62fdf834f42ea9ad6502272422e Mon Sep 17 00:00:00 2001 From: CodesByMidnyt <100563026+3th-Enjay@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:24:00 +0100 Subject: [PATCH 3/8] Update IContentRegistry.cairo --- src/interfaces/IContentRegistry.cairo | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/interfaces/IContentRegistry.cairo b/src/interfaces/IContentRegistry.cairo index 6965566..8b13789 100644 --- a/src/interfaces/IContentRegistry.cairo +++ b/src/interfaces/IContentRegistry.cairo @@ -1,8 +1 @@ -#[starknet::interface] -trait IUserRegistry { - fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); - fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); - fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool); - fn deactivate_user(self: @ContractState, user: ContractAddress); -} From cb7e70135827ef5a8568362b4eb581144c3ef1c2 Mon Sep 17 00:00:00 2001 From: CodesByMidnyt <100563026+3th-Enjay@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:24:15 +0100 Subject: [PATCH 4/8] Update IUserRegistry.cairo --- src/interfaces/IUserRegistry.cairo | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/interfaces/IUserRegistry.cairo b/src/interfaces/IUserRegistry.cairo index 8b13789..65b9f67 100644 --- a/src/interfaces/IUserRegistry.cairo +++ b/src/interfaces/IUserRegistry.cairo @@ -1 +1,10 @@ +#[starknet::interface] +trait IUserRegistry { + fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); + fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); + fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool); + fn deactivate_user(self: @ContractState, user: ContractAddress); +} + + From ba2aa65e31e5927d321a4ca08c99407f537bc8d4 Mon Sep 17 00:00:00 2001 From: CodesByMidnyt <100563026+3th-Enjay@users.noreply.github.com> Date: Fri, 28 Mar 2025 11:25:52 +0100 Subject: [PATCH 5/8] Update UserRegistry.cairo --- src/chainlib/UserRegistry.cairo | 99 +++++++++++++++------------------ 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/src/chainlib/UserRegistry.cairo b/src/chainlib/UserRegistry.cairo index e3507ab..0a99631 100644 --- a/src/chainlib/UserRegistry.cairo +++ b/src/chainlib/UserRegistry.cairo @@ -1,60 +1,53 @@ -use starknet::ContractAddress; -use starknet::get_caller_address; -use starknet::syscalls::emit_event; - -#[derive(Drop, Serde, Clone, Copy, PartialEq)] -struct UserProfile { - name: felt252, - email: felt252, - is_active: bool, -} - -#[starknet::interface] -trait IUserRegistry { - fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); - fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); - fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool); - fn deactivate_user(self: @ContractState, user: ContractAddress); -} - #[starknet::contract] mod UserRegistry { - use super::*; - + use starknet::ContractAddress; + use starknet::get_caller_address; + use starknet::syscalls::emit_event; + use super::IUserRegistry; + + #[derive(Drop, Serde, Clone, Copy, PartialEq)] + struct UserProfile { + name: felt252, + email: felt252, + is_active: bool, + } + struct Storage { users: LegacyMap, } - - #[external] - fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { - self.users.write(user, UserProfile { name, email, is_active: true }); - emit_event(("UserCreated", user, name, email)); - } - - #[external] - fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { - assert!(self.users.contains(user), "User not found"); - let mut profile = self.users.read(user); - profile.name = name; - profile.email = email; - self.users.write(user, profile); - emit_event(("UserUpdated", user, name, email)); - } - - #[external] - fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool) { - assert!(self.users.contains(user), "User not found"); - let profile = self.users.read(user); - (profile.name, profile.email, profile.is_active) - } - - #[external] - fn deactivate_user(self: @ContractState, user: ContractAddress) { - assert!(self.users.contains(user), "User not found"); - let mut profile = self.users.read(user); - profile.is_active = false; - self.users.write(user, profile); - emit_event(("UserDeactivated", user)); + + #[abi(embed_v0)] + impl UserRegistryImpl of IUserRegistry { + #[external] + fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { + self.users.write(user, UserProfile { name, email, is_active: true }); + emit_event(("UserCreated", user, name, email)); + } + + #[external] + fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { + assert!(self.users.contains(user), "User not found"); + let mut profile = self.users.read(user); + profile.name = name; + profile.email = email; + self.users.write(user, profile); + emit_event(("UserUpdated", user, name, email)); + } + + #[external] + fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool) { + assert!(self.users.contains(user), "User not found"); + let profile = self.users.read(user); + (profile.name, profile.email, profile.is_active) + } + + #[external] + fn deactivate_user(self: @ContractState, user: ContractAddress) { + assert!(self.users.contains(user), "User not found"); + let mut profile = self.users.read(user); + profile.is_active = false; + self.users.write(user, profile); + emit_event(("UserDeactivated", user)); + } } } - From df2252544f272ddfffc4a0b24d4ec2f2231a689c Mon Sep 17 00:00:00 2001 From: 3th-Enjay Date: Fri, 28 Mar 2025 12:40:08 +0100 Subject: [PATCH 6/8] Update tool versions and fix parameter formatting in ChainLib and IChainLib interfaces --- .tool-versions | 2 +- src/chainlib/ChainLib.cairo | 8 ++++---- src/interfaces/IChainLib.cairo | 4 ++-- src/interfaces/IUserRegistry.cairo | 2 -- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.tool-versions b/.tool-versions index ddfaa0e..9344a6e 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -scarb 2.8.4 +scarb 2.9.1 starknet-foundry 0.39.0 \ No newline at end of file diff --git a/src/chainlib/ChainLib.cairo b/src/chainlib/ChainLib.cairo index fa16763..7433d3f 100644 --- a/src/chainlib/ChainLib.cairo +++ b/src/chainlib/ChainLib.cairo @@ -55,7 +55,7 @@ pub mod ChainLib { /// @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 + ref self: ContractState, user_name: felt252, init_param1: felt252, init_param2: felt252, ) -> u256 { // Ensure that the username is not empty. assert!(user_name != 0, "User name cannot be empty"); @@ -96,7 +96,7 @@ pub mod ChainLib { token_bound_account } fn get_token_bound_account_by_owner( - ref self: ContractState, address: ContractAddress + ref self: ContractState, address: ContractAddress, ) -> TokenBoundAccount { let token_bound_account = self.accountsaddr.read(address); token_bound_account @@ -114,7 +114,7 @@ pub mod ChainLib { /// @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 + ref self: ContractState, username: felt252, role: Role, rank: Rank, metadata: felt252, ) -> u256 { // Ensure that the username is not empty. assert!(username != 0, "User name cannot be empty"); @@ -130,7 +130,7 @@ pub mod ChainLib { role: role, rank: rank, verified: false, // Default verification status is false. - metadata: metadata + metadata: metadata, }; // Store the new user in the users mapping. diff --git a/src/interfaces/IChainLib.cairo b/src/interfaces/IChainLib.cairo index 4f90038..8709a50 100644 --- a/src/interfaces/IChainLib.cairo +++ b/src/interfaces/IChainLib.cairo @@ -10,11 +10,11 @@ pub trait IChainLib { fn get_token_bound_account(ref self: TContractState, id: u256) -> TokenBoundAccount; fn get_token_bound_account_by_owner( - ref self: TContractState, address: ContractAddress + ref self: TContractState, address: ContractAddress, ) -> TokenBoundAccount; fn register_user( - ref self: TContractState, username: felt252, role: Role, rank: Rank, metadata: felt252 + ref self: TContractState, username: felt252, role: Role, rank: Rank, metadata: felt252, ) -> u256; fn verify_user(ref self: TContractState, user_id: u256) -> bool; fn retrieve_user_profile(ref self: TContractState, user_id: u256) -> User; diff --git a/src/interfaces/IUserRegistry.cairo b/src/interfaces/IUserRegistry.cairo index 65b9f67..6965566 100644 --- a/src/interfaces/IUserRegistry.cairo +++ b/src/interfaces/IUserRegistry.cairo @@ -6,5 +6,3 @@ trait IUserRegistry { fn deactivate_user(self: @ContractState, user: ContractAddress); } - - From b61b08e450fcb0005a6fedaa734da6120987720d Mon Sep 17 00:00:00 2001 From: 3th-Enjay Date: Sat, 29 Mar 2025 13:28:11 +0100 Subject: [PATCH 7/8] Enhance IUserRegistry interface with new user profile management functions --- src/chainlib/UserRegistry.cairo | 321 +++++++++++++++++++++++++++-- src/interfaces/IUserRegistry.cairo | 41 +++- 2 files changed, 334 insertions(+), 28 deletions(-) diff --git a/src/chainlib/UserRegistry.cairo b/src/chainlib/UserRegistry.cairo index 0a99631..db5b0f4 100644 --- a/src/chainlib/UserRegistry.cairo +++ b/src/chainlib/UserRegistry.cairo @@ -1,8 +1,6 @@ #[starknet::contract] mod UserRegistry { - use starknet::ContractAddress; - use starknet::get_caller_address; - use starknet::syscalls::emit_event; + use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; use super::IUserRegistry; #[derive(Drop, Serde, Clone, Copy, PartialEq)] @@ -10,44 +8,323 @@ mod UserRegistry { name: felt252, email: felt252, is_active: bool, + role: Role, + rank: Rank, + metadata: felt252, + created_at: u64, + updated_at: u64, + verified: bool, + } + + #[derive(Drop, Serde, Copy, PartialEq)] + enum Role { + READER, + WRITER, + ADMIN, + } + + #[derive(Drop, Serde, Copy, PartialEq)] + enum Rank { + LEVEL1, + LEVEL2, + LEVEL3, } struct Storage { users: LegacyMap, + user_ids: LegacyMap, + next_user_id: u256, + admin: ContractAddress, + active_users: LegacyMap, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UserCreated: UserCreated, + UserUpdated: UserUpdated, + UserDeactivated: UserDeactivated, + UserReactivated: UserReactivated, + UserVerified: UserVerified, + } + + #[derive(Drop, starknet::Event)] + struct UserCreated { + user_id: u256, + address: ContractAddress, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct UserUpdated { + user_id: u256, + fields: felt252, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct UserDeactivated { + user_id: u256, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct UserReactivated { + user_id: u256, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct UserVerified { + user_id: u256, + timestamp: u64, + } + + #[constructor] + fn constructor(ref self: ContractState, admin: ContractAddress) { + self.admin.write(admin); + self.next_user_id.write(1); } #[abi(embed_v0)] impl UserRegistryImpl of IUserRegistry { #[external] - fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { - self.users.write(user, UserProfile { name, email, is_active: true }); - emit_event(("UserCreated", user, name, email)); + fn create_user( + ref self: ContractState, + name: felt252, + email: felt252, + role: Role, + rank: Rank, + metadata: felt252, + ) -> u256 { + let caller = get_caller_address(); + assert(!self.users.contains(caller), "User already exists"); + + let user_id = self.next_user_id.read(); + let new_profile = UserProfile { + name, + email, + is_active: true, + role, + rank, + metadata, + created_at: get_block_timestamp(), + updated_at: get_block_timestamp(), + verified: false, + }; + + self.users.write(caller, new_profile); + self.user_ids.write(user_id, caller); + self.active_users.write(caller, true); + self.next_user_id.write(user_id + 1); + + self + .emit( + Event::UserCreated( + UserCreated { user_id, address: caller, timestamp: get_block_timestamp() }, + ), + ); + + user_id } #[external] - fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { - assert!(self.users.contains(user), "User not found"); - let mut profile = self.users.read(user); - profile.name = name; - profile.email = email; - self.users.write(user, profile); - emit_event(("UserUpdated", user, name, email)); + fn update_user( + ref self: ContractState, + user_id: u256, + name: felt252, + email: felt252, + role: Role, + rank: Rank, + metadata: felt252, + ) -> bool { + let caller = get_caller_address(); + let user_address = self.user_ids.read(user_id); + let mut profile = self.users.read(user_address); + let admin = self.admin.read(); + + assert(caller == user_address || caller == admin, "Unauthorized"); + assert(self.active_users.read(user_address), "User inactive"); + + let mut fields_updated = 0; + let mut changes_made = false; + + if name != 0 && name != profile.name { + profile.name = name; + fields_updated = if fields_updated == 0 { + 'name' + } else { + 'multiple' + }; + changes_made = true; + } + + if email != 0 && email != profile.email { + profile.email = email; + fields_updated = if fields_updated == 0 { + 'email' + } else { + 'multiple' + }; + changes_made = true; + } + + if role != profile.role { + profile.role = role; + fields_updated = if fields_updated == 0 { + 'role' + } else { + 'multiple' + }; + changes_made = true; + } + + if rank != profile.rank { + profile.rank = rank; + fields_updated = if fields_updated == 0 { + 'rank' + } else { + 'multiple' + }; + changes_made = true; + } + + if metadata != 0 && metadata != profile.metadata { + profile.metadata = metadata; + fields_updated = if fields_updated == 0 { + 'metadata' + } else { + 'multiple' + }; + changes_made = true; + } + + if changes_made { + profile.updated_at = get_block_timestamp(); + self.users.write(user_address, profile); + self + .emit( + Event::UserUpdated( + UserUpdated { + user_id, fields: fields_updated, timestamp: get_block_timestamp(), + }, + ), + ); + } + + changes_made } #[external] - fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool) { - assert!(self.users.contains(user), "User not found"); - let profile = self.users.read(user); - (profile.name, profile.email, profile.is_active) + fn get_user( + self: @ContractState, user_id: u256, + ) -> (felt252, felt252, bool, Role, Rank, felt252, bool) { + let user_address = self.user_ids.read(user_id); + assert(self.users.contains(user_address), "User not found"); + let profile = self.users.read(user_address); + ( + profile.name, + profile.email, + profile.is_active, + profile.role, + profile.rank, + profile.metadata, + profile.verified, + ) } #[external] - fn deactivate_user(self: @ContractState, user: ContractAddress) { - assert!(self.users.contains(user), "User not found"); - let mut profile = self.users.read(user); + fn get_user_by_address( + self: @ContractState, address: ContractAddress, + ) -> (felt252, felt252, bool, Role, Rank, felt252, bool) { + assert(self.users.contains(address), "User not found"); + let profile = self.users.read(address); + ( + profile.name, + profile.email, + profile.is_active, + profile.role, + profile.rank, + profile.metadata, + profile.verified, + ) + } + + #[external] + fn deactivate_user(ref self: ContractState, user_id: u256) -> bool { + let caller = get_caller_address(); + let user_address = self.user_ids.read(user_id); + let admin = self.admin.read(); + + assert(caller == user_address || caller == admin, "Unauthorized"); + assert(self.active_users.read(user_address), "Already inactive"); + + self.active_users.write(user_address, false); + let mut profile = self.users.read(user_address); profile.is_active = false; - self.users.write(user, profile); - emit_event(("UserDeactivated", user)); + self.users.write(user_address, profile); + + self + .emit( + Event::UserDeactivated( + UserDeactivated { user_id, timestamp: get_block_timestamp() }, + ), + ); + + true + } + + #[external] + fn reactivate_user(ref self: ContractState, user_id: u256) -> bool { + let caller = get_caller_address(); + let user_address = self.user_ids.read(user_id); + let admin = self.admin.read(); + + assert(caller == user_address || caller == admin, "Unauthorized"); + assert(!self.active_users.read(user_address), "Already active"); + + self.active_users.write(user_address, true); + let mut profile = self.users.read(user_address); + profile.is_active = true; + self.users.write(user_address, profile); + + self + .emit( + Event::UserReactivated( + UserReactivated { user_id, timestamp: get_block_timestamp() }, + ), + ); + + true + } + + #[external] + fn verify_user(ref self: ContractState, user_id: u256) -> bool { + let caller = get_caller_address(); + assert(caller == self.admin.read(), "Admin only"); + + let user_address = self.user_ids.read(user_id); + let mut profile = self.users.read(user_address); + profile.verified = true; + self.users.write(user_address, profile); + + self + .emit( + Event::UserVerified(UserVerified { user_id, timestamp: get_block_timestamp() }), + ); + + true + } + + #[external] + fn is_user_active(self: @ContractState, user_id: u256) -> bool { + let user_address = self.user_ids.read(user_id); + self.active_users.read(user_address) + } + + #[external] + fn get_admin(self: @ContractState) -> ContractAddress { + self.admin.read() } } } diff --git a/src/interfaces/IUserRegistry.cairo b/src/interfaces/IUserRegistry.cairo index 6965566..a343d1b 100644 --- a/src/interfaces/IUserRegistry.cairo +++ b/src/interfaces/IUserRegistry.cairo @@ -1,8 +1,37 @@ #[starknet::interface] -trait IUserRegistry { - fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); - fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252); - fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool); - fn deactivate_user(self: @ContractState, user: ContractAddress); -} +trait IUserRegistry { + // Basic user management (existing functions) + fn create_user(self: @TContractState, user: ContractAddress, name: felt252, email: felt252); + + fn update_user(self: @TContractState, user: ContractAddress, name: felt252, email: felt252); + + fn get_user(self: @TContractState, user: ContractAddress) -> (felt252, felt252, bool); + + fn deactivate_user(self: @TContractState, user: ContractAddress); + + // New functions for enhanced profile management + fn create_user_profile( + ref self: TContractState, username: felt252, role: Role, rank: Rank, metadata: felt252, + ) -> u256; + + fn retrieve_user_profile(self: @TContractState, user_id: u256) -> User; + fn retrieve_user_profile_by_address(self: @TContractState, address: ContractAddress) -> User; + + fn update_user_profile( + ref self: TContractState, + user_id: u256, + username: felt252, + role: Role, + rank: Rank, + metadata: felt252, + ) -> bool; + + fn reactivate_user_profile(ref self: TContractState, user_id: u256) -> bool; + + fn is_profile_active(self: @TContractState, user_id: u256) -> bool; + + fn is_verified(self: @TContractState, user_id: u256) -> bool; + + fn get_admin(self: @TContractState) -> ContractAddress; +} From 6260863a91753379c3c2491c7255ccdb3befdef6 Mon Sep 17 00:00:00 2001 From: 3th-Enjay Date: Sun, 30 Mar 2025 14:14:55 +0100 Subject: [PATCH 8/8] Refactor IUserRegistry interface to enhance user profile management functions --- src/chainlib/ChainLib.cairo | 419 +++++++++++++++++++++++++++++ src/interfaces/IChainLib.cairo | 37 +++ src/interfaces/IUserRegistry.cairo | 36 --- 3 files changed, 456 insertions(+), 36 deletions(-) diff --git a/src/chainlib/ChainLib.cairo b/src/chainlib/ChainLib.cairo index 7433d3f..41e1d11 100644 --- a/src/chainlib/ChainLib.cairo +++ b/src/chainlib/ChainLib.cairo @@ -186,3 +186,422 @@ pub mod ChainLib { } } } +#[starknet::contract] +mod UserRegistry { + use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; + use super::IUserRegistry; + + #[derive(Drop, Serde, Clone, Copy, PartialEq)] + struct UserProfile { + name: felt252, + email: felt252, + is_active: bool, + role: Role, + rank: Rank, + metadata: felt252, + created_at: u64, + updated_at: u64, + verified: bool, + } + + // We need to expose this for the interface + #[derive(Drop, Serde, Clone, Copy, PartialEq)] + struct User { + name: felt252, + email: felt252, + is_active: bool, + role: Role, + rank: Rank, + metadata: felt252, + verified: bool, + } + + #[derive(Drop, Serde, Copy, PartialEq)] + enum Role { + READER, + WRITER, + ADMIN, + } + + #[derive(Drop, Serde, Copy, PartialEq)] + enum Rank { + LEVEL1, + LEVEL2, + LEVEL3, + } + + struct Storage { + users: LegacyMap, + user_ids: LegacyMap, + next_user_id: u256, + admin: ContractAddress, + active_users: LegacyMap, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + UserCreated: UserCreated, + UserUpdated: UserUpdated, + UserDeactivated: UserDeactivated, + UserReactivated: UserReactivated, + UserVerified: UserVerified, + } + + #[derive(Drop, starknet::Event)] + struct UserCreated { + user_id: u256, + address: ContractAddress, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct UserUpdated { + user_id: u256, + fields: felt252, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct UserDeactivated { + user_id: u256, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct UserReactivated { + user_id: u256, + timestamp: u64, + } + + #[derive(Drop, starknet::Event)] + struct UserVerified { + user_id: u256, + timestamp: u64, + } + + #[constructor] + fn constructor(ref self: ContractState, admin: ContractAddress) { + self.admin.write(admin); + self.next_user_id.write(1); + } + + // Helper function to convert UserProfile to User + fn profile_to_user(profile: UserProfile) -> User { + User { + name: profile.name, + email: profile.email, + is_active: profile.is_active, + role: profile.role, + rank: profile.rank, + metadata: profile.metadata, + verified: profile.verified, + } + } + + #[abi(embed_v0)] + impl UserRegistryImpl of IUserRegistry { + #[external] + fn create_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { + let mut state = self.get_contract_state(); + assert(!state.users.contains(user), "User already exists"); + + let user_id = state.next_user_id.read(); + let new_profile = UserProfile { + name, + email, + is_active: true, + role: Role::READER, // Default role + rank: Rank::LEVEL1, // Default rank + metadata: 0, // Default metadata + created_at: get_block_timestamp(), + updated_at: get_block_timestamp(), + verified: false, + }; + + state.users.write(user, new_profile); + state.user_ids.write(user_id, user); + state.active_users.write(user, true); + state.next_user_id.write(user_id + 1); + + state + .emit( + Event::UserCreated( + UserCreated { user_id, address: user, timestamp: get_block_timestamp() }, + ), + ); + } + #[external] + fn update_user(self: @ContractState, user: ContractAddress, name: felt252, email: felt252) { + let mut state = self.get_contract_state(); + let caller = get_caller_address(); + let admin = state.admin.read(); + + assert(caller == user || caller == admin, "Unauthorized"); + assert(state.users.contains(user), "User not found"); + assert(state.active_users.read(user), "User inactive"); + + let mut profile = state.users.read(user); + let mut fields_updated = 0; + let mut changes_made = false; + + if name != 0 && name != profile.name { + profile.name = name; + fields_updated = if fields_updated == 0 { + 'name' + } else { + 'multiple' + }; + changes_made = true; + } + + if email != 0 && email != profile.email { + profile.email = email; + fields_updated = if fields_updated == 0 { + 'email' + } else { + 'multiple' + }; + changes_made = true; + } + + if changes_made { + profile.updated_at = get_block_timestamp(); + state.users.write(user, profile); + + // Find user_id for the event + let mut user_id = 0; + let next_id = state.next_user_id.read(); + let mut i = 1; + while i < next_id { + if state.user_ids.read(i) == user { + user_id = i; + break; + } + i += 1; + } + + state + .emit( + Event::UserUpdated( + UserUpdated { + user_id, fields: fields_updated, timestamp: get_block_timestamp(), + }, + ), + ); + } + } + + #[external] + fn get_user(self: @ContractState, user: ContractAddress) -> (felt252, felt252, bool) { + assert(self.users.contains(user), "User not found"); + let profile = self.users.read(user); + (profile.name, profile.email, profile.is_active) + } + + #[external] + fn deactivate_user(self: @ContractState, user: ContractAddress) { + let mut state = self.get_contract_state(); + let caller = get_caller_address(); + let admin = state.admin.read(); + + assert(caller == user, caller == admin, "Unauthorized"); + assert(state.users.contains(user), "User not found"); + assert(state.active_users.read(user), "Already inactive"); + + state.active_users.write(user, false); + let mut profile = state.users.read(user); + profile.is_active = false; + state.users.write(user, profile); + + // Find user_id for the event + let mut user_id = 0; + let next_id = state.next_user_id.read(); + let mut i = 1; + while i < next_id { + if state.user_ids.read(i) == user { + user_id = i; + break; + } + i += 1; + } + + state + .emit( + Event::UserDeactivated( + UserDeactivated { user_id, timestamp: get_block_timestamp() }, + ), + ); + } + + // New functions for enhanced profile management + #[external] + fn create_user_profile( + ref self: ContractState, username: felt252, role: Role, rank: Rank, metadata: felt252, + ) -> u256 { + let caller = get_caller_address(); + assert(!self.users.contains(caller), "User already exists"); + let user_id = self.next_user_id.read(); + let new_profile = UserProfile { + name: username, + email: 0, // Empty email + is_active: true, + role, + rank, + metadata, + created_at: get_block_timestamp(), + updated_at: get_block_timestamp(), + verified: false, + }; + + self.users.write(caller, new_profile); + self.user_ids.write(user_id, caller); + self.active_users.write(caller, true); + self.next_user_id.write(user_id + 1); + + self + .emit( + Event::UserCreated( + UserCreated { user_id, address: caller, timestamp: get_block_timestamp() }, + ), + ); + + user_id + } + + #[external] + fn retrieve_user_profile(self: @ContractState, user_id: u256) -> User { + let user_address = self.user_ids.read(user_id); + assert(self.users.contains(user_address), "User not found"); + let profile = self.users.read(user_address); + profile_to_user(profile) + } + + #[external] + fn retrieve_user_profile_by_address( + self: @ContractState, address: ContractAddress, + ) -> User { + assert(self.users.contains(address), "User not found"); + let profile = self.users.read(address); + profile_to_user(profile) + } + + #[external] + fn update_user_profile( + ref self: ContractState, + user_id: u256, + username: felt252, + role: Role, + rank: Rank, + metadata: felt252, + ) -> bool { + let caller = get_caller_address(); + let user_address = self.user_ids.read(user_id); + let mut profile = self.users.read(user_address); + let admin = self.admin.read(); + + assert(caller == user_address || caller == admin, "Unauthorized"); + assert(self.active_users.read(user_address), "User inactive"); + + let mut fields_updated = 0; + let mut changes_made = false; + + if username != 0 && username != profile.name { + profile.name = username; + fields_updated = if fields_updated == 0 { + 'name' + } else { + 'multiple' + }; + changes_made = true; + } + + if role != profile.role { + profile.role = role; + fields_updated = if fields_updated == 0 { + 'role' + } else { + 'multiple' + }; + changes_made = true; + } + + if rank != profile.rank { + profile.rank = rank; + fields_updated = if fields_updated == 0 { + 'rank' + } else { + 'multiple' + }; + changes_made = true; + } + + if metadata != 0 && metadata != profile.metadata { + profile.metadata = metadata; + fields_updated = if fields_updated == 0 { + 'metadata' + } else { + 'multiple' + }; + changes_made = true; + } + + if changes_made { + profile.updated_at = get_block_timestamp(); + self.users.write(user_address, profile); + self + .emit( + Event::UserUpdated( + UserUpdated { + user_id, fields: fields_updated, timestamp: get_block_timestamp(), + }, + ), + ); + } + + changes_made + } + + #[external] + fn reactivate_user_profile(ref self: ContractState, user_id: u256) -> bool { + let caller = get_caller_address(); + let user_address = self.user_ids.read(user_id); + let admin = self.admin.read(); + + assert(caller == user_address || caller == admin, "Unauthorized"); + assert(!self.active_users.read(user_address), "Already active"); + + self.active_users.write(user_address, true); + let mut profile = self.users.read(user_address); + profile.is_active = true; + self.users.write(user_address, profile); + + self + .emit( + Event::UserReactivated( + UserReactivated { user_id, timestamp: get_block_timestamp() }, + ), + ); + + true + } + + #[external] + fn is_profile_active(self: @ContractState, user_id: u256) -> bool { + let user_address = self.user_ids.read(user_id); + self.active_users.read(user_address) + } + + #[external] + fn is_verified(self: @ContractState, user_id: u256) -> bool { + let user_address = self.user_ids.read(user_id); + let profile = self.users.read(user_address); + profile.verified + } + + #[external] + fn get_admin(self: @ContractState) -> ContractAddress { + self.admin.read() + } + } +} diff --git a/src/interfaces/IChainLib.cairo b/src/interfaces/IChainLib.cairo index 8709a50..d461670 100644 --- a/src/interfaces/IChainLib.cairo +++ b/src/interfaces/IChainLib.cairo @@ -22,3 +22,40 @@ pub trait IChainLib { fn is_verified(ref self: TContractState, user_id: u256) -> bool; } +#[starknet::interface] +trait IUserRegistry { + // Basic user management (existing functions) + fn create_user(self: @TContractState, user: ContractAddress, name: felt252, email: felt252); + + fn update_user(self: @TContractState, user: ContractAddress, name: felt252, email: felt252); + + fn get_user(self: @TContractState, user: ContractAddress) -> (felt252, felt252, bool); + + fn deactivate_user(self: @TContractState, user: ContractAddress); + + // New functions for enhanced profile management + fn create_user_profile( + ref self: TContractState, username: felt252, role: Role, rank: Rank, metadata: felt252, + ) -> u256; + + fn retrieve_user_profile(self: @TContractState, user_id: u256) -> User; + + fn retrieve_user_profile_by_address(self: @TContractState, address: ContractAddress) -> User; + + fn update_user_profile( + ref self: TContractState, + user_id: u256, + username: felt252, + role: Role, + rank: Rank, + metadata: felt252, + ) -> bool; + + fn reactivate_user_profile(ref self: TContractState, user_id: u256) -> bool; + + fn is_profile_active(self: @TContractState, user_id: u256) -> bool; + + fn is_verified(self: @TContractState, user_id: u256) -> bool; + + fn get_admin(self: @TContractState) -> ContractAddress; +} diff --git a/src/interfaces/IUserRegistry.cairo b/src/interfaces/IUserRegistry.cairo index a343d1b..8b13789 100644 --- a/src/interfaces/IUserRegistry.cairo +++ b/src/interfaces/IUserRegistry.cairo @@ -1,37 +1 @@ -#[starknet::interface] -trait IUserRegistry { - // Basic user management (existing functions) - fn create_user(self: @TContractState, user: ContractAddress, name: felt252, email: felt252); - fn update_user(self: @TContractState, user: ContractAddress, name: felt252, email: felt252); - - fn get_user(self: @TContractState, user: ContractAddress) -> (felt252, felt252, bool); - - fn deactivate_user(self: @TContractState, user: ContractAddress); - - // New functions for enhanced profile management - fn create_user_profile( - ref self: TContractState, username: felt252, role: Role, rank: Rank, metadata: felt252, - ) -> u256; - - fn retrieve_user_profile(self: @TContractState, user_id: u256) -> User; - - fn retrieve_user_profile_by_address(self: @TContractState, address: ContractAddress) -> User; - - fn update_user_profile( - ref self: TContractState, - user_id: u256, - username: felt252, - role: Role, - rank: Rank, - metadata: felt252, - ) -> bool; - - fn reactivate_user_profile(ref self: TContractState, user_id: u256) -> bool; - - fn is_profile_active(self: @TContractState, user_id: u256) -> bool; - - fn is_verified(self: @TContractState, user_id: u256) -> bool; - - fn get_admin(self: @TContractState) -> ContractAddress; -}