Skip to content

Commit c91ae0f

Browse files
committed
feat: implement module interface standardization
- Add docs/MODULE_INTERFACE_STANDARDS.md defining 8 rules: module-level doc comment, manager-struct pattern, section comments, #[must_use] on pure getters, error handling, auth pattern, compliance table, and a minimal example module - Refactor reputation.rs: free functions -> ReputationManager struct, add module doc, section comments (Mutations/Queries/Internal), #[must_use] on get_reputation - Add module doc, Mutations/Queries section comments, and #[must_use] on all 3 getters in score.rs - Add module doc, standardize section comments (Initialization/Mutations/Admin/Queries), and #[must_use] on all 5 getters in rewards.rs - Update all 4 reputation call sites in lib.rs from free functions to ReputationManager::* methods
1 parent 6b60f23 commit c91ae0f

6 files changed

Lines changed: 286 additions & 103 deletions

File tree

contracts/teachlink/src/events.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -712,7 +712,14 @@ pub struct ChainMetricsUpdatedEvent {
712712
pub average_fee: i128,
713713
pub updated_at: u64,
714714
}
715-
715+
Description
716+
No comprehensive monitoring dashboard exists.
717+
718+
Acceptance Criteria
719+
Create real-time metrics
720+
Track historical trends
721+
Manage alerts
722+
Provide insights
716723
/// Emitted when sustainability metrics are updated.
717724
#[contractevent]
718725
#[derive(Clone, Debug)]

contracts/teachlink/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,19 +1333,19 @@ impl TeachLinkBridge {
13331333
// ========== Reputation Functions (main) ==========
13341334

13351335
pub fn update_participation(env: Env, user: Address, points: u32) {
1336-
reputation::update_participation(&env, user, points);
1336+
reputation::ReputationManager::update_participation(&env, user, points);
13371337
}
13381338

13391339
pub fn update_course_progress(env: Env, user: Address, is_completion: bool) {
1340-
reputation::update_course_progress(&env, user, is_completion);
1340+
reputation::ReputationManager::update_course_progress(&env, user, is_completion);
13411341
}
13421342

13431343
pub fn rate_contribution(env: Env, user: Address, rating: u32) {
1344-
reputation::rate_contribution(&env, user, rating);
1344+
reputation::ReputationManager::rate_contribution(&env, user, rating);
13451345
}
13461346

13471347
pub fn get_user_reputation(env: Env, user: Address) -> types::UserReputation {
1348-
reputation::get_reputation(&env, &user)
1348+
reputation::ReputationManager::get_reputation(&env, &user)
13491349
}
13501350

13511351
// ========== Content Tokenization Functions ==========
Lines changed: 99 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
//! User reputation tracking.
2+
//!
3+
//! Responsibilities:
4+
//! - Track participation score, course progress, and contribution quality
5+
//! - Compute completion rate in basis points
6+
//! - Emit events on every state change
7+
//! - Expose read-only views for reputation data
8+
19
use crate::events::{
210
ContributionRatedEvent, CourseProgressUpdatedEvent, ParticipationUpdatedEvent,
311
};
@@ -7,102 +15,107 @@ use soroban_sdk::{symbol_short, Address, Env, Symbol};
715
const BASIS_POINTS: u32 = 10000;
816
const REPUTATION: Symbol = symbol_short!("reptn");
917

10-
pub fn update_participation(env: &Env, user: Address, points: u32) {
11-
user.require_auth();
12-
let mut reputation = get_reputation(env, &user);
13-
reputation.participation_score += points;
14-
reputation.last_update = env.ledger().timestamp();
15-
set_reputation(env, &user, &reputation);
16-
17-
// Emit event
18-
ParticipationUpdatedEvent {
19-
user: user.clone(),
20-
points_added: points,
21-
new_participation_score: reputation.participation_score,
22-
updated_at: env.ledger().timestamp(),
18+
/// Manages user reputation scores.
19+
pub struct ReputationManager;
20+
21+
impl ReputationManager {
22+
// ===== Mutations =====
23+
24+
/// Add participation points for a user.
25+
pub fn update_participation(env: &Env, user: Address, points: u32) {
26+
user.require_auth();
27+
let mut reputation = Self::get_reputation(env, &user);
28+
reputation.participation_score += points;
29+
reputation.last_update = env.ledger().timestamp();
30+
Self::set_reputation(env, &user, &reputation);
31+
32+
ParticipationUpdatedEvent {
33+
user: user.clone(),
34+
points_added: points,
35+
new_participation_score: reputation.participation_score,
36+
updated_at: reputation.last_update,
37+
}
38+
.publish(env);
2339
}
24-
.publish(env);
25-
}
2640

27-
pub fn update_course_progress(env: &Env, user: Address, is_completion: bool) {
28-
user.require_auth();
29-
let mut reputation = get_reputation(env, &user);
30-
31-
if is_completion {
32-
reputation.total_courses_completed += 1;
33-
// Logic: You can't complete a course without starting it,
34-
// but simple increment here assumes course started logic handled elsewhere or previously
35-
if reputation.total_courses_started < reputation.total_courses_completed {
36-
reputation.total_courses_started = reputation.total_courses_completed;
41+
/// Record a course start or completion for a user.
42+
pub fn update_course_progress(env: &Env, user: Address, is_completion: bool) {
43+
user.require_auth();
44+
let mut reputation = Self::get_reputation(env, &user);
45+
46+
if is_completion {
47+
reputation.total_courses_completed += 1;
48+
if reputation.total_courses_started < reputation.total_courses_completed {
49+
reputation.total_courses_started = reputation.total_courses_completed;
50+
}
51+
} else {
52+
reputation.total_courses_started += 1;
3753
}
38-
} else {
39-
reputation.total_courses_started += 1;
40-
}
4154

42-
if reputation.total_courses_started > 0 {
43-
reputation.completion_rate =
44-
(reputation.total_courses_completed * BASIS_POINTS) / reputation.total_courses_started;
45-
}
55+
if reputation.total_courses_started > 0 {
56+
reputation.completion_rate = (reputation.total_courses_completed * BASIS_POINTS)
57+
/ reputation.total_courses_started;
58+
}
4659

47-
reputation.last_update = env.ledger().timestamp();
48-
set_reputation(env, &user, &reputation);
60+
reputation.last_update = env.ledger().timestamp();
61+
Self::set_reputation(env, &user, &reputation);
4962

50-
// Emit event
51-
CourseProgressUpdatedEvent {
52-
user: user.clone(),
53-
total_courses_started: reputation.total_courses_started,
54-
total_courses_completed: reputation.total_courses_completed,
55-
completion_rate: reputation.completion_rate,
56-
updated_at: env.ledger().timestamp(),
63+
CourseProgressUpdatedEvent {
64+
user: user.clone(),
65+
total_courses_started: reputation.total_courses_started,
66+
total_courses_completed: reputation.total_courses_completed,
67+
completion_rate: reputation.completion_rate,
68+
updated_at: reputation.last_update,
69+
}
70+
.publish(env);
5771
}
58-
.publish(env);
59-
}
6072

61-
pub fn rate_contribution(env: &Env, user: Address, rating: u32) {
62-
// Rating should be 0-5 scaled (e.g. 0-100 or 0-500)
63-
// Here assuming 0-5
64-
assert!(rating <= 5, "Rating must be between 0 and 5");
65-
66-
let mut reputation = get_reputation(env, &user);
67-
68-
let current_total_quality = reputation.contribution_quality * reputation.total_contributions;
69-
reputation.total_contributions += 1;
70-
71-
// Weighted Average
72-
reputation.contribution_quality =
73-
(current_total_quality + rating) / reputation.total_contributions;
74-
reputation.last_update = env.ledger().timestamp();
75-
76-
set_reputation(env, &user, &reputation);
73+
/// Record a contribution rating (0–5) for a user.
74+
pub fn rate_contribution(env: &Env, user: Address, rating: u32) {
75+
assert!(rating <= 5, "Rating must be between 0 and 5");
76+
77+
let mut reputation = Self::get_reputation(env, &user);
78+
let current_total_quality = reputation.contribution_quality * reputation.total_contributions;
79+
reputation.total_contributions += 1;
80+
reputation.contribution_quality =
81+
(current_total_quality + rating) / reputation.total_contributions;
82+
reputation.last_update = env.ledger().timestamp();
83+
Self::set_reputation(env, &user, &reputation);
84+
85+
ContributionRatedEvent {
86+
user: user.clone(),
87+
rating,
88+
new_contribution_quality: reputation.contribution_quality,
89+
total_contributions: reputation.total_contributions,
90+
rated_at: reputation.last_update,
91+
}
92+
.publish(env);
93+
}
7794

78-
// Emit event
79-
ContributionRatedEvent {
80-
user: user.clone(),
81-
rating,
82-
new_contribution_quality: reputation.contribution_quality,
83-
total_contributions: reputation.total_contributions,
84-
rated_at: env.ledger().timestamp(),
95+
// ===== Queries =====
96+
97+
/// Return the reputation record for a user, defaulting to zeroes.
98+
#[must_use]
99+
pub fn get_reputation(env: &Env, user: &Address) -> UserReputation {
100+
env.storage()
101+
.persistent()
102+
.get(&(REPUTATION, user.clone()))
103+
.unwrap_or(UserReputation {
104+
participation_score: 0,
105+
completion_rate: 0,
106+
contribution_quality: 0,
107+
total_courses_started: 0,
108+
total_courses_completed: 0,
109+
total_contributions: 0,
110+
last_update: 0,
111+
})
85112
}
86-
.publish(env);
87-
}
88113

89-
pub fn get_reputation(env: &Env, user: &Address) -> UserReputation {
90-
env.storage()
91-
.persistent()
92-
.get(&(REPUTATION, user.clone()))
93-
.unwrap_or(UserReputation {
94-
participation_score: 0,
95-
completion_rate: 0,
96-
contribution_quality: 0,
97-
total_courses_started: 0,
98-
total_courses_completed: 0,
99-
total_contributions: 0,
100-
last_update: 0,
101-
})
102-
}
114+
// ===== Internal =====
103115

104-
fn set_reputation(env: &Env, user: &Address, reputation: &UserReputation) {
105-
env.storage()
106-
.persistent()
107-
.set(&(REPUTATION, user.clone()), reputation);
116+
fn set_reputation(env: &Env, user: &Address, reputation: &UserReputation) {
117+
env.storage()
118+
.persistent()
119+
.set(&(REPUTATION, user.clone()), reputation);
120+
}
108121
}

contracts/teachlink/src/rewards.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
//! Reward pool management and distribution.
2+
//!
3+
//! Responsibilities:
4+
//! - Initialize and fund the reward pool
5+
//! - Issue rewards to users (admin-gated)
6+
//! - Allow users to claim pending rewards
7+
//! - Expose read-only views for pool and user reward state
8+
19
use crate::errors::RewardsError;
210
use crate::events::{RewardClaimedEvent, RewardIssuedEvent, RewardPoolFundedEvent};
311
use crate::reentrancy;
@@ -16,6 +24,8 @@ const MAX_REWARD_AMOUNT: i128 = 170141183460469231731687303715884105727;
1624
pub struct Rewards;
1725

1826
impl Rewards {
27+
// ===== Initialization =====
28+
1929
/// Initialize the rewards system
2030
pub fn initialize_rewards(
2131
env: &Env,
@@ -40,9 +50,7 @@ impl Rewards {
4050
Ok(())
4151
}
4252

43-
// ==========================
44-
// Pool Management
45-
// ==========================
53+
// ===== Mutations =====
4654

4755
pub fn fund_reward_pool(env: &Env, funder: Address, amount: i128) -> Result<(), RewardsError> {
4856
#[cfg(not(test))]
@@ -184,9 +192,7 @@ impl Rewards {
184192
Ok(())
185193
}
186194

187-
// ==========================
188-
// Claiming
189-
// ==========================
195+
// ===== Mutations (continued) =====
190196

191197
pub fn claim_rewards(env: &Env, user: Address) -> Result<(), RewardsError> {
192198
#[cfg(not(test))]
@@ -263,9 +269,7 @@ impl Rewards {
263269
)
264270
}
265271

266-
// ==========================
267-
// Admin Functions
268-
// ==========================
272+
// ===== Admin =====
269273

270274
/// Set reward rate for a specific reward type
271275
pub fn set_reward_rate(
@@ -312,10 +316,9 @@ impl Rewards {
312316
env.storage().instance().set(&REWARDS_ADMIN, &new_admin);
313317
}
314318

315-
// ==========================
316-
// View Functions
317-
// ==========================
319+
// ===== Queries =====
318320

321+
#[must_use]
319322
pub fn get_user_rewards(env: &Env, user: Address) -> Option<UserReward> {
320323
let user_rewards: Map<Address, UserReward> = env
321324
.storage()
@@ -325,17 +328,20 @@ impl Rewards {
325328
user_rewards.get(user)
326329
}
327330

331+
#[must_use]
328332
pub fn get_reward_pool_balance(env: &Env) -> i128 {
329333
env.storage().instance().get(&REWARD_POOL).unwrap_or(0)
330334
}
331335

336+
#[must_use]
332337
pub fn get_total_rewards_issued(env: &Env) -> i128 {
333338
env.storage()
334339
.instance()
335340
.get(&TOTAL_REWARDS_ISSUED)
336341
.unwrap_or(0)
337342
}
338343

344+
#[must_use]
339345
pub fn get_reward_rate(env: &Env, reward_type: String) -> Option<RewardRate> {
340346
let reward_rates: Map<String, RewardRate> = env
341347
.storage()
@@ -345,6 +351,7 @@ impl Rewards {
345351
reward_rates.get(reward_type)
346352
}
347353

354+
#[must_use]
348355
pub fn get_rewards_admin(env: &Env) -> Address {
349356
// SAFETY: REWARDS_ADMIN is always set during initialize_rewards
350357
env.storage().instance().get(&REWARDS_ADMIN).unwrap()

contracts/teachlink/src/score.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
//! Credit score calculation from on-chain activities.
2+
//!
3+
//! Responsibilities:
4+
//! - Award points for course completions and contributions
5+
//! - Maintain per-user score, course list, and contribution history
6+
//! - Emit events on every state change
7+
//! - Expose read-only views for scores and history
8+
19
use crate::events::{ContributionRecordedEvent, CourseCompletedEvent, CreditScoreUpdatedEvent};
210
use crate::storage::{CONTRIBUTIONS, COURSE_COMPLETIONS, CREDIT_SCORE};
311
use crate::types::{Contribution, ContributionType};
@@ -6,6 +14,8 @@ use soroban_sdk::{Address, Bytes, Env, Vec};
614
pub struct ScoreManager;
715

816
impl ScoreManager {
17+
// ===== Mutations =====
18+
919
/// Update the user's score by adding points
1020
pub fn update_score(env: &Env, user: Address, points: u64) {
1121
// Use a tuple key (CREDIT_SCORE, user) for mapping user to score
@@ -82,7 +92,10 @@ impl ScoreManager {
8292
.publish(env);
8393
}
8494

95+
// ===== Queries =====
96+
8597
/// Get the user's current credit score
98+
#[must_use]
8699
pub fn get_score(env: &Env, user: Address) -> u64 {
87100
env.storage()
88101
.persistent()
@@ -91,6 +104,7 @@ impl ScoreManager {
91104
}
92105

93106
/// Get valid course completions
107+
#[must_use]
94108
pub fn get_courses(env: &Env, user: Address) -> Vec<u64> {
95109
env.storage()
96110
.persistent()
@@ -99,6 +113,7 @@ impl ScoreManager {
99113
}
100114

101115
/// Get user contributions
116+
#[must_use]
102117
pub fn get_contributions(env: &Env, user: Address) -> Vec<Contribution> {
103118
env.storage()
104119
.persistent()

0 commit comments

Comments
 (0)