diff --git a/contracts/reputation/src/lib.rs b/contracts/reputation/src/lib.rs index 399dc684..5eea06ee 100644 --- a/contracts/reputation/src/lib.rs +++ b/contracts/reputation/src/lib.rs @@ -4,6 +4,7 @@ use soroban_sdk::{ contract, contracterror, contractimpl, contracttype, Address, Bytes, BytesN, Env, IntoVal, Symbol, Vec, }; +pub use profile::BadgeLevel; mod profile; mod storage; @@ -480,6 +481,7 @@ impl ReputationContract { let total_jobs = metrics.completed_jobs; let badge_level = metrics.badge_level; + profile.refresh_badges(); storage::write_profile(&env, &address, &profile); env.events().publish( ("reputation", "ScoreAdjusted"), @@ -512,6 +514,7 @@ impl ReputationContract { let total_jobs = metrics.completed_jobs; let badge_level = metrics.badge_level; + profile.refresh_badges(); storage::write_profile(&env, &address, &profile); env.events().publish( ("reputation", "ScoreAdjusted"), @@ -955,6 +958,72 @@ mod test { assert_eq!(saved_hash, Some(hash)); } + // ── Issue #402: badge minting ── + + #[test] + fn test_badge_starts_at_bronze_for_default_score() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let addr = Address::generate(&env); + let cid = env.register_contract(None, ReputationContract); + let client = ReputationContractClient::new(&env, &cid); + client.initialize(&admin); + + // Default score is 5000 → Bronze + let badge = client.get_badge(&addr, &Role::Freelancer); + assert_eq!(badge, BadgeLevel::Bronze); + } + + #[test] + fn test_badge_upgrades_to_silver_at_6000() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let addr = Address::generate(&env); + let cid = env.register_contract(None, ReputationContract); + let client = ReputationContractClient::new(&env, &cid); + client.initialize(&admin); + + // Raise score by 1000 → 5000+1000 = 6000 → Silver + client.update_score(&addr, &Role::Freelancer, &1000); + let badge = client.get_badge(&addr, &Role::Freelancer); + assert_eq!(badge, BadgeLevel::Silver); + } + + #[test] + fn test_badge_upgrades_to_gold_at_8000() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let addr = Address::generate(&env); + let cid = env.register_contract(None, ReputationContract); + let client = ReputationContractClient::new(&env, &cid); + client.initialize(&admin); + + client.update_score(&addr, &Role::Freelancer, &3000); // 5000+3000=8000 + assert_eq!(client.get_badge(&addr, &Role::Freelancer), BadgeLevel::Gold); + } + + #[test] + fn test_slash_downgrades_badge() { + let env = Env::default(); + env.mock_all_auths(); + let admin = Address::generate(&env); + let addr = Address::generate(&env); + let cid = env.register_contract(None, ReputationContract); + let client = ReputationContractClient::new(&env, &cid); + client.initialize(&admin); + + // Bring to Gold first, then slash twice to drop back to Bronze + client.update_score(&addr, &Role::Client, &3000); // 8000 → Gold + assert_eq!(client.get_badge(&addr, &Role::Client), BadgeLevel::Gold); + client.slash(&addr, &Role::Client, &soroban_sdk::Symbol::new(&env, "fraud")); // 6000 → Silver + assert_eq!(client.get_badge(&addr, &Role::Client), BadgeLevel::Silver); + client.slash(&addr, &Role::Client, &soroban_sdk::Symbol::new(&env, "fraud")); // 4000 → Bronze + assert_eq!(client.get_badge(&addr, &Role::Client), BadgeLevel::Bronze); + } + #[test] #[should_panic(expected = "Error(Contract, #2)")] fn test_upgrade_requires_admin() { diff --git a/contracts/reputation/src/profile.rs b/contracts/reputation/src/profile.rs index b1ab4e2b..df611d65 100644 --- a/contracts/reputation/src/profile.rs +++ b/contracts/reputation/src/profile.rs @@ -38,6 +38,36 @@ impl RoleMetrics { } } +/// Badge tier awarded based on cumulative score thresholds. +/// Scores are in basis points (0–10 000). +/// +/// Thresholds: +/// Bronze ≥ 4 000 +/// Silver ≥ 6 000 +/// Gold ≥ 8 000 +/// Platinum ≥ 9 500 +#[contracttype] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BadgeLevel { + None, + Bronze, + Silver, + Gold, + Platinum, +} + +impl BadgeLevel { + pub fn from_score(score: i32) -> Self { + match score { + s if s >= 9_500 => BadgeLevel::Platinum, + s if s >= 8_000 => BadgeLevel::Gold, + s if s >= 6_000 => BadgeLevel::Silver, + s if s >= 4_000 => BadgeLevel::Bronze, + _ => BadgeLevel::None, + } + } +} + #[contracttype] #[derive(Clone, Debug, PartialEq)] pub struct Profile {