Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 296 additions & 2 deletions contracts/registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -46,6 +57,8 @@ pub enum DataKey {
WaveCounter,
Contributions(Address, u32), // contributor, wave_id -> contribution
History(Address), // contributor -> Vec<wave_id>
ContributorPerformance(Address), // contributor -> performance metrics
ProgramDifficulty(u32), // program_id -> current difficulty level
}

#[contract]
Expand Down Expand Up @@ -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;
Expand All @@ -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(),
);

Expand All @@ -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),
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<ContributorPerformance> {
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 ────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -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)]
Expand Down
1 change: 1 addition & 0 deletions contracts/registry/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down