From e50eaa461b9e2d5abcbd2b40aa28c02987f2c5f6 Mon Sep 17 00:00:00 2001 From: Ajidokwu Sabo Date: Thu, 27 Mar 2025 01:47:15 +0100 Subject: [PATCH 1/3] feat: created register, reterieve and verify user closes #1 --- src/base/types.cairo | 29 ++++++++ src/chainlib/ChainLib.cairo | 130 ++++++++++++++++++++++++++++----- src/interfaces/IChainLib.cairo | 11 ++- tests/test_ChainLib.cairo | 99 ++++++++++++++++++++++--- 4 files changed, 238 insertions(+), 31 deletions(-) diff --git a/src/base/types.cairo b/src/base/types.cairo index eea1e40..163d40f 100644 --- a/src/base/types.cairo +++ b/src/base/types.cairo @@ -9,3 +9,32 @@ pub struct TokenBoundAccount { pub created_at: u64, pub updated_at: u64, } +#[derive(Drop, Serde, starknet::Store)] +pub struct User { + pub id: u256, + pub username: felt252, + pub wallet_address: ContractAddress, + pub role: Role, + pub rank: Rank, + pub verified: bool, + pub metadata: felt252, +} + + +#[derive(Drop, Serde, starknet::Store, Clone, PartialEq)] +pub enum Role { + #[default] + NIL, + READER, + WRITER, +} + + +#[derive(Drop, Serde, starknet::Store, Clone, PartialEq)] +pub enum Rank { + #[default] + BEGINNER, + INTERMEDIATE, + EXPERT, +} + diff --git a/src/chainlib/ChainLib.cairo b/src/chainlib/ChainLib.cairo index a2d6f60..4cf896e 100644 --- a/src/chainlib/ChainLib.cairo +++ b/src/chainlib/ChainLib.cairo @@ -6,73 +6,91 @@ pub mod ChainLib { }; use starknet::{ContractAddress, get_block_timestamp, get_caller_address}; use crate::interfaces::IChainLib::IChainLib; - use crate::base::types::{TokenBoundAccount}; + use crate::base::types::{TokenBoundAccount, User, Role, Rank}; #[storage] struct Storage { // Contract addresses for component management - deployed: bool, + admin: ContractAddress, current_account_id: u256, accounts: Map, accountsaddr: Map, next_course_id: u256, - nuum: Map, + user_id: u256, + users: Map, } #[constructor] - fn constructor(ref self: ContractState) { + fn constructor(ref self: ContractState, admin: ContractAddress) { // Store the values in contract state - self.deployed.write(true); + self.admin.write(admin); } #[event] #[derive(Drop, starknet::Event)] pub enum Event { - TokenBountAccountcreated: TokenBountAccountcreated, + TokenBoundAccountCreated: TokenBoundAccountCreated, + UserCreated: UserCreated, } #[derive(Drop, starknet::Event)] - pub struct TokenBountAccountcreated { + pub struct TokenBoundAccountCreated { + pub id: u256, + } + + #[derive(Drop, starknet::Event)] + pub struct UserCreated { pub id: u256, } #[abi(embed_v0)] impl ChainLibNetImpl of IChainLib { + /// @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, + ref self: ContractState, user_name: felt252, init_param1: felt252, init_param2: felt252 ) -> u256 { - // Validate input parameters. + // Ensure that the username is not empty. assert!(user_name != 0, "User name cannot be empty"); + + // Validate initialization parameters. assert!(init_param1 != 0, "Initialization parameter 1 cannot be empty"); // Retrieve the current account ID before incrementing. let account_id = self.current_account_id.read(); - // Create a new token bound account struct. + // Create a new token-bound account with the provided parameters. let new_token_bound_account = TokenBoundAccount { id: account_id, - address: get_caller_address(), + address: get_caller_address(), // Assign the caller's address. user_name: user_name, init_param1: init_param1, init_param2: init_param2, - created_at: get_block_timestamp(), - updated_at: get_block_timestamp(), + created_at: get_block_timestamp(), // Capture the creation timestamp. + updated_at: get_block_timestamp() // Set initial updated timestamp. }; - // Store the new account in the accounts map. + // Store the new account in the accounts mapping. self.accounts.write(account_id, new_token_bound_account); - // Increment the account ID counter after using the current value. + // Increment the account ID counter for the next registration. self.current_account_id.write(account_id + 1); - // Emit an event to signal the creation of the token bound account. - self.emit(TokenBountAccountcreated { id: account_id }); + // Emit an event to notify about the new token-bound account creation. + self.emit(TokenBoundAccountCreated { id: account_id }); + // Return the assigned account ID. account_id } + fn get_token_bound_account(ref self: ContractState, id: u256) -> TokenBoundAccount { let token_bound_account = self.accounts.read(id); token_bound_account @@ -83,8 +101,82 @@ pub mod ChainLib { let token_bound_account = self.accountsaddr.read(address); token_bound_account } - fn test_deployment(ref self: ContractState) -> bool { - self.deployed.read() + + + /// @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 { + // Ensure that the username is not empty. + assert!(username != 0, "User name cannot be empty"); + + // Retrieve the current user ID before incrementing. + let user_id = self.user_id.read(); + + // Create a new user profile with provided details. + let new_user = User { + id: user_id, + username: username, + wallet_address: get_caller_address(), // Assign the caller's address. + role: role, + rank: rank, + verified: false, // Default verification status is false. + metadata: metadata + }; + + // Store the new user in the users mapping. + self.users.write(user_id, new_user); + + // Increment the user ID counter for the next registration. + self.current_account_id.write(user_id + 1); + + // Emit an event to notify about the new user registration. + self.emit(UserCreated { id: user_id }); + + // Return the assigned user ID. + user_id + } + + + /// @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. + assert((self.admin.read() == caller), 'Only admin can verify users'); + let mut user = self.users.read(user_id); + user.verified = true; + 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); + + // Return the retrieved user profile. + user + } + + fn getAdmin(self: @ContractState) -> ContractAddress { + let admin = self.admin.read(); + admin } } } diff --git a/src/interfaces/IChainLib.cairo b/src/interfaces/IChainLib.cairo index c095a9a..c885f52 100644 --- a/src/interfaces/IChainLib.cairo +++ b/src/interfaces/IChainLib.cairo @@ -1,5 +1,5 @@ use starknet::ContractAddress; -use crate::base::types::{TokenBoundAccount}; +use crate::base::types::{TokenBoundAccount, User, Role, Rank}; #[starknet::interface] pub trait IChainLib { @@ -12,5 +12,12 @@ pub trait IChainLib { fn get_token_bound_account_by_owner( ref self: TContractState, address: ContractAddress ) -> TokenBoundAccount; - fn test_deployment(ref self: TContractState) -> bool; + + fn register_user( + 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; + fn getAdmin(self: @TContractState) -> ContractAddress; } + diff --git a/tests/test_ChainLib.cairo b/tests/test_ChainLib.cairo index 24a1daa..b29dc86 100644 --- a/tests/test_ChainLib.cairo +++ b/tests/test_ChainLib.cairo @@ -7,39 +7,41 @@ use starknet::ContractAddress; use starknet::class_hash::ClassHash; use starknet::contract_address::contract_address_const; use starknet::testing::{set_caller_address, set_contract_address}; +use chain_lib::base::types::{Role, Rank}; -fn setup() -> ContractAddress { +fn setup() -> (ContractAddress, ContractAddress) { let declare_result = declare("ChainLib"); assert(declare_result.is_ok(), 'Contract declaration failed'); + let admin_address: ContractAddress = contract_address_const::<'admin'>(); let contract_class = declare_result.unwrap().contract_class(); - let mut calldata = array![]; + let mut calldata = array![admin_address.into()]; let deploy_result = contract_class.deploy(@calldata); assert(deploy_result.is_ok(), 'Contract deployment failed'); let (contract_address, _) = deploy_result.unwrap(); - (contract_address) + (contract_address, admin_address) } #[test] fn test_initial_data() { - let contract_address = setup(); + let (contract_address, admin_address) = setup(); let dispatcher = IChainLibDispatcher { contract_address }; // Ensure dispatcher methods exist - let deployed = dispatcher.test_deployment(); + let admin = dispatcher.getAdmin(); - assert(deployed == true, 'deployment failed'); + assert(admin == admin_address, 'deployment failed'); } #[test] fn test_create_token_bount_account() { - let contract_address = setup(); + let (contract_address, _) = setup(); let dispatcher = IChainLibDispatcher { contract_address }; // Test input values @@ -47,15 +49,92 @@ fn test_create_token_bount_account() { let init_param1: felt252 = 'John@yahoo.com'; let init_param2: felt252 = 'john is a boy'; - // Call create_claim + // Call account let account_id = dispatcher.create_token_account(user_name, init_param1, init_param2); - // Validate that the claim ID is correctly incremented + // Validate that the account ID is correctly incremented assert(account_id == 0, 'account_id should start from 0'); - // Retrieve the claim to verify it was stored correctly + // Retrieve the account to verify it was stored correctly let token_bound_account = dispatcher.get_token_bound_account(account_id); assert(token_bound_account.user_name == user_name, 'namemismatch'); assert(token_bound_account.init_param1 == init_param1, 'init_param1 mismatch'); assert(token_bound_account.init_param2 == init_param2, 'init_param2 mismatch'); } + + +#[test] +fn test_create_user() { + let (contract_address, _) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Call create_user + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + + // Validate that the claim ID is correctly incremented + assert(account_id == 0, 'account_id should start from 0'); + + // Retrieve the user to verify it was stored correctly + let user = dispatcher.retrieve_user_profile(account_id); + assert(user.username == username, 'username mismatch'); + assert(user.role == role, 'role mismatch'); + assert(user.rank == rank, 'rank mismatch'); + assert(user.metadata == metadata, 'metadata mismatch'); + assert(!user.verified, 'already verified'); +} + +#[test] +fn test_verify_user() { + let (contract_address, admin_address) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Call register user + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + // Retrieve the user to verify it was stored correctly + let user = dispatcher.retrieve_user_profile(account_id); + assert(!user.verified, 'already verified'); + + cheat_caller_address(contract_address, admin_address, CheatSpan::Indefinite); + let verify = dispatcher.verify_user(user.id); + assert(verify, 'Verification Falied'); + + let verified_user = dispatcher.retrieve_user_profile(account_id); + assert(verified_user.verified, 'Not Verified'); +} + +#[test] +#[should_panic(expected: 'Only admin can verify users')] +fn test_verify_user_not_admin() { + let (contract_address, _) = setup(); + let dispatcher = IChainLibDispatcher { contract_address }; + + // Test input values + let username: felt252 = 'John'; + let role: Role = Role::READER; + let rank: Rank = Rank::BEGINNER; + let metadata: felt252 = 'john is a boy'; + + // Call register + let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); + // Retrieve the user to verify it was stored correctly + let user = dispatcher.retrieve_user_profile(account_id); + assert(!user.verified, 'already verified'); + + let verify = dispatcher.verify_user(user.id); + assert(verify, 'Verification Falied'); + + let verified_user = dispatcher.retrieve_user_profile(account_id); + assert(verified_user.verified, 'Not Verified'); +} From 4b7c1272a1f08573e5c5c4f7fc41f56ba193453d Mon Sep 17 00:00:00 2001 From: Ajidokwu Sabo Date: Thu, 27 Mar 2025 01:52:14 +0100 Subject: [PATCH 2/3] feat: created register, reterieve and verify user closes #1 --- src/chainlib/ChainLib.cairo | 6 ++++++ src/interfaces/IChainLib.cairo | 1 + tests/test_ChainLib.cairo | 4 ++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/chainlib/ChainLib.cairo b/src/chainlib/ChainLib.cairo index 4cf896e..fa16763 100644 --- a/src/chainlib/ChainLib.cairo +++ b/src/chainlib/ChainLib.cairo @@ -174,6 +174,12 @@ pub mod ChainLib { user } + fn is_verified(ref self: ContractState, user_id: u256) -> bool { + let mut user = self.users.read(user_id); + user.verified + } + + fn getAdmin(self: @ContractState) -> ContractAddress { let admin = self.admin.read(); admin diff --git a/src/interfaces/IChainLib.cairo b/src/interfaces/IChainLib.cairo index c885f52..4f90038 100644 --- a/src/interfaces/IChainLib.cairo +++ b/src/interfaces/IChainLib.cairo @@ -19,5 +19,6 @@ pub trait IChainLib { fn verify_user(ref self: TContractState, user_id: u256) -> bool; 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; } diff --git a/tests/test_ChainLib.cairo b/tests/test_ChainLib.cairo index b29dc86..56359b6 100644 --- a/tests/test_ChainLib.cairo +++ b/tests/test_ChainLib.cairo @@ -110,8 +110,8 @@ fn test_verify_user() { let verify = dispatcher.verify_user(user.id); assert(verify, 'Verification Falied'); - let verified_user = dispatcher.retrieve_user_profile(account_id); - assert(verified_user.verified, 'Not Verified'); + let verified_user = dispatcher.is_verified(account_id); + assert(verified_user, 'Not Verified'); } #[test] From 96596f8537fcf630286fe74793869f31a21f4324 Mon Sep 17 00:00:00 2001 From: Ajidokwu Sabo Date: Thu, 27 Mar 2025 12:39:03 +0100 Subject: [PATCH 3/3] feat: created register, reterieve and verify user closes #14 --- tests/test_ChainLib.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ChainLib.cairo b/tests/test_ChainLib.cairo index 56359b6..5fbbb1f 100644 --- a/tests/test_ChainLib.cairo +++ b/tests/test_ChainLib.cairo @@ -128,7 +128,7 @@ fn test_verify_user_not_admin() { // Call register let account_id = dispatcher.register_user(username, role.clone(), rank.clone(), metadata); - // Retrieve the user to verify it was stored correctly + // Retrieve the user to verify user was stored correctly let user = dispatcher.retrieve_user_profile(account_id); assert(!user.verified, 'already verified');