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
29 changes: 29 additions & 0 deletions src/base/types.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

136 changes: 117 additions & 19 deletions src/chainlib/ChainLib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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<u256, TokenBoundAccount>,
accountsaddr: Map<ContractAddress, TokenBoundAccount>,
next_course_id: u256,
nuum: Map<u8, u8>,
user_id: u256,
users: Map<u256, User>,
}


#[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<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,
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
Expand All @@ -83,8 +101,88 @@ 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 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
}
}
}
12 changes: 10 additions & 2 deletions 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};
use crate::base::types::{TokenBoundAccount, User, Role, Rank};

#[starknet::interface]
pub trait IChainLib<TContractState> {
Expand All @@ -12,5 +12,13 @@ pub trait IChainLib<TContractState> {
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;
fn is_verified(ref self: TContractState, user_id: u256) -> bool;
}

99 changes: 89 additions & 10 deletions tests/test_ChainLib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,55 +7,134 @@ 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
let user_name: felt252 = 'John';
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.is_verified(account_id);
assert(verified_user, '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 user 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');
}