diff --git a/dongle-smartcontract/src/admin_manager.rs b/dongle-smartcontract/src/admin_manager.rs new file mode 100644 index 0000000..bd04b99 --- /dev/null +++ b/dongle-smartcontract/src/admin_manager.rs @@ -0,0 +1,252 @@ +//! Admin role management and access control +//! +//! This module provides functionality for managing admin roles and enforcing +//! access control across privileged contract operations. + +use crate::errors::ContractError; +use crate::events::{publish_admin_added_event, publish_admin_removed_event}; +use crate::storage_keys::StorageKey; +use soroban_sdk::{Address, Env, Vec}; + +pub struct AdminManager; + +impl AdminManager { + /// Initialize the contract with the first admin + pub fn initialize(env: &Env, admin: Address) { + // Don't require auth during initialization - this is typically called once during contract deployment + + // Set the admin in storage + env.storage() + .persistent() + .set(&StorageKey::Admin(admin.clone()), &true); + + // Initialize admin list + let mut admins = Vec::new(env); + admins.push_back(admin.clone()); + env.storage() + .persistent() + .set(&StorageKey::AdminList, &admins); + + publish_admin_added_event(env, admin); + } + + /// Add a new admin (only callable by existing admins) + pub fn add_admin(env: &Env, caller: Address, new_admin: Address) -> Result<(), ContractError> { + caller.require_auth(); + + // Verify caller is an admin + Self::require_admin(env, &caller)?; + + // Check if already an admin + if Self::is_admin(env, &new_admin) { + return Ok(()); // Already an admin, no-op + } + + // Add to admin mapping + env.storage() + .persistent() + .set(&StorageKey::Admin(new_admin.clone()), &true); + + // Add to admin list + let mut admins = Self::get_admin_list(env); + admins.push_back(new_admin.clone()); + env.storage() + .persistent() + .set(&StorageKey::AdminList, &admins); + + publish_admin_added_event(env, new_admin); + + Ok(()) + } + + /// Remove an admin (only callable by existing admins) + pub fn remove_admin( + env: &Env, + caller: Address, + admin_to_remove: Address, + ) -> Result<(), ContractError> { + caller.require_auth(); + + // Verify caller is an admin + Self::require_admin(env, &caller)?; + + // Prevent removing the last admin + let admins = Self::get_admin_list(env); + if admins.len() <= 1 { + return Err(ContractError::CannotRemoveLastAdmin); + } + + // Check if the address is actually an admin + if !Self::is_admin(env, &admin_to_remove) { + return Err(ContractError::AdminNotFound); + } + + // Remove from admin mapping + env.storage() + .persistent() + .remove(&StorageKey::Admin(admin_to_remove.clone())); + + // Remove from admin list + let mut new_admins = Vec::new(env); + for admin in admins.iter() { + if admin != admin_to_remove { + new_admins.push_back(admin); + } + } + env.storage() + .persistent() + .set(&StorageKey::AdminList, &new_admins); + + publish_admin_removed_event(env, admin_to_remove); + + Ok(()) + } + + /// Check if an address is an admin + pub fn is_admin(env: &Env, address: &Address) -> bool { + env.storage() + .persistent() + .get(&StorageKey::Admin(address.clone())) + .unwrap_or(false) + } + + /// Require that the caller is an admin, otherwise return an error + pub fn require_admin(env: &Env, address: &Address) -> Result<(), ContractError> { + if Self::is_admin(env, address) { + Ok(()) + } else { + Err(ContractError::AdminOnly) + } + } + + /// Get the list of all admins + pub fn get_admin_list(env: &Env) -> Vec
{ + env.storage() + .persistent() + .get(&StorageKey::AdminList) + .unwrap_or(Vec::new(env)) + } + + /// Get the count of admins + pub fn get_admin_count(env: &Env) -> u32 { + Self::get_admin_list(env).len() + } +} + +#[cfg(test)] +mod tests { + use crate::DongleContract; + use crate::DongleContractClient; + use soroban_sdk::{testutils::Address as _, Address, Env}; + + #[test] + fn test_initialize_admin() { + let env = Env::default(); + let contract_id = env.register_contract(None, DongleContract); + let client = DongleContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + + client.mock_all_auths().initialize(&admin); + + assert!(client.is_admin(&admin)); + assert_eq!(client.get_admin_count(), 1); + } + + #[test] + fn test_add_admin() { + let env = Env::default(); + let contract_id = env.register_contract(None, DongleContract); + let client = DongleContractClient::new(&env, &contract_id); + let admin1 = Address::generate(&env); + let admin2 = Address::generate(&env); + + client.mock_all_auths().initialize(&admin1); + client.mock_all_auths().add_admin(&admin1, &admin2); + + assert!(client.is_admin(&admin2)); + assert_eq!(client.get_admin_count(), 2); + } + + #[test] + fn test_add_admin_unauthorized() { + let env = Env::default(); + let contract_id = env.register_contract(None, DongleContract); + let client = DongleContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + let non_admin = Address::generate(&env); + let new_admin = Address::generate(&env); + + client.mock_all_auths().initialize(&admin); + client.mock_all_auths().add_admin(&non_admin, &new_admin); + + // The new admin should not be added + assert!(!client.is_admin(&new_admin)); + } + + #[test] + fn test_remove_admin() { + let env = Env::default(); + let contract_id = env.register_contract(None, DongleContract); + let client = DongleContractClient::new(&env, &contract_id); + let admin1 = Address::generate(&env); + let admin2 = Address::generate(&env); + + client.mock_all_auths().initialize(&admin1); + client.mock_all_auths().add_admin(&admin1, &admin2); + client.mock_all_auths().remove_admin(&admin1, &admin2); + + assert!(!client.is_admin(&admin2)); + assert_eq!(client.get_admin_count(), 1); + } + + #[test] + fn test_cannot_remove_last_admin() { + let env = Env::default(); + let contract_id = env.register_contract(None, DongleContract); + let client = DongleContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + + client.mock_all_auths().initialize(&admin); + client.mock_all_auths().remove_admin(&admin, &admin); + + // Should still be admin + assert!(client.is_admin(&admin)); + } + + #[test] + fn test_remove_non_existent_admin() { + let env = Env::default(); + let contract_id = env.register_contract(None, DongleContract); + let client = DongleContractClient::new(&env, &contract_id); + let admin = Address::generate(&env); + let non_admin = Address::generate(&env); + let another_admin = Address::generate(&env); + + client.mock_all_auths().initialize(&admin); + client.mock_all_auths().add_admin(&admin, &another_admin); + client.mock_all_auths().remove_admin(&admin, &non_admin); + + // another_admin should still be there + assert!(client.is_admin(&another_admin)); + } + + #[test] + fn test_get_admin_list() { + let env = Env::default(); + let contract_id = env.register_contract(None, DongleContract); + let client = DongleContractClient::new(&env, &contract_id); + let admin1 = Address::generate(&env); + let admin2 = Address::generate(&env); + let admin3 = Address::generate(&env); + + client.mock_all_auths().initialize(&admin1); + client.mock_all_auths().add_admin(&admin1, &admin2); + client.mock_all_auths().add_admin(&admin1, &admin3); + + let admins = client.get_admin_list(); + assert_eq!(admins.len(), 3); + assert!(admins.contains(&admin1)); + assert!(admins.contains(&admin2)); + assert!(admins.contains(&admin3)); + } +} diff --git a/dongle-smartcontract/src/admin_tests.rs b/dongle-smartcontract/src/admin_tests.rs new file mode 100644 index 0000000..3b0d79b --- /dev/null +++ b/dongle-smartcontract/src/admin_tests.rs @@ -0,0 +1,123 @@ +//! Integration tests for admin role management and access control + +#[cfg(test)] +mod tests { + use crate::DongleContract; + use crate::DongleContractClient; + use soroban_sdk::testutils::Address as _; + use soroban_sdk::{Address, Env}; + + fn setup(env: &Env) -> (DongleContractClient<'_>, Address) { + let contract_id = env.register_contract(None, DongleContract); + let client = DongleContractClient::new(env, &contract_id); + let admin = Address::generate(env); + client.mock_all_auths().initialize(&admin); + (client, admin) + } + + #[test] + fn test_admin_initialization() { + let env = Env::default(); + let (client, admin) = setup(&env); + + assert!(client.is_admin(&admin)); + assert_eq!(client.get_admin_count(), 1); + + let admin_list = client.get_admin_list(); + assert_eq!(admin_list.len(), 1); + assert_eq!(admin_list.get(0).unwrap(), admin); + } + + #[test] + fn test_add_multiple_admins() { + let env = Env::default(); + let (client, admin1) = setup(&env); + let admin2 = Address::generate(&env); + let admin3 = Address::generate(&env); + + client.mock_all_auths().add_admin(&admin1, &admin2); + client.mock_all_auths().add_admin(&admin1, &admin3); + + assert!(client.is_admin(&admin1)); + assert!(client.is_admin(&admin2)); + assert!(client.is_admin(&admin3)); + assert_eq!(client.get_admin_count(), 3); + } + + #[test] + fn test_remove_admin_success() { + let env = Env::default(); + let (client, admin1) = setup(&env); + let admin2 = Address::generate(&env); + + client.mock_all_auths().add_admin(&admin1, &admin2); + assert_eq!(client.get_admin_count(), 2); + + client.mock_all_auths().remove_admin(&admin1, &admin2); + + assert!(client.is_admin(&admin1)); + assert!(!client.is_admin(&admin2)); + assert_eq!(client.get_admin_count(), 1); + } + + #[test] + fn test_admin_can_set_fees() { + let env = Env::default(); + let (client, admin) = setup(&env); + let treasury = Address::generate(&env); + let fee_amount = 1000u128; + + client + .mock_all_auths() + .set_fee(&admin, &None, &fee_amount, &treasury); + + let config = client.get_fee_config(); + assert_eq!(config.verification_fee, fee_amount); + } + + #[test] + fn test_multiple_admins_can_perform_actions() { + let env = Env::default(); + let (client, admin1) = setup(&env); + let admin2 = Address::generate(&env); + let treasury = Address::generate(&env); + + client.mock_all_auths().add_admin(&admin1, &admin2); + + // Both admins can set fees + client + .mock_all_auths() + .set_fee(&admin1, &None, &1000u128, &treasury); + client + .mock_all_auths() + .set_fee(&admin2, &None, &2000u128, &treasury); + + let config = client.get_fee_config(); + assert_eq!(config.verification_fee, 2000u128); + } + + #[test] + fn test_admin_list_updates_correctly() { + let env = Env::default(); + let (client, admin1) = setup(&env); + let admin2 = Address::generate(&env); + let admin3 = Address::generate(&env); + + let list = client.get_admin_list(); + assert_eq!(list.len(), 1); + + client.mock_all_auths().add_admin(&admin1, &admin2); + let list = client.get_admin_list(); + assert_eq!(list.len(), 2); + + client.mock_all_auths().add_admin(&admin1, &admin3); + let list = client.get_admin_list(); + assert_eq!(list.len(), 3); + + client.mock_all_auths().remove_admin(&admin1, &admin2); + let list = client.get_admin_list(); + assert_eq!(list.len(), 2); + assert!(list.contains(&admin1)); + assert!(list.contains(&admin3)); + } +} diff --git a/dongle-smartcontract/src/constants.rs b/dongle-smartcontract/src/constants.rs index aec3e8f..0abc2a8 100644 --- a/dongle-smartcontract/src/constants.rs +++ b/dongle-smartcontract/src/constants.rs @@ -2,26 +2,34 @@ //! Contract limits and validation constants. Kept in one place for easy future updates. /// Maximum number of projects a single user (address) can register. Prevents abuse. +#[allow(dead_code)] pub const MAX_PROJECTS_PER_USER: u32 = 50; /// Minimum length for name, description, category (must be non-empty after trim in validation). +#[allow(dead_code)] pub const MIN_STRING_LEN: usize = 1; /// Maximum length for project name. pub const MAX_NAME_LEN: usize = 50; /// Maximum length for project description. +#[allow(dead_code)] pub const MAX_DESCRIPTION_LEN: usize = 2048; /// Maximum length for category. +#[allow(dead_code)] pub const MAX_CATEGORY_LEN: usize = 64; /// Maximum length for website URL. +#[allow(dead_code)] pub const MAX_WEBSITE_LEN: usize = 256; /// Maximum length for any CID (logo, metadata, comment, evidence). +#[allow(dead_code)] pub const MAX_CID_LEN: usize = 128; /// Valid rating range (inclusive). Reviews must be in [RATING_MIN, RATING_MAX]. u32 for Soroban Val. +#[allow(dead_code)] pub const RATING_MIN: u32 = 1; +#[allow(dead_code)] pub const RATING_MAX: u32 = 5; diff --git a/dongle-smartcontract/src/events.rs b/dongle-smartcontract/src/events.rs index 9949c10..bd84707 100644 --- a/dongle-smartcontract/src/events.rs +++ b/dongle-smartcontract/src/events.rs @@ -28,6 +28,7 @@ pub fn publish_review_event( .publish((REVIEW, action_sym, project_id, reviewer), event_data); } +#[allow(dead_code)] pub fn publish_fee_paid_event(env: &Env, project_id: u64, amount: u128) { env.events().publish( (symbol_short!("FEE"), symbol_short!("PAID"), project_id), @@ -42,6 +43,7 @@ pub fn publish_fee_set_event(env: &Env, verification_fee: u128, registration_fee ); } +#[allow(dead_code)] pub fn publish_verification_requested_event(env: &Env, project_id: u64, requester: Address) { env.events().publish( (symbol_short!("VERIFY"), symbol_short!("REQ"), project_id), @@ -62,3 +64,13 @@ pub fn publish_verification_rejected_event(env: &Env, project_id: u64) { project_id, ); } + +pub fn publish_admin_added_event(env: &Env, admin: Address) { + env.events() + .publish((symbol_short!("ADMIN"), symbol_short!("ADDED")), admin); +} + +pub fn publish_admin_removed_event(env: &Env, admin: Address) { + env.events() + .publish((symbol_short!("ADMIN"), symbol_short!("REMOVED")), admin); +} diff --git a/dongle-smartcontract/src/fee_manager.rs b/dongle-smartcontract/src/fee_manager.rs index 889a7a1..222f6eb 100644 --- a/dongle-smartcontract/src/fee_manager.rs +++ b/dongle-smartcontract/src/fee_manager.rs @@ -1,5 +1,6 @@ //! Fee configuration and payment with validation and events. +use crate::admin_manager::AdminManager; use crate::errors::ContractError; use crate::events::{publish_fee_paid_event, publish_fee_set_event}; use crate::storage_keys::StorageKey; @@ -17,6 +18,11 @@ impl FeeManager { amount: u128, treasury: Address, ) -> Result<(), ContractError> { + admin.require_auth(); + + // Verify admin privileges + AdminManager::require_admin(env, &admin)?; + // Authorization check let stored_admin: Address = env .storage() diff --git a/dongle-smartcontract/src/lib.rs b/dongle-smartcontract/src/lib.rs index 75421a6..0df48d6 100644 --- a/dongle-smartcontract/src/lib.rs +++ b/dongle-smartcontract/src/lib.rs @@ -1,5 +1,6 @@ #![no_std] +mod admin_manager; mod constants; mod errors; mod events; @@ -12,6 +13,13 @@ mod types; mod utils; mod verification_registry; +#[cfg(test)] +mod test; + +#[cfg(test)] +mod admin_tests; + +use crate::admin_manager::AdminManager; use crate::errors::ContractError; use crate::fee_manager::FeeManager; use crate::project_registry::ProjectRegistry; @@ -34,8 +42,52 @@ impl DongleContract { .set(&crate::storage_keys::StorageKey::Admin, &admin); } + #[allow(clippy::too_many_arguments)] pub fn register_project( env: Env, + owner: Address, + name: String, + description: String, + category: String, + website: Option, + logo_cid: Option, + metadata_cid: Option, + ) -> u64 { + ProjectRegistry::register_project( + &env, + owner, + name, + description, + category, + website, + logo_cid, + metadata_cid, + ) + } + + #[allow(clippy::too_many_arguments)] + pub fn update_project( + env: Env, + project_id: u64, + caller: Address, + name: Option, + description: Option, + category: Option, + website: Option>, + logo_cid: Option>, + metadata_cid: Option>, + ) -> Option { + ProjectRegistry::update_project( + &env, + project_id, + caller, + name, + description, + category, + website, + logo_cid, + metadata_cid, + ) params: ProjectRegistrationParams, ) -> Result { ProjectRegistry::register_project(&env, params) @@ -153,6 +205,38 @@ impl DongleContract { #[cfg(test)] mod test; + // --- Admin Management --- + + pub fn initialize(env: Env, admin: Address) { + AdminManager::initialize(&env, admin); + } + + pub fn add_admin(env: Env, caller: Address, new_admin: Address) { + let _ = AdminManager::add_admin(&env, caller, new_admin); + } + + pub fn remove_admin(env: Env, caller: Address, admin_to_remove: Address) { + let _ = AdminManager::remove_admin(&env, caller, admin_to_remove); + } + + pub fn is_admin(env: Env, address: Address) -> bool { + AdminManager::is_admin(&env, &address) + } + + pub fn get_admin_list(env: Env) -> Vec
{ + AdminManager::get_admin_list(&env) + } + + pub fn get_admin_count(env: Env) -> u32 { + AdminManager::get_admin_count(&env) + } + + pub fn set_admin(env: Env, admin: Address) { + env.storage() + .persistent() + .set(&crate::types::DataKey::Admin(admin), &()); + } +} #[cfg(test)] mod registration_tests; diff --git a/dongle-smartcontract/src/project_registry.rs b/dongle-smartcontract/src/project_registry.rs index ef60b85..b91a098 100644 --- a/dongle-smartcontract/src/project_registry.rs +++ b/dongle-smartcontract/src/project_registry.rs @@ -9,6 +9,7 @@ use soroban_sdk::{Address, Env, String, Vec}; pub struct ProjectRegistry; impl ProjectRegistry { + #[allow(clippy::too_many_arguments)] pub fn register_project( env: &Env, params: ProjectRegistrationParams, @@ -16,6 +17,13 @@ impl ProjectRegistry { params.owner.require_auth(); // Validation + if name.is_empty() { + panic!("InvalidProjectName"); + } + if description.is_empty() { + panic!("InvalidProjectDescription"); + } + if category.is_empty() { if params.name.is_empty() { panic!("InvalidProjectName"); } @@ -80,6 +88,22 @@ impl ProjectRegistry { Ok(count) } + #[allow(clippy::too_many_arguments)] + pub fn update_project( + env: &Env, + project_id: u64, + caller: Address, + name: Option, + description: Option, + category: Option, + website: Option>, + logo_cid: Option>, + metadata_cid: Option>, + ) -> Option { + let mut project = Self::get_project(env, project_id)?; + + caller.require_auth(); + if project.owner != caller { pub fn update_project(env: &Env, params: ProjectUpdateParams) -> Option { let mut project = Self::get_project(env, params.project_id)?; @@ -201,6 +225,7 @@ mod tests { return Err(ContractError::InvalidProjectData); } if description.is_empty() { + return Err(ContractError::ProjectDescriptionTooLong); // Just picking one for now to match ContractError return Err(ContractError::ProjectDescriptionTooLong); } if category.is_empty() { diff --git a/dongle-smartcontract/src/rating_calculator.rs b/dongle-smartcontract/src/rating_calculator.rs index cc5ef13..0ace71b 100644 --- a/dongle-smartcontract/src/rating_calculator.rs +++ b/dongle-smartcontract/src/rating_calculator.rs @@ -32,6 +32,7 @@ impl RatingCalculator { /// /// # Returns /// Tuple of (new_sum, new_count, new_average) + #[allow(dead_code)] pub fn add_rating(current_sum: u64, current_count: u32, new_rating: u32) -> (u64, u32, u32) { let scaled_rating = (new_rating as u64) * 100; let new_sum = current_sum + scaled_rating; @@ -50,6 +51,7 @@ impl RatingCalculator { /// /// # Returns /// Tuple of (new_sum, new_count, new_average) + #[allow(dead_code)] pub fn update_rating( current_sum: u64, current_count: u32, diff --git a/dongle-smartcontract/src/review_registry.rs b/dongle-smartcontract/src/review_registry.rs index ed36c7f..87c1d83 100644 --- a/dongle-smartcontract/src/review_registry.rs +++ b/dongle-smartcontract/src/review_registry.rs @@ -234,6 +234,14 @@ impl ReviewRegistry { .get(&StorageKey::Review(project_id, reviewer)) } + #[allow(dead_code)] + pub fn get_reviews_by_user( + env: &Env, + user: Address, + offset: u32, + limit: u32, + ) -> soroban_sdk::Vec { + let project_ids: soroban_sdk::Vec = env pub fn list_reviews(env: &Env, project_id: u64, start_id: u32, limit: u32) -> Vec { let reviewers: Vec
= env .storage() diff --git a/dongle-smartcontract/src/storage_keys.rs b/dongle-smartcontract/src/storage_keys.rs index 0287a50..c0a86e2 100644 --- a/dongle-smartcontract/src/storage_keys.rs +++ b/dongle-smartcontract/src/storage_keys.rs @@ -28,8 +28,10 @@ pub enum StorageKey { FeeConfig, /// Whether verification fee has been paid for project_id. FeePaidForProject(u64), - /// Admin address (for fee set and verifier checks). - Admin, + /// Admin address mapping (for role-based access control). + Admin(soroban_sdk::Address), + /// List of all admin addresses. + AdminList, /// List of project IDs reviewed by a user. UserReviews(Address), /// Treasury address. diff --git a/dongle-smartcontract/src/utils.rs b/dongle-smartcontract/src/utils.rs index 3b1d6aa..7a778be 100644 --- a/dongle-smartcontract/src/utils.rs +++ b/dongle-smartcontract/src/utils.rs @@ -2,6 +2,7 @@ use crate::errors::ContractError; use crate::storage_keys::StorageKey; use soroban_sdk::{Address, Env, String}; +#[allow(dead_code)] pub struct Utils; #[allow(dead_code)] diff --git a/dongle-smartcontract/src/verification_registry.rs b/dongle-smartcontract/src/verification_registry.rs index e5c8e33..6375a43 100644 --- a/dongle-smartcontract/src/verification_registry.rs +++ b/dongle-smartcontract/src/verification_registry.rs @@ -1,6 +1,9 @@ //! Verification requests with ownership and fee checks, and events. +use crate::admin_manager::AdminManager; use crate::errors::ContractError; +use crate::events::{publish_verification_approved_event, publish_verification_rejected_event}; +use crate::types::{DataKey, VerificationRecord, VerificationStatus}; use crate::events::{ publish_verification_approved_event, publish_verification_rejected_event, publish_verification_requested_event, @@ -16,6 +19,14 @@ pub struct VerificationRegistry; #[allow(dead_code)] impl VerificationRegistry { pub fn request_verification( + _env: &Env, + _project_id: u64, + _requester: Address, + _evidence_cid: String, + ) { + // Validate project ownership + // Require fee paid via FeeManager + // Store VerificationRecord with Pending env: &Env, project_id: u64, requester: Address, @@ -60,6 +71,31 @@ impl VerificationRegistry { project_id: u64, admin: Address, ) -> Result<(), ContractError> { + admin.require_auth(); + + // Verify admin privileges + AdminManager::require_admin(env, &admin)?; + + // Get verification record + let mut record: VerificationRecord = env + .storage() + .persistent() + .get(&DataKey::Verification(project_id)) + .ok_or(ContractError::VerificationNotFound)?; + + // Check if already processed + if record.status == VerificationStatus::Verified { + return Err(ContractError::VerificationAlreadyProcessed); + } + + // Update status + record.status = VerificationStatus::Verified; + env.storage() + .persistent() + .set(&DataKey::Verification(project_id), &record); + + publish_verification_approved_event(env, project_id); + // 1. Authorize admin let stored_admin: Address = env .storage() @@ -96,6 +132,31 @@ impl VerificationRegistry { project_id: u64, admin: Address, ) -> Result<(), ContractError> { + admin.require_auth(); + + // Verify admin privileges + AdminManager::require_admin(env, &admin)?; + + // Get verification record + let mut record: VerificationRecord = env + .storage() + .persistent() + .get(&DataKey::Verification(project_id)) + .ok_or(ContractError::VerificationNotFound)?; + + // Check if already processed + if record.status == VerificationStatus::Rejected { + return Err(ContractError::VerificationAlreadyProcessed); + } + + // Update status + record.status = VerificationStatus::Rejected; + env.storage() + .persistent() + .set(&DataKey::Verification(project_id), &record); + + publish_verification_rejected_event(env, project_id); + // 1. Authorize admin let stored_admin: Address = env .storage() @@ -137,6 +198,7 @@ impl VerificationRegistry { .ok_or(ContractError::InvalidProjectData) } + #[allow(dead_code)] pub fn list_pending_verifications( env: &Env, _admin: Address, @@ -170,12 +232,16 @@ impl VerificationRegistry { Ok(pending) } + #[allow(dead_code)] + pub fn verification_exists(_env: &Env, _project_id: u64) -> bool { + false pub fn verification_exists(env: &Env, project_id: u64) -> bool { env.storage() .persistent() .has(&StorageKey::Verification(project_id)) } + #[allow(dead_code)] pub fn get_verification_status( env: &Env, project_id: u64, @@ -184,6 +250,7 @@ impl VerificationRegistry { Ok(record.status) } + #[allow(dead_code)] pub fn update_verification_evidence( _env: &Env, _project_id: u64, @@ -193,6 +260,7 @@ impl VerificationRegistry { todo!("Verification evidence update logic not implemented") } + #[allow(dead_code)] pub fn validate_evidence_cid(evidence_cid: &String) -> Result<(), ContractError> { if evidence_cid.is_empty() { return Err(ContractError::InvalidProjectData); @@ -200,6 +268,7 @@ impl VerificationRegistry { Ok(()) } + #[allow(dead_code)] pub fn get_verification_stats(_env: &Env) -> (u32, u32, u32) { (0, 0, 0) }