From 0ff407f4c35332267805ee463874e92429d416c4 Mon Sep 17 00:00:00 2001 From: ryzen_xp Date: Thu, 22 Jan 2026 19:22:03 +0530 Subject: [PATCH 1/4] [Feat]:: Implemented "Ban Expert" Logic --- .../src/contract.rs | 55 ++++-- .../identity-registry-contract/src/error.rs | 2 +- .../identity-registry-contract/src/events.rs | 19 +- .../identity-registry-contract/src/lib.rs | 16 +- .../identity-registry-contract/src/storage.rs | 30 ++- .../identity-registry-contract/src/test.rs | 185 +++++++++++++++++- .../identity-registry-contract/src/types.rs | 4 +- 7 files changed, 258 insertions(+), 53 deletions(-) diff --git a/contracts/identity-registry-contract/src/contract.rs b/contracts/identity-registry-contract/src/contract.rs index 31249f4..4a00d4d 100644 --- a/contracts/identity-registry-contract/src/contract.rs +++ b/contracts/identity-registry-contract/src/contract.rs @@ -1,8 +1,8 @@ -use soroban_sdk::{Address, Env}; -use crate::storage; use crate::error::RegistryError; -use crate::types::ExpertStatus; use crate::events; +use crate::storage; +use crate::types::ExpertStatus; +use soroban_sdk::{Address, Env}; pub fn initialize_registry(env: &Env, admin: &Address) -> Result<(), RegistryError> { if storage::has_admin(env) { @@ -16,18 +16,51 @@ pub fn initialize_registry(env: &Env, admin: &Address) -> Result<(), RegistryErr pub fn verify_expert(env: &Env, expert: &Address) -> Result<(), RegistryError> { let admin = storage::get_admin(env).ok_or(RegistryError::NotInitialized)?; - + admin.require_auth(); - + let current_status = storage::get_expert_status(env, expert); - + if current_status == ExpertStatus::Verified { return Err(RegistryError::AlreadyVerified); } - + storage::set_expert_record(env, expert, ExpertStatus::Verified); - - events::emit_status_change(env, expert.clone(), current_status, ExpertStatus::Verified, admin); - + + events::emit_status_change( + env, + expert.clone(), + current_status, + ExpertStatus::Verified, + admin, + ); + Ok(()) -} \ No newline at end of file +} + +pub fn ban_expert(env: &Env, expert: &Address) -> Result<(), RegistryError> { + let admin = storage::get_admin(env).ok_or(RegistryError::NotInitialized)?; + admin.require_auth(); + + let current_status = storage::get_expert_status(env, expert); + + if current_status == ExpertStatus::Banned { + return Err(RegistryError::AlreadyBanned); + } + + storage::set_expert_record(env, expert, ExpertStatus::Banned); + + events::emit_status_change( + env, + expert.clone(), + current_status, + ExpertStatus::Banned, + admin, + ); + + Ok(()) +} + +pub fn get_expert_status(env: &Env, expert: &Address) -> ExpertStatus { + storage::get_expert_status(env, expert) +} diff --git a/contracts/identity-registry-contract/src/error.rs b/contracts/identity-registry-contract/src/error.rs index 1893aaa..4b7a0eb 100644 --- a/contracts/identity-registry-contract/src/error.rs +++ b/contracts/identity-registry-contract/src/error.rs @@ -15,4 +15,4 @@ pub enum RegistryError { ExpertNotFound = 4, AlreadyVerified = 5, AlreadyBanned = 6, -} \ No newline at end of file +} diff --git a/contracts/identity-registry-contract/src/events.rs b/contracts/identity-registry-contract/src/events.rs index 7d4065d..9c25ac2 100644 --- a/contracts/identity-registry-contract/src/events.rs +++ b/contracts/identity-registry-contract/src/events.rs @@ -1,5 +1,5 @@ -use soroban_sdk::{contracttype, Address, Env, Symbol}; use crate::types::ExpertStatus; +use soroban_sdk::{contracttype, Address, Env, Symbol}; // The Event Data Structure #[contracttype] @@ -13,11 +13,11 @@ pub struct ExpertStatusChangedEvent { // Helper function to emit the event pub fn emit_status_change( - env: &Env, - expert: Address, - old_status: ExpertStatus, - new_status: ExpertStatus, - admin: Address + env: &Env, + expert: Address, + old_status: ExpertStatus, + new_status: ExpertStatus, + admin: Address, ) { let event = ExpertStatusChangedEvent { expert, @@ -25,7 +25,8 @@ pub fn emit_status_change( new_status, admin, }; - + // We publish with the topic "status_change" so indexers can find it easily - env.events().publish((Symbol::new(env, "status_change"),), event); -} \ No newline at end of file + env.events() + .publish((Symbol::new(env, "status_change"),), event); +} diff --git a/contracts/identity-registry-contract/src/lib.rs b/contracts/identity-registry-contract/src/lib.rs index 6e5de09..db01c6a 100644 --- a/contracts/identity-registry-contract/src/lib.rs +++ b/contracts/identity-registry-contract/src/lib.rs @@ -4,12 +4,13 @@ mod contract; mod error; mod events; mod storage; -mod types; #[cfg(test)] mod test; +mod types; -use soroban_sdk::{contract, contractimpl, Address, Env}; use crate::error::RegistryError; +use crate::types::ExpertStatus; +use soroban_sdk::{contract, contractimpl, Address, Env}; #[contract] pub struct IdentityRegistryContract; @@ -25,4 +26,13 @@ impl IdentityRegistryContract { pub fn add_expert(env: Env, expert: Address) -> Result<(), RegistryError> { contract::verify_expert(&env, &expert) } -} \ No newline at end of file + + /// Ban an expert and revoke their verification status (Admin only) + pub fn ban_expert(env: Env, expert: Address) -> Result<(), RegistryError> { + contract::ban_expert(&env, &expert) + } + + pub fn get_status(env: Env, expert: Address) -> ExpertStatus { + contract::get_expert_status(&env, &expert) + } +} diff --git a/contracts/identity-registry-contract/src/storage.rs b/contracts/identity-registry-contract/src/storage.rs index b681d80..c8746af 100644 --- a/contracts/identity-registry-contract/src/storage.rs +++ b/contracts/identity-registry-contract/src/storage.rs @@ -1,12 +1,12 @@ +use crate::types::{ExpertRecord, ExpertStatus}; use soroban_sdk::{contracttype, Address, Env}; -use crate::types::{ExpertStatus, ExpertRecord}; // 1. Data Keys #[contracttype] #[derive(Clone)] pub enum DataKey { - Admin, - Expert(Address), + Admin, + Expert(Address), } // Constants for TTL (Time To Live) @@ -39,7 +39,7 @@ pub fn get_admin(env: &Env) -> Option
{ pub fn set_expert_record(env: &Env, expert: &Address, status: ExpertStatus) { let key = DataKey::Expert(expert.clone()); - + let record = ExpertRecord { status, updated_at: env.ledger().timestamp(), @@ -49,26 +49,22 @@ pub fn set_expert_record(env: &Env, expert: &Address, status: ExpertStatus) { env.storage().persistent().set(&key, &record); // 2. Extend the TTL - // This tells the network: "If this data is going to die in less than 2 months, + // This tells the network: "If this data is going to die in less than 2 months, // extend its life to 1 full year from now." - env.storage().persistent().extend_ttl( - &key, - LEDGERS_THRESHOLD, - LEDGERS_EXTEND_TO - ); + env.storage() + .persistent() + .extend_ttl(&key, LEDGERS_THRESHOLD, LEDGERS_EXTEND_TO); } pub fn get_expert_record(env: &Env, expert: &Address) -> ExpertRecord { let key = DataKey::Expert(expert.clone()); - + // We also bump TTL on reads // If an expert is being checked frequently, they should stay alive. if env.storage().persistent().has(&key) { - env.storage().persistent().extend_ttl( - &key, - LEDGERS_THRESHOLD, - LEDGERS_EXTEND_TO - ); + env.storage() + .persistent() + .extend_ttl(&key, LEDGERS_THRESHOLD, LEDGERS_EXTEND_TO); } env.storage() @@ -82,4 +78,4 @@ pub fn get_expert_record(env: &Env, expert: &Address) -> ExpertRecord { pub fn get_expert_status(env: &Env, expert: &Address) -> ExpertStatus { get_expert_record(env, expert).status -} \ No newline at end of file +} diff --git a/contracts/identity-registry-contract/src/test.rs b/contracts/identity-registry-contract/src/test.rs index b76bacf..9bf973f 100644 --- a/contracts/identity-registry-contract/src/test.rs +++ b/contracts/identity-registry-contract/src/test.rs @@ -2,14 +2,16 @@ extern crate std; +use crate::error::RegistryError; +use crate::types::ExpertStatus; use crate::{IdentityRegistryContract, IdentityRegistryContractClient}; -use soroban_sdk::{Env, testutils::Address as _, Symbol, Address, IntoVal, TryIntoVal}; use soroban_sdk::testutils::{AuthorizedFunction, AuthorizedInvocation, Events}; +use soroban_sdk::{testutils::Address as _, Address, Env, IntoVal, Symbol, TryIntoVal}; #[test] fn test_initialization() { let env = Env::default(); - let contract_id = env.register_contract(None, IdentityRegistryContract); + let contract_id = env.register(IdentityRegistryContract, ()); let client = IdentityRegistryContractClient::new(&env, &contract_id); // 1. Generate a fake admin address @@ -28,8 +30,8 @@ fn test_initialization() { fn test_add_expert() { let env = Env::default(); env.mock_all_auths(); - - let contract_id = env.register_contract(None, IdentityRegistryContract); + + let contract_id = env.register(IdentityRegistryContract, ()); let client = IdentityRegistryContractClient::new(&env, &contract_id); let admin = Address::generate(&env); @@ -60,8 +62,8 @@ fn test_add_expert() { #[should_panic] fn test_add_expert_unauthorized() { let env = Env::default(); - - let contract_id = env.register_contract(None, IdentityRegistryContract); + + let contract_id = env.register(IdentityRegistryContract, ()); let client = IdentityRegistryContractClient::new(&env, &contract_id); let admin = Address::generate(&env); @@ -76,8 +78,8 @@ fn test_add_expert_unauthorized() { fn test_expert_status_changed_event() { let env = Env::default(); env.mock_all_auths(); - - let contract_id = env.register_contract(None, IdentityRegistryContract); + + let contract_id = env.register(IdentityRegistryContract, ()); let client = IdentityRegistryContractClient::new(&env, &contract_id); let admin = Address::generate(&env); @@ -90,7 +92,170 @@ fn test_expert_status_changed_event() { let event = events.last().unwrap(); assert_eq!(event.0, contract_id); - + let topic: Symbol = event.1.get(0).unwrap().try_into_val(&env).unwrap(); assert_eq!(topic, Symbol::new(&env, "status_change")); -} \ No newline at end of file +} +#[test] +fn test_ban_expert() { + let env = Env::default(); + let contract_id = env.register(IdentityRegistryContract, ()); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + // Setup: Create admin and expert addresses + let admin = Address::generate(&env); + let expert = Address::generate(&env); + + // Initialize the contract + client.init(&admin); + + // Verify the expert first + env.mock_all_auths(); + client.add_expert(&expert); + + // Verify status is Verified + let status = client.get_status(&expert); + assert_eq!(status, ExpertStatus::Verified); + + // Ban the expert (should succeed) + client.ban_expert(&expert); + + // Check that status is now Banned + let status = client.get_status(&expert); + assert_eq!(status, ExpertStatus::Banned); + + // Test: Try to ban again (should fail with AlreadyBanned) + let result = client.try_ban_expert(&expert); + assert_eq!(result, Err(Ok(RegistryError::AlreadyBanned))); +} + +#[test] +#[should_panic] +fn test_ban_expert_unauthorized() { + let env = Env::default(); + let contract_id = env.register(IdentityRegistryContract, ()); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let expert = Address::generate(&env); + + client.init(&admin); + + env.mock_all_auths(); + client.add_expert(&expert); + + env.mock_all_auths_allowing_non_root_auth(); + + env.mock_auths(&[]); + + client.ban_expert(&expert); +} + +#[test] +fn test_ban_unverified_expert() { + let env = Env::default(); + let contract_id = env.register(IdentityRegistryContract, ()); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let expert = Address::generate(&env); + + // Initialize + client.init(&admin); + + // Verify initial status is Unverified + let status = client.get_status(&expert); + assert_eq!(status, ExpertStatus::Unverified); + + // Ban an expert who was never verified (should still succeed) + env.mock_all_auths(); + client.ban_expert(&expert); + + // Status should be Banned now + let status = client.get_status(&expert); + assert_eq!(status, ExpertStatus::Banned); +} + +#[test] +fn test_ban_expert_workflow() { + let env = Env::default(); + let contract_id = env.register(IdentityRegistryContract, ()); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let expert1 = Address::generate(&env); + let expert2 = Address::generate(&env); + let expert3 = Address::generate(&env); + + // Initialize + client.init(&admin); + + env.mock_all_auths(); + + // Verify multiple experts + client.add_expert(&expert1); + client.add_expert(&expert2); + client.add_expert(&expert3); + + // Check all are verified + assert_eq!(client.get_status(&expert1), ExpertStatus::Verified); + assert_eq!(client.get_status(&expert2), ExpertStatus::Verified); + assert_eq!(client.get_status(&expert3), ExpertStatus::Verified); + + // Ban expert2 + client.ban_expert(&expert2); + + // Verify expert2 is banned, others remain verified + assert_eq!(client.get_status(&expert1), ExpertStatus::Verified); + assert_eq!(client.get_status(&expert2), ExpertStatus::Banned); + assert_eq!(client.get_status(&expert3), ExpertStatus::Verified); + + // Ban expert1 + client.ban_expert(&expert1); + + // Verify expert1 is now banned + assert_eq!(client.get_status(&expert1), ExpertStatus::Banned); + assert_eq!(client.get_status(&expert2), ExpertStatus::Banned); + assert_eq!(client.get_status(&expert3), ExpertStatus::Verified); +} + +#[test] +fn test_ban_before_contract_initialized() { + let env = Env::default(); + let contract_id = env.register(IdentityRegistryContract, ()); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + let expert = Address::generate(&env); + + env.mock_all_auths(); + + // Try to ban without initializing (should fail) + let result = client.try_ban_expert(&expert); + assert_eq!(result, Err(Ok(RegistryError::NotInitialized))); +} + +#[test] +fn test_complete_expert_lifecycle() { + let env = Env::default(); + let contract_id = env.register(IdentityRegistryContract, ()); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let expert = Address::generate(&env); + + // Initialize + client.init(&admin); + + env.mock_all_auths(); + + // 1. Initial state: Unverified + assert_eq!(client.get_status(&expert), ExpertStatus::Unverified); + + // 2. Verify the expert + client.add_expert(&expert); + assert_eq!(client.get_status(&expert), ExpertStatus::Verified); + + // 3. Ban the expert + client.ban_expert(&expert); + assert_eq!(client.get_status(&expert), ExpertStatus::Banned); +} diff --git a/contracts/identity-registry-contract/src/types.rs b/contracts/identity-registry-contract/src/types.rs index 65e3335..a6fbef5 100644 --- a/contracts/identity-registry-contract/src/types.rs +++ b/contracts/identity-registry-contract/src/types.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{contracttype, Address}; +use soroban_sdk::contracttype; // 1. Expert Status Enum #[contracttype] @@ -16,4 +16,4 @@ pub enum ExpertStatus { pub struct ExpertRecord { pub status: ExpertStatus, pub updated_at: u64, // Ledger timestamp of the last change -} \ No newline at end of file +} From e490a33ffbbbb623d0943b0664f844718b46deed Mon Sep 17 00:00:00 2001 From: ryzen_xp Date: Thu, 22 Jan 2026 19:53:56 +0530 Subject: [PATCH 2/4] fix code rebbit issue --- contracts/identity-registry-contract/src/contract.rs | 4 ++++ contracts/identity-registry-contract/src/events.rs | 2 +- contracts/identity-registry-contract/src/lib.rs | 1 + contracts/identity-registry-contract/src/storage.rs | 6 ++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/contracts/identity-registry-contract/src/contract.rs b/contracts/identity-registry-contract/src/contract.rs index 4a00d4d..1f89ad7 100644 --- a/contracts/identity-registry-contract/src/contract.rs +++ b/contracts/identity-registry-contract/src/contract.rs @@ -4,6 +4,7 @@ use crate::storage; use crate::types::ExpertStatus; use soroban_sdk::{Address, Env}; +/// Initialize the registry with an admin address pub fn initialize_registry(env: &Env, admin: &Address) -> Result<(), RegistryError> { if storage::has_admin(env) { return Err(RegistryError::AlreadyInitialized); @@ -14,6 +15,7 @@ pub fn initialize_registry(env: &Env, admin: &Address) -> Result<(), RegistryErr Ok(()) } +/// Verify an expert by setting their status to Verified (Admin only) pub fn verify_expert(env: &Env, expert: &Address) -> Result<(), RegistryError> { let admin = storage::get_admin(env).ok_or(RegistryError::NotInitialized)?; @@ -38,6 +40,7 @@ pub fn verify_expert(env: &Env, expert: &Address) -> Result<(), RegistryError> { Ok(()) } +/// Ban an expert by setting their status to Banned (Admin only) pub fn ban_expert(env: &Env, expert: &Address) -> Result<(), RegistryError> { let admin = storage::get_admin(env).ok_or(RegistryError::NotInitialized)?; admin.require_auth(); @@ -61,6 +64,7 @@ pub fn ban_expert(env: &Env, expert: &Address) -> Result<(), RegistryError> { Ok(()) } +/// Get the current status of an expert pub fn get_expert_status(env: &Env, expert: &Address) -> ExpertStatus { storage::get_expert_status(env, expert) } diff --git a/contracts/identity-registry-contract/src/events.rs b/contracts/identity-registry-contract/src/events.rs index 9c25ac2..faf0fa9 100644 --- a/contracts/identity-registry-contract/src/events.rs +++ b/contracts/identity-registry-contract/src/events.rs @@ -11,7 +11,7 @@ pub struct ExpertStatusChangedEvent { pub admin: Address, } -// Helper function to emit the event +// Helper function to emit the status change event pub fn emit_status_change( env: &Env, expert: Address, diff --git a/contracts/identity-registry-contract/src/lib.rs b/contracts/identity-registry-contract/src/lib.rs index db01c6a..e410c12 100644 --- a/contracts/identity-registry-contract/src/lib.rs +++ b/contracts/identity-registry-contract/src/lib.rs @@ -32,6 +32,7 @@ impl IdentityRegistryContract { contract::ban_expert(&env, &expert) } + /// Get the current status of an expert pub fn get_status(env: Env, expert: Address) -> ExpertStatus { contract::get_expert_status(&env, &expert) } diff --git a/contracts/identity-registry-contract/src/storage.rs b/contracts/identity-registry-contract/src/storage.rs index c8746af..68f9c37 100644 --- a/contracts/identity-registry-contract/src/storage.rs +++ b/contracts/identity-registry-contract/src/storage.rs @@ -23,20 +23,24 @@ const LEDGERS_EXTEND_TO: u32 = 6_300_000; // ~1 year // ... [Admin Helpers] ... +/// Check if the admin has been set pub fn has_admin(env: &Env) -> bool { env.storage().instance().has(&DataKey::Admin) } +/// Set the admin address pub fn set_admin(env: &Env, admin: &Address) { env.storage().instance().set(&DataKey::Admin, admin); } +/// Get the admin address pub fn get_admin(env: &Env) -> Option
{ env.storage().instance().get(&DataKey::Admin) } // ... [Expert Helpers] ... +/// Set the expert record with status and timestamp pub fn set_expert_record(env: &Env, expert: &Address, status: ExpertStatus) { let key = DataKey::Expert(expert.clone()); @@ -56,6 +60,7 @@ pub fn set_expert_record(env: &Env, expert: &Address, status: ExpertStatus) { .extend_ttl(&key, LEDGERS_THRESHOLD, LEDGERS_EXTEND_TO); } +/// Get the expert record, extending TTL if exists pub fn get_expert_record(env: &Env, expert: &Address) -> ExpertRecord { let key = DataKey::Expert(expert.clone()); @@ -76,6 +81,7 @@ pub fn get_expert_record(env: &Env, expert: &Address) -> ExpertRecord { }) } +/// Get the expert status pub fn get_expert_status(env: &Env, expert: &Address) -> ExpertStatus { get_expert_record(env, expert).status } From dce95fa805fc611ad0886ca372055ed5e4b32846 Mon Sep 17 00:00:00 2001 From: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Date: Fri, 23 Jan 2026 05:59:28 +0530 Subject: [PATCH 3/4] Update contract.rs --- contracts/identity-registry-contract/src/contract.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/identity-registry-contract/src/contract.rs b/contracts/identity-registry-contract/src/contract.rs index 609fccc..9e82c45 100644 --- a/contracts/identity-registry-contract/src/contract.rs +++ b/contracts/identity-registry-contract/src/contract.rs @@ -1,4 +1,3 @@ -use crate::types::ExpertStatus; use soroban_sdk::{Address, Env, Vec}; use crate::storage; use crate::events; From 134c26893793144f3ef249585d98db126e98b716 Mon Sep 17 00:00:00 2001 From: Sandeep chauhan <92181599+ryzen-xp@users.noreply.github.com> Date: Fri, 23 Jan 2026 06:11:59 +0530 Subject: [PATCH 4/4] Update test.rs --- contracts/identity-registry-contract/src/test.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/identity-registry-contract/src/test.rs b/contracts/identity-registry-contract/src/test.rs index 5bd2d38..451e808 100644 --- a/contracts/identity-registry-contract/src/test.rs +++ b/contracts/identity-registry-contract/src/test.rs @@ -3,7 +3,6 @@ extern crate std; use crate::error::RegistryError; -use crate::types::ExpertStatus; use crate::{IdentityRegistryContract, IdentityRegistryContractClient}; use crate::{storage, types::ExpertStatus}; use soroban_sdk::{Env, testutils::Address as _, Symbol, Address, IntoVal, TryIntoVal, Vec, vec};