From b2eb9bcfaaeefc24898b99c66d2fd586118006c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cussyalfaks=E2=80=9D?= <“usmanalfaki@gmail.com”> Date: Thu, 22 Jan 2026 09:39:42 +0100 Subject: [PATCH 1/2] feat(admin): implement expert verification whitelist gatekeeper --- Cargo.lock | 7 +++ .../src/contract.rs | 20 +++++++ .../identity-registry-contract/src/lib.rs | 5 ++ .../identity-registry-contract/src/test.rs | 54 ++++++++++++++++++- 4 files changed, 85 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9ce16f4..5ad48b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -904,6 +904,13 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "payment-vault-contract" +version = "0.0.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "pkcs8" version = "0.10.2" diff --git a/contracts/identity-registry-contract/src/contract.rs b/contracts/identity-registry-contract/src/contract.rs index 23712ca..31249f4 100644 --- a/contracts/identity-registry-contract/src/contract.rs +++ b/contracts/identity-registry-contract/src/contract.rs @@ -1,6 +1,8 @@ use soroban_sdk::{Address, Env}; use crate::storage; use crate::error::RegistryError; +use crate::types::ExpertStatus; +use crate::events; pub fn initialize_registry(env: &Env, admin: &Address) -> Result<(), RegistryError> { if storage::has_admin(env) { @@ -9,5 +11,23 @@ pub fn initialize_registry(env: &Env, admin: &Address) -> Result<(), RegistryErr storage::set_admin(env, admin); + Ok(()) +} + +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); + Ok(()) } \ No newline at end of file diff --git a/contracts/identity-registry-contract/src/lib.rs b/contracts/identity-registry-contract/src/lib.rs index 85df4f3..6e5de09 100644 --- a/contracts/identity-registry-contract/src/lib.rs +++ b/contracts/identity-registry-contract/src/lib.rs @@ -20,4 +20,9 @@ impl IdentityRegistryContract { pub fn init(env: Env, admin: Address) -> Result<(), RegistryError> { contract::initialize_registry(&env, &admin) } + + /// Add an expert to the whitelist (Admin only) + pub fn add_expert(env: Env, expert: Address) -> Result<(), RegistryError> { + contract::verify_expert(&env, &expert) + } } \ 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 a610c9f..c6346ee 100644 --- a/contracts/identity-registry-contract/src/test.rs +++ b/contracts/identity-registry-contract/src/test.rs @@ -1,6 +1,10 @@ #![cfg(test)] + +extern crate std; + use crate::{IdentityRegistryContract, IdentityRegistryContractClient}; -use soroban_sdk::{Env, testutils::Address as _}; +use soroban_sdk::{Env, testutils::Address as _, Symbol, Address, IntoVal}; +use soroban_sdk::testutils::{AuthorizedFunction, AuthorizedInvocation}; #[test] fn test_initialization() { @@ -18,4 +22,52 @@ fn test_initialization() { // 3. Call init again (Should fail) let res_duplicate = client.try_init(&admin); assert!(res_duplicate.is_err()); +} + +#[test] +fn test_add_expert() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register_contract(None, IdentityRegistryContract); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let expert = Address::generate(&env); + + client.init(&admin); + + let res = client.try_add_expert(&expert); + assert!(res.is_ok()); + + assert_eq!( + env.auths()[0], + ( + admin.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + contract_id.clone(), + Symbol::new(&env, "add_expert"), + (expert.clone(),).into_val(&env) + )), + sub_invocations: std::vec![] + } + ) + ); +} + +#[test] +#[should_panic] +fn test_add_expert_unauthorized() { + let env = Env::default(); + + let contract_id = env.register_contract(None, IdentityRegistryContract); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let expert = Address::generate(&env); + + client.init(&admin); + + client.add_expert(&expert); } \ No newline at end of file From 9d6bf26b24cd4aa8b1c4d565e30a45230bba4427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cussyalfaks=E2=80=9D?= <“usmanalfaki@gmail.com”> Date: Thu, 22 Jan 2026 09:52:21 +0100 Subject: [PATCH 2/2] event changes test --- .../identity-registry-contract/src/test.rs | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/contracts/identity-registry-contract/src/test.rs b/contracts/identity-registry-contract/src/test.rs index c6346ee..b76bacf 100644 --- a/contracts/identity-registry-contract/src/test.rs +++ b/contracts/identity-registry-contract/src/test.rs @@ -3,8 +3,8 @@ extern crate std; use crate::{IdentityRegistryContract, IdentityRegistryContractClient}; -use soroban_sdk::{Env, testutils::Address as _, Symbol, Address, IntoVal}; -use soroban_sdk::testutils::{AuthorizedFunction, AuthorizedInvocation}; +use soroban_sdk::{Env, testutils::Address as _, Symbol, Address, IntoVal, TryIntoVal}; +use soroban_sdk::testutils::{AuthorizedFunction, AuthorizedInvocation, Events}; #[test] fn test_initialization() { @@ -70,4 +70,27 @@ fn test_add_expert_unauthorized() { client.init(&admin); client.add_expert(&expert); +} + +#[test] +fn test_expert_status_changed_event() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register_contract(None, IdentityRegistryContract); + let client = IdentityRegistryContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + let expert = Address::generate(&env); + + client.init(&admin); + client.add_expert(&expert); + + let events = env.events().all(); + 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