From c3b0fb86a87d5d9ab2da5920ff7b0a1eb2f8b626 Mon Sep 17 00:00:00 2001 From: JACOB STANLEY Date: Tue, 21 Jul 2026 15:22:13 +0100 Subject: [PATCH] https://github.com/Menjay7/mesh-contract.git --- contracts/registry/src/lib.rs | 298 ++++++++++++++++++++++++++++++++- contracts/registry/src/test.rs | 1 + 2 files changed, 297 insertions(+), 2 deletions(-) diff --git a/contracts/registry/src/lib.rs b/contracts/registry/src/lib.rs index addbe76..b14e225 100644 --- a/contracts/registry/src/lib.rs +++ b/contracts/registry/src/lib.rs @@ -31,6 +31,17 @@ pub struct WaveMeta { pub closed_at: u64, pub total_points: u32, pub status: WaveStatus, + pub difficulty_level: u32, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ContributorPerformance { + pub total_waves_participated: u32, + pub total_points_earned: u32, + pub average_points_per_wave: u32, + pub success_rate: u32, + pub last_updated: u64, } /// Storage keys for the registry contract state. @@ -46,6 +57,8 @@ pub enum DataKey { WaveCounter, Contributions(Address, u32), // contributor, wave_id -> contribution History(Address), // contributor -> Vec + ContributorPerformance(Address), // contributor -> performance metrics + ProgramDifficulty(u32), // program_id -> current difficulty level } #[contract] @@ -118,6 +131,9 @@ impl RegistryContract { panic!("program doesn't exist"); } + // Get or initialize difficulty level for the program + let difficulty_level: u32 = env.storage().persistent().get(&DataKey::ProgramDifficulty(program_id)).unwrap_or(1); + // Increment global wave ID let mut counter: u32 = env.storage().instance().get(&DataKey::WaveCounter).unwrap_or(0); counter += 1; @@ -131,13 +147,14 @@ impl RegistryContract { closed_at: 0, total_points: 0, status: WaveStatus::Open, + difficulty_level, }; env.storage().persistent().set(&DataKey::Waves(wave_id), &wave); // Emit WaveOpened event env.events().publish( - (symbol_short!("wave_open"), program_id, wave_id), + (symbol_short!("wave_open"), program_id, wave_id, difficulty_level), env.ledger().timestamp(), ); @@ -162,6 +179,9 @@ impl RegistryContract { env.storage().persistent().set(&DataKey::Waves(wave_id), &wave); + // Adjust difficulty based on overall performance + Self::adjust_program_difficulty(&env, wave.program_id, total_points, wave.difficulty_level); + // Emit WaveClosed event env.events().publish( (symbol_short!("wave_cls"), wave_id, total_points), @@ -203,8 +223,11 @@ impl RegistryContract { if !history.contains(wave_id) { history.push_back(wave_id); - env.storage().persistent().set(&DataKey::History(address), &history); + env.storage().persistent().set(&DataKey::History(address.clone()), &history); } + + // Update contributor performance metrics + Self::update_contributor_performance(&env, address.clone(), points); } /// Returns the full contribution history for a contributor. @@ -245,6 +268,86 @@ impl RegistryContract { admin.require_auth(); env.storage().instance().set(&DataKey::SettlementContract, &new_settlement); } + + /// Update contributor performance metrics after recording a contribution + fn update_contributor_performance(env: &Env, address: Address, points: u32) { + let mut perf: ContributorPerformance = env + .storage() + .persistent() + .get(&DataKey::ContributorPerformance(address.clone())) + .unwrap_or_else(|| ContributorPerformance { + total_waves_participated: 0, + total_points_earned: 0, + average_points_per_wave: 0, + success_rate: 100, + last_updated: 0, + }); + + perf.total_waves_participated += 1; + perf.total_points_earned += points; + perf.average_points_per_wave = perf.total_points_earned / perf.total_waves_participated; + perf.last_updated = env.ledger().timestamp(); + + // Simple success rate calculation: if points > 0, it's a success + if points > 0 { + let successful_waves = (perf.success_rate as u128 * perf.total_waves_participated as u128 / 100) + 1; + perf.success_rate = (successful_waves * 100 / perf.total_waves_participated as u128) as u32; + } + + env.storage().persistent().set(&DataKey::ContributorPerformance(address), &perf); + } + + /// Adjust program difficulty based on wave performance + fn adjust_program_difficulty(env: &Env, program_id: u32, total_points: u32, current_difficulty: u32) { + // Performance thresholds for difficulty adjustment + const HIGH_PERFORMANCE_THRESHOLD: u32 = 1000; + const LOW_PERFORMANCE_THRESHOLD: u32 = 100; + const MAX_DIFFICULTY: u32 = 10; + const MIN_DIFFICULTY: u32 = 1; + + let new_difficulty = if total_points > HIGH_PERFORMANCE_THRESHOLD { + // High performance - increase difficulty + (current_difficulty + 1).min(MAX_DIFFICULTY) + } else if total_points < LOW_PERFORMANCE_THRESHOLD && current_difficulty > MIN_DIFFICULTY { + // Low performance - decrease difficulty + current_difficulty - 1 + } else { + // Maintain current difficulty + current_difficulty + }; + + if new_difficulty != current_difficulty { + env.storage().persistent().set(&DataKey::ProgramDifficulty(program_id), &new_difficulty); + + // Emit DifficultyAdjusted event + env.events().publish( + (symbol_short!("diff_adj"), program_id, current_difficulty, new_difficulty), + total_points, + ); + } + } + + /// Get contributor performance metrics + pub fn get_contributor_performance(env: Env, address: Address) -> Option { + env.storage().persistent().get(&DataKey::ContributorPerformance(address)) + } + + /// Get current difficulty level for a program + pub fn get_program_difficulty(env: Env, program_id: u32) -> u32 { + env.storage().persistent().get(&DataKey::ProgramDifficulty(program_id)).unwrap_or(1) + } + + /// Manually set difficulty level for a program (admin only) + pub fn set_program_difficulty(env: Env, program_id: u32, difficulty: u32) { + let admin: Address = env.storage().instance().get(&DataKey::Admin).expect("not initialized"); + admin.require_auth(); + + if difficulty < 1 || difficulty > 10 { + panic!("difficulty must be between 1 and 10"); + } + + env.storage().persistent().set(&DataKey::ProgramDifficulty(program_id), &difficulty); + } } // ─── Tests ──────────────────────────────────────────────────────────────────── @@ -370,6 +473,197 @@ mod tests { assert_eq!(records.len(), 1); assert_eq!(records.get(0).unwrap().points, 50); } + + #[test] + fn test_difficulty_initialization() { + let env = Env::default(); + env.mock_all_auths(); + let (client, _, _) = setup(&env); + + let config = ProgramConfig { + name: String::from_str(&env, "prog1"), + organizer: Address::generate(&env), + metadata: String::from_str(&env, "meta"), + funding_target: 1000, + }; + let admin: Address = client.get_admin(); + let program_id = client.register_program(&admin, &config); + + // Initial difficulty should be 1 + assert_eq!(client.get_program_difficulty(&program_id), 1); + + // Open wave should use initial difficulty + let wave_id = client.open_wave(&program_id); + let wave = client.get_wave(&wave_id).unwrap(); + assert_eq!(wave.difficulty_level, 1); + } + + #[test] + fn test_difficulty_increase_on_high_performance() { + let env = Env::default(); + env.mock_all_auths(); + let (client, _, _) = setup(&env); + + let config = ProgramConfig { + name: String::from_str(&env, "prog1"), + organizer: Address::generate(&env), + metadata: String::from_str(&env, "meta"), + funding_target: 1000, + }; + let admin: Address = client.get_admin(); + let program_id = client.register_program(&admin, &config); + let wave_id = client.open_wave(&program_id); + + // Close wave with high performance (> 1000 points) + client.close_wave(&wave_id, &1500); + + // Difficulty should increase to 2 + assert_eq!(client.get_program_difficulty(&program_id), 2); + + // Next wave should use new difficulty + let wave_id2 = client.open_wave(&program_id); + let wave2 = client.get_wave(&wave_id2).unwrap(); + assert_eq!(wave2.difficulty_level, 2); + } + + #[test] + fn test_difficulty_decrease_on_low_performance() { + let env = Env::default(); + env.mock_all_auths(); + let (client, _, _) = setup(&env); + + let config = ProgramConfig { + name: String::from_str(&env, "prog1"), + organizer: Address::generate(&env), + metadata: String::from_str(&env, "meta"), + funding_target: 1000, + }; + let admin: Address = client.get_admin(); + let program_id = client.register_program(&admin, &config); + + // Set initial difficulty to 3 + client.set_program_difficulty(&program_id, &3); + assert_eq!(client.get_program_difficulty(&program_id), 3); + + let wave_id = client.open_wave(&program_id); + + // Close wave with low performance (< 100 points) + client.close_wave(&wave_id, &50); + + // Difficulty should decrease to 2 + assert_eq!(client.get_program_difficulty(&program_id), 2); + } + + #[test] + fn test_difficulty_max_cap() { + let env = Env::default(); + env.mock_all_auths(); + let (client, _, _) = setup(&env); + + let config = ProgramConfig { + name: String::from_str(&env, "prog1"), + organizer: Address::generate(&env), + metadata: String::from_str(&env, "meta"), + funding_target: 1000, + }; + let admin: Address = client.get_admin(); + let program_id = client.register_program(&admin, &config); + + // Set difficulty to max (10) + client.set_program_difficulty(&program_id, &10); + assert_eq!(client.get_program_difficulty(&program_id), 10); + + let wave_id = client.open_wave(&program_id); + + // Close wave with extremely high performance + client.close_wave(&wave_id, &10000); + + // Difficulty should stay at max (10) + assert_eq!(client.get_program_difficulty(&program_id), 10); + } + + #[test] + fn test_contributor_performance_tracking() { + let env = Env::default(); + env.mock_all_auths(); + let (client, _, _) = setup(&env); + + let config = ProgramConfig { + name: String::from_str(&env, "prog1"), + organizer: Address::generate(&env), + metadata: String::from_str(&env, "meta"), + funding_target: 1000, + }; + let admin: Address = client.get_admin(); + let program_id = client.register_program(&admin, &config); + let wave_id = client.open_wave(&program_id); + + let contributor = Address::generate(&env); + + // Record first contribution + client.record_contribution(&wave_id, &contributor, &100); + + let perf = client.get_contributor_performance(&contributor).unwrap(); + assert_eq!(perf.total_waves_participated, 1); + assert_eq!(perf.total_points_earned, 100); + assert_eq!(perf.average_points_per_wave, 100); + + // Record second contribution + client.record_contribution(&wave_id, &contributor, &200); + + let perf = client.get_contributor_performance(&contributor).unwrap(); + assert_eq!(perf.total_waves_participated, 2); + assert_eq!(perf.total_points_earned, 300); + assert_eq!(perf.average_points_per_wave, 150); + } + + #[test] + #[should_panic(expected = "difficulty must be between 1 and 10")] + fn test_set_invalid_difficulty() { + let env = Env::default(); + env.mock_all_auths(); + let (client, _, _) = setup(&env); + + let config = ProgramConfig { + name: String::from_str(&env, "prog1"), + organizer: Address::generate(&env), + metadata: String::from_str(&env, "meta"), + funding_target: 1000, + }; + let admin: Address = client.get_admin(); + let program_id = client.register_program(&admin, &config); + + // Try to set invalid difficulty (0) + client.set_program_difficulty(&program_id, &0); + } + + #[test] + fn test_difficulty_maintained_on_moderate_performance() { + let env = Env::default(); + env.mock_all_auths(); + let (client, _, _) = setup(&env); + + let config = ProgramConfig { + name: String::from_str(&env, "prog1"), + organizer: Address::generate(&env), + metadata: String::from_str(&env, "meta"), + funding_target: 1000, + }; + let admin: Address = client.get_admin(); + let program_id = client.register_program(&admin, &config); + + // Set difficulty to 5 + client.set_program_difficulty(&program_id, &5); + assert_eq!(client.get_program_difficulty(&program_id), 5); + + let wave_id = client.open_wave(&program_id); + + // Close wave with moderate performance (between thresholds) + client.close_wave(&wave_id, &500); + + // Difficulty should remain at 5 + assert_eq!(client.get_program_difficulty(&program_id), 5); + } } #[cfg(test)] diff --git a/contracts/registry/src/test.rs b/contracts/registry/src/test.rs index ae7786d..76573e7 100644 --- a/contracts/registry/src/test.rs +++ b/contracts/registry/src/test.rs @@ -133,6 +133,7 @@ fn test_full_wave_lifecycle() { let wave = client.get_wave(&wave_id).expect("Wave should exist"); assert_eq!(wave.status, WaveStatus::Open); assert_eq!(wave.program_id, program_id); + assert_eq!(wave.difficulty_level, 1); // Default initial difficulty // 3. Close Wave let close_ts = 300000;