@@ -31,6 +31,17 @@ pub struct WaveMeta {
3131 pub closed_at : u64 ,
3232 pub total_points : u32 ,
3333 pub status : WaveStatus ,
34+ pub difficulty_level : u32 ,
35+ }
36+
37+ #[ contracttype]
38+ #[ derive( Clone , Debug , Eq , PartialEq ) ]
39+ pub struct ContributorPerformance {
40+ pub total_waves_participated : u32 ,
41+ pub total_points_earned : u32 ,
42+ pub average_points_per_wave : u32 ,
43+ pub success_rate : u32 ,
44+ pub last_updated : u64 ,
3445}
3546
3647/// Storage keys for the registry contract state.
@@ -46,6 +57,8 @@ pub enum DataKey {
4657 WaveCounter ,
4758 Contributions ( Address , u32 ) , // contributor, wave_id -> contribution
4859 History ( Address ) , // contributor -> Vec<wave_id>
60+ ContributorPerformance ( Address ) , // contributor -> performance metrics
61+ ProgramDifficulty ( u32 ) , // program_id -> current difficulty level
4962}
5063
5164#[ contract]
@@ -118,6 +131,9 @@ impl RegistryContract {
118131 panic ! ( "program doesn't exist" ) ;
119132 }
120133
134+ // Get or initialize difficulty level for the program
135+ let difficulty_level: u32 = env. storage ( ) . persistent ( ) . get ( & DataKey :: ProgramDifficulty ( program_id) ) . unwrap_or ( 1 ) ;
136+
121137 // Increment global wave ID
122138 let mut counter: u32 = env. storage ( ) . instance ( ) . get ( & DataKey :: WaveCounter ) . unwrap_or ( 0 ) ;
123139 counter += 1 ;
@@ -131,13 +147,14 @@ impl RegistryContract {
131147 closed_at : 0 ,
132148 total_points : 0 ,
133149 status : WaveStatus :: Open ,
150+ difficulty_level,
134151 } ;
135152
136153 env. storage ( ) . persistent ( ) . set ( & DataKey :: Waves ( wave_id) , & wave) ;
137154
138155 // Emit WaveOpened event
139156 env. events ( ) . publish (
140- ( symbol_short ! ( "wave_open" ) , program_id, wave_id) ,
157+ ( symbol_short ! ( "wave_open" ) , program_id, wave_id, difficulty_level ) ,
141158 env. ledger ( ) . timestamp ( ) ,
142159 ) ;
143160
@@ -162,6 +179,9 @@ impl RegistryContract {
162179
163180 env. storage ( ) . persistent ( ) . set ( & DataKey :: Waves ( wave_id) , & wave) ;
164181
182+ // Adjust difficulty based on overall performance
183+ Self :: adjust_program_difficulty ( & env, wave. program_id , total_points, wave. difficulty_level ) ;
184+
165185 // Emit WaveClosed event
166186 env. events ( ) . publish (
167187 ( symbol_short ! ( "wave_cls" ) , wave_id, total_points) ,
@@ -203,8 +223,11 @@ impl RegistryContract {
203223
204224 if !history. contains ( wave_id) {
205225 history. push_back ( wave_id) ;
206- env. storage ( ) . persistent ( ) . set ( & DataKey :: History ( address) , & history) ;
226+ env. storage ( ) . persistent ( ) . set ( & DataKey :: History ( address. clone ( ) ) , & history) ;
207227 }
228+
229+ // Update contributor performance metrics
230+ Self :: update_contributor_performance ( & env, address. clone ( ) , points) ;
208231 }
209232
210233 /// Returns the full contribution history for a contributor.
@@ -245,6 +268,86 @@ impl RegistryContract {
245268 admin. require_auth ( ) ;
246269 env. storage ( ) . instance ( ) . set ( & DataKey :: SettlementContract , & new_settlement) ;
247270 }
271+
272+ /// Update contributor performance metrics after recording a contribution
273+ fn update_contributor_performance ( env : & Env , address : Address , points : u32 ) {
274+ let mut perf: ContributorPerformance = env
275+ . storage ( )
276+ . persistent ( )
277+ . get ( & DataKey :: ContributorPerformance ( address. clone ( ) ) )
278+ . unwrap_or_else ( || ContributorPerformance {
279+ total_waves_participated : 0 ,
280+ total_points_earned : 0 ,
281+ average_points_per_wave : 0 ,
282+ success_rate : 100 ,
283+ last_updated : 0 ,
284+ } ) ;
285+
286+ perf. total_waves_participated += 1 ;
287+ perf. total_points_earned += points;
288+ perf. average_points_per_wave = perf. total_points_earned / perf. total_waves_participated ;
289+ perf. last_updated = env. ledger ( ) . timestamp ( ) ;
290+
291+ // Simple success rate calculation: if points > 0, it's a success
292+ if points > 0 {
293+ let successful_waves = ( perf. success_rate as u128 * perf. total_waves_participated as u128 / 100 ) + 1 ;
294+ perf. success_rate = ( successful_waves * 100 / perf. total_waves_participated as u128 ) as u32 ;
295+ }
296+
297+ env. storage ( ) . persistent ( ) . set ( & DataKey :: ContributorPerformance ( address) , & perf) ;
298+ }
299+
300+ /// Adjust program difficulty based on wave performance
301+ fn adjust_program_difficulty ( env : & Env , program_id : u32 , total_points : u32 , current_difficulty : u32 ) {
302+ // Performance thresholds for difficulty adjustment
303+ const HIGH_PERFORMANCE_THRESHOLD : u32 = 1000 ;
304+ const LOW_PERFORMANCE_THRESHOLD : u32 = 100 ;
305+ const MAX_DIFFICULTY : u32 = 10 ;
306+ const MIN_DIFFICULTY : u32 = 1 ;
307+
308+ let new_difficulty = if total_points > HIGH_PERFORMANCE_THRESHOLD {
309+ // High performance - increase difficulty
310+ ( current_difficulty + 1 ) . min ( MAX_DIFFICULTY )
311+ } else if total_points < LOW_PERFORMANCE_THRESHOLD && current_difficulty > MIN_DIFFICULTY {
312+ // Low performance - decrease difficulty
313+ current_difficulty - 1
314+ } else {
315+ // Maintain current difficulty
316+ current_difficulty
317+ } ;
318+
319+ if new_difficulty != current_difficulty {
320+ env. storage ( ) . persistent ( ) . set ( & DataKey :: ProgramDifficulty ( program_id) , & new_difficulty) ;
321+
322+ // Emit DifficultyAdjusted event
323+ env. events ( ) . publish (
324+ ( symbol_short ! ( "diff_adj" ) , program_id, current_difficulty, new_difficulty) ,
325+ total_points,
326+ ) ;
327+ }
328+ }
329+
330+ /// Get contributor performance metrics
331+ pub fn get_contributor_performance ( env : Env , address : Address ) -> Option < ContributorPerformance > {
332+ env. storage ( ) . persistent ( ) . get ( & DataKey :: ContributorPerformance ( address) )
333+ }
334+
335+ /// Get current difficulty level for a program
336+ pub fn get_program_difficulty ( env : Env , program_id : u32 ) -> u32 {
337+ env. storage ( ) . persistent ( ) . get ( & DataKey :: ProgramDifficulty ( program_id) ) . unwrap_or ( 1 )
338+ }
339+
340+ /// Manually set difficulty level for a program (admin only)
341+ pub fn set_program_difficulty ( env : Env , program_id : u32 , difficulty : u32 ) {
342+ let admin: Address = env. storage ( ) . instance ( ) . get ( & DataKey :: Admin ) . expect ( "not initialized" ) ;
343+ admin. require_auth ( ) ;
344+
345+ if difficulty < 1 || difficulty > 10 {
346+ panic ! ( "difficulty must be between 1 and 10" ) ;
347+ }
348+
349+ env. storage ( ) . persistent ( ) . set ( & DataKey :: ProgramDifficulty ( program_id) , & difficulty) ;
350+ }
248351}
249352
250353// ─── Tests ────────────────────────────────────────────────────────────────────
@@ -370,6 +473,197 @@ mod tests {
370473 assert_eq ! ( records. len( ) , 1 ) ;
371474 assert_eq ! ( records. get( 0 ) . unwrap( ) . points, 50 ) ;
372475 }
476+
477+ #[ test]
478+ fn test_difficulty_initialization ( ) {
479+ let env = Env :: default ( ) ;
480+ env. mock_all_auths ( ) ;
481+ let ( client, _, _) = setup ( & env) ;
482+
483+ let config = ProgramConfig {
484+ name : String :: from_str ( & env, "prog1" ) ,
485+ organizer : Address :: generate ( & env) ,
486+ metadata : String :: from_str ( & env, "meta" ) ,
487+ funding_target : 1000 ,
488+ } ;
489+ let admin: Address = client. get_admin ( ) ;
490+ let program_id = client. register_program ( & admin, & config) ;
491+
492+ // Initial difficulty should be 1
493+ assert_eq ! ( client. get_program_difficulty( & program_id) , 1 ) ;
494+
495+ // Open wave should use initial difficulty
496+ let wave_id = client. open_wave ( & program_id) ;
497+ let wave = client. get_wave ( & wave_id) . unwrap ( ) ;
498+ assert_eq ! ( wave. difficulty_level, 1 ) ;
499+ }
500+
501+ #[ test]
502+ fn test_difficulty_increase_on_high_performance ( ) {
503+ let env = Env :: default ( ) ;
504+ env. mock_all_auths ( ) ;
505+ let ( client, _, _) = setup ( & env) ;
506+
507+ let config = ProgramConfig {
508+ name : String :: from_str ( & env, "prog1" ) ,
509+ organizer : Address :: generate ( & env) ,
510+ metadata : String :: from_str ( & env, "meta" ) ,
511+ funding_target : 1000 ,
512+ } ;
513+ let admin: Address = client. get_admin ( ) ;
514+ let program_id = client. register_program ( & admin, & config) ;
515+ let wave_id = client. open_wave ( & program_id) ;
516+
517+ // Close wave with high performance (> 1000 points)
518+ client. close_wave ( & wave_id, & 1500 ) ;
519+
520+ // Difficulty should increase to 2
521+ assert_eq ! ( client. get_program_difficulty( & program_id) , 2 ) ;
522+
523+ // Next wave should use new difficulty
524+ let wave_id2 = client. open_wave ( & program_id) ;
525+ let wave2 = client. get_wave ( & wave_id2) . unwrap ( ) ;
526+ assert_eq ! ( wave2. difficulty_level, 2 ) ;
527+ }
528+
529+ #[ test]
530+ fn test_difficulty_decrease_on_low_performance ( ) {
531+ let env = Env :: default ( ) ;
532+ env. mock_all_auths ( ) ;
533+ let ( client, _, _) = setup ( & env) ;
534+
535+ let config = ProgramConfig {
536+ name : String :: from_str ( & env, "prog1" ) ,
537+ organizer : Address :: generate ( & env) ,
538+ metadata : String :: from_str ( & env, "meta" ) ,
539+ funding_target : 1000 ,
540+ } ;
541+ let admin: Address = client. get_admin ( ) ;
542+ let program_id = client. register_program ( & admin, & config) ;
543+
544+ // Set initial difficulty to 3
545+ client. set_program_difficulty ( & program_id, & 3 ) ;
546+ assert_eq ! ( client. get_program_difficulty( & program_id) , 3 ) ;
547+
548+ let wave_id = client. open_wave ( & program_id) ;
549+
550+ // Close wave with low performance (< 100 points)
551+ client. close_wave ( & wave_id, & 50 ) ;
552+
553+ // Difficulty should decrease to 2
554+ assert_eq ! ( client. get_program_difficulty( & program_id) , 2 ) ;
555+ }
556+
557+ #[ test]
558+ fn test_difficulty_max_cap ( ) {
559+ let env = Env :: default ( ) ;
560+ env. mock_all_auths ( ) ;
561+ let ( client, _, _) = setup ( & env) ;
562+
563+ let config = ProgramConfig {
564+ name : String :: from_str ( & env, "prog1" ) ,
565+ organizer : Address :: generate ( & env) ,
566+ metadata : String :: from_str ( & env, "meta" ) ,
567+ funding_target : 1000 ,
568+ } ;
569+ let admin: Address = client. get_admin ( ) ;
570+ let program_id = client. register_program ( & admin, & config) ;
571+
572+ // Set difficulty to max (10)
573+ client. set_program_difficulty ( & program_id, & 10 ) ;
574+ assert_eq ! ( client. get_program_difficulty( & program_id) , 10 ) ;
575+
576+ let wave_id = client. open_wave ( & program_id) ;
577+
578+ // Close wave with extremely high performance
579+ client. close_wave ( & wave_id, & 10000 ) ;
580+
581+ // Difficulty should stay at max (10)
582+ assert_eq ! ( client. get_program_difficulty( & program_id) , 10 ) ;
583+ }
584+
585+ #[ test]
586+ fn test_contributor_performance_tracking ( ) {
587+ let env = Env :: default ( ) ;
588+ env. mock_all_auths ( ) ;
589+ let ( client, _, _) = setup ( & env) ;
590+
591+ let config = ProgramConfig {
592+ name : String :: from_str ( & env, "prog1" ) ,
593+ organizer : Address :: generate ( & env) ,
594+ metadata : String :: from_str ( & env, "meta" ) ,
595+ funding_target : 1000 ,
596+ } ;
597+ let admin: Address = client. get_admin ( ) ;
598+ let program_id = client. register_program ( & admin, & config) ;
599+ let wave_id = client. open_wave ( & program_id) ;
600+
601+ let contributor = Address :: generate ( & env) ;
602+
603+ // Record first contribution
604+ client. record_contribution ( & wave_id, & contributor, & 100 ) ;
605+
606+ let perf = client. get_contributor_performance ( & contributor) . unwrap ( ) ;
607+ assert_eq ! ( perf. total_waves_participated, 1 ) ;
608+ assert_eq ! ( perf. total_points_earned, 100 ) ;
609+ assert_eq ! ( perf. average_points_per_wave, 100 ) ;
610+
611+ // Record second contribution
612+ client. record_contribution ( & wave_id, & contributor, & 200 ) ;
613+
614+ let perf = client. get_contributor_performance ( & contributor) . unwrap ( ) ;
615+ assert_eq ! ( perf. total_waves_participated, 2 ) ;
616+ assert_eq ! ( perf. total_points_earned, 300 ) ;
617+ assert_eq ! ( perf. average_points_per_wave, 150 ) ;
618+ }
619+
620+ #[ test]
621+ #[ should_panic( expected = "difficulty must be between 1 and 10" ) ]
622+ fn test_set_invalid_difficulty ( ) {
623+ let env = Env :: default ( ) ;
624+ env. mock_all_auths ( ) ;
625+ let ( client, _, _) = setup ( & env) ;
626+
627+ let config = ProgramConfig {
628+ name : String :: from_str ( & env, "prog1" ) ,
629+ organizer : Address :: generate ( & env) ,
630+ metadata : String :: from_str ( & env, "meta" ) ,
631+ funding_target : 1000 ,
632+ } ;
633+ let admin: Address = client. get_admin ( ) ;
634+ let program_id = client. register_program ( & admin, & config) ;
635+
636+ // Try to set invalid difficulty (0)
637+ client. set_program_difficulty ( & program_id, & 0 ) ;
638+ }
639+
640+ #[ test]
641+ fn test_difficulty_maintained_on_moderate_performance ( ) {
642+ let env = Env :: default ( ) ;
643+ env. mock_all_auths ( ) ;
644+ let ( client, _, _) = setup ( & env) ;
645+
646+ let config = ProgramConfig {
647+ name : String :: from_str ( & env, "prog1" ) ,
648+ organizer : Address :: generate ( & env) ,
649+ metadata : String :: from_str ( & env, "meta" ) ,
650+ funding_target : 1000 ,
651+ } ;
652+ let admin: Address = client. get_admin ( ) ;
653+ let program_id = client. register_program ( & admin, & config) ;
654+
655+ // Set difficulty to 5
656+ client. set_program_difficulty ( & program_id, & 5 ) ;
657+ assert_eq ! ( client. get_program_difficulty( & program_id) , 5 ) ;
658+
659+ let wave_id = client. open_wave ( & program_id) ;
660+
661+ // Close wave with moderate performance (between thresholds)
662+ client. close_wave ( & wave_id, & 500 ) ;
663+
664+ // Difficulty should remain at 5
665+ assert_eq ! ( client. get_program_difficulty( & program_id) , 5 ) ;
666+ }
373667}
374668
375669#[ cfg( test) ]
0 commit comments