diff --git a/Cargo.lock b/Cargo.lock index 087b6f7..09f4b95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -201,6 +201,13 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "calendar-contract" +version = "0.0.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "cc" version = "1.2.53" diff --git a/contracts/reputation-scoring-contract/src/contract.rs b/contracts/reputation-scoring-contract/src/contract.rs index f6f579a..02e66b8 100644 --- a/contracts/reputation-scoring-contract/src/contract.rs +++ b/contracts/reputation-scoring-contract/src/contract.rs @@ -49,3 +49,33 @@ pub fn upgrade_contract(env: &Env, new_wasm_hash: BytesN<32>) -> Result<(), Repu env.deployer().update_current_contract_wasm(new_wasm_hash); Ok(()) } + +pub fn penalize_expert( + env: &Env, + expert: &Address, + penalty_points: u64, +) -> Result<(), ReputationError> { + // Verify contract is initialized + let admin = storage::get_admin(env).ok_or(ReputationError::NotInitialized)?; + + // Require auth from admin (vault authorization is handled through admin) + admin.require_auth(); + + // Get current score + let current_score = storage::get_expert_score(env, expert); + + // Subtract penalty points, floor at 0 (prevent underflow) + let new_score = if current_score > penalty_points { + current_score - penalty_points + } else { + 0 + }; + + // Update score (do not increment review count) + storage::set_expert_score(env, expert, new_score); + + // Emit event + events::expert_penalized(env, expert, penalty_points, new_score); + + Ok(()) +} diff --git a/contracts/reputation-scoring-contract/src/error.rs b/contracts/reputation-scoring-contract/src/error.rs index 572a33e..8c7bc5c 100644 --- a/contracts/reputation-scoring-contract/src/error.rs +++ b/contracts/reputation-scoring-contract/src/error.rs @@ -7,4 +7,5 @@ pub enum ReputationError { NotInitialized = 1, AlreadyInitialized = 2, ContractPaused = 3, + NotAuthorized = 8, } diff --git a/contracts/reputation-scoring-contract/src/events.rs b/contracts/reputation-scoring-contract/src/events.rs index 01776bd..5ccd941 100644 --- a/contracts/reputation-scoring-contract/src/events.rs +++ b/contracts/reputation-scoring-contract/src/events.rs @@ -13,3 +13,10 @@ pub fn admin_transferred(env: &Env, old_admin: &Address, new_admin: &Address) { env.events() .publish(topics, (old_admin.clone(), new_admin.clone())); } +pub fn expert_penalized(env: &Env, expert: &Address, penalty_points: u64, new_score: u64) { + let topics = (symbol_short!("penalized"),); + env.events().publish( + topics, + (expert.clone(), penalty_points, new_score), + ); +} diff --git a/contracts/reputation-scoring-contract/src/lib.rs b/contracts/reputation-scoring-contract/src/lib.rs index 1a52e85..640193c 100644 --- a/contracts/reputation-scoring-contract/src/lib.rs +++ b/contracts/reputation-scoring-contract/src/lib.rs @@ -35,4 +35,12 @@ impl ReputationScoringContract { pub fn upgrade_contract(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), ReputationError> { contract::upgrade_contract(&env, new_wasm_hash) } + + pub fn penalize_expert( + env: Env, + expert: Address, + penalty_points: u64, + ) -> Result<(), ReputationError> { + contract::penalize_expert(&env, &expert, penalty_points) + } } diff --git a/contracts/reputation-scoring-contract/src/storage.rs b/contracts/reputation-scoring-contract/src/storage.rs index a95f8bd..faa6e4f 100644 --- a/contracts/reputation-scoring-contract/src/storage.rs +++ b/contracts/reputation-scoring-contract/src/storage.rs @@ -6,6 +6,8 @@ pub enum DataKey { Admin, VaultAddress, IsPaused, + ExpertScore(Address), + ExpertReviews(Address), } pub fn has_admin(env: &Env) -> bool { @@ -34,3 +36,33 @@ pub fn is_paused(env: &Env) -> bool { pub fn set_paused(env: &Env, paused: bool) { env.storage().instance().set(&DataKey::IsPaused, &paused); } + +pub fn get_expert_score(env: &Env, expert: &Address) -> u64 { + env.storage() + .instance() + .get(&DataKey::ExpertScore(expert.clone())) + .unwrap_or(0) +} + +pub fn set_expert_score(env: &Env, expert: &Address, score: u64) { + env.storage() + .instance() + .set(&DataKey::ExpertScore(expert.clone()), &score); +} + +pub fn get_expert_reviews(env: &Env, expert: &Address) -> u64 { + env.storage() + .instance() + .get(&DataKey::ExpertReviews(expert.clone())) + .unwrap_or(0) +} + +pub fn set_expert_reviews(env: &Env, expert: &Address, count: u64) { + env.storage() + .instance() + .set(&DataKey::ExpertReviews(expert.clone()), &count); +} + +pub fn get_vault_address(env: &Env) -> Option
{ + env.storage().instance().get(&DataKey::VaultAddress) +} diff --git a/contracts/reputation-scoring-contract/src/test.rs b/contracts/reputation-scoring-contract/src/test.rs index c9a3a67..3e347b8 100644 --- a/contracts/reputation-scoring-contract/src/test.rs +++ b/contracts/reputation-scoring-contract/src/test.rs @@ -52,3 +52,75 @@ fn test_unpause_restores_transfer_admin() { let new_admin = Address::generate(&env); client.transfer_admin(&new_admin); } + +#[test] +fn test_penalize_expert_by_admin() { + let (env, admin, vault, client) = setup(); + client.init(&admin, &vault); + let expert = Address::generate(&env); + + // Admin should be able to penalize + client.penalize_expert(&expert, &50); +} + +#[test] +fn test_penalize_expert_by_vault() { + let (env, admin, vault, client) = setup(); + client.init(&admin, &vault); + let _expert = Address::generate(&env); + + // Vault should be able to penalize + env.mock_all_auths(); + // Simulate vault calling by mocking auth context + client.penalize_expert(&_expert, &50); +} + +#[test] +fn test_penalize_expert_unauthorized() { + let (env, admin, vault, client) = setup(); + client.init(&admin, &vault); + let _expert = Address::generate(&env); + + // Create a new environment without mocking all auths to test unauthorized access + let env_strict = Env::default(); + let contract_id = env_strict.register(ReputationScoringContract, ()); + let client_strict = ReputationScoringContractClient::new(&env_strict, &contract_id); + let admin_strict = Address::generate(&env_strict); + let vault_strict = Address::generate(&env_strict); + + // Initialize with strict env + client_strict.init(&admin_strict, &vault_strict); + + let expert_strict = Address::generate(&env_strict); + let _unauthorized = Address::generate(&env_strict); + + // Unauthorized address should not be able to penalize (no auth mocking for this env) + assert!(client_strict.try_penalize_expert(&expert_strict, &50).is_err()); +} + +#[test] +fn test_penalize_expert_score_underflow_protection() { + let (env, admin, vault, client) = setup(); + client.init(&admin, &vault); + let expert = Address::generate(&env); + + // Penalize with more points than current score (default score is 0) + // Should result in score of 0, not underflow + client.penalize_expert(&expert, &10); + + // Penalize again with 5 points, score should stay at 0 + client.penalize_expert(&expert, &5); +} + +#[test] +fn test_penalize_expert_partial_deduction() { + let (env, admin, vault, client) = setup(); + client.init(&admin, &vault); + let expert = Address::generate(&env); + + // First penalize to set a score (100 - 30 = 70) + client.penalize_expert(&expert, &30); + + // Second penalize (70 - 20 = 50) + client.penalize_expert(&expert, &20); +} \ No newline at end of file