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
9 changes: 9 additions & 0 deletions contracts/teachlink/src/analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,15 @@ impl AnalyticsManager {

env.storage().instance().set(&BRIDGE_METRICS, &metrics);

// Audit: admin reset of metrics
let _ = crate::audit::AuditManager::create_audit_record(
env,
crate::types::OperationType::ConfigUpdate,
admin.clone(),
Bytes::new(env),
Bytes::new(env),
);

Ok(())
}

Expand Down
88 changes: 88 additions & 0 deletions contracts/teachlink/src/analytics_overflow_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#[cfg(test)]
mod tests {
use super::super::escrow_analytics::EscrowAnalyticsManager;
use super::super::notification::NotificationManager;
use super::super::notification_types::{NotificationChannel, NotificationContent};
use super::super::storage::{ESCROW_ANALYTICS, NOTIFICATION_COUNTER, NOTIFICATION_LOGS, NOTIFICATION_TRACKING, SCHEDULED_NOTIFICATIONS};
use super::super::types::EscrowMetrics;
use soroban_sdk::testutils::Ledger;
use soroban_sdk::{Address, Bytes, Env, Map};

fn setup_env() -> Env {
let env = Env::default();
env.mock_all_auths();
env
}

#[test]
fn escrow_overflow_triggers_resets_and_can_be_cleared() {
let env = setup_env();

let metrics = EscrowMetrics {
total_escrows: u64::MAX,
total_volume: i128::MAX,
total_disputes: u64::MAX,
total_resolved: u64::MAX,
average_resolution_time: 123,
resets: 0,
};

env.storage().instance().set(&ESCROW_ANALYTICS, &metrics);

// This should overflow both the u64 counter and the i128 volume
EscrowAnalyticsManager::update_creation(&env, 1);

let m = EscrowAnalyticsManager::get_metrics(&env);
assert_eq!(m.total_escrows, 0);
assert_eq!(m.total_volume, 0);
assert!(m.resets >= 1);

// Reset via admin (mocked auth)
let admin = Address::random(&env);
EscrowAnalyticsManager::reset_metrics(&env, admin.clone());

let m2 = EscrowAnalyticsManager::get_metrics(&env);
assert_eq!(m2.total_escrows, 0);
assert_eq!(m2.total_volume, 0);
assert_eq!(m2.resets, 0);
}

#[test]
fn notification_id_overflow_and_reset_behaviour() {
let env = setup_env();

// set counter to max
env.storage().instance().set(&NOTIFICATION_COUNTER, &u64::MAX);

let recipient = Address::random(&env);
let content = NotificationContent {
subject: Bytes::from_slice(&env, b"t"),
body: Bytes::from_slice(&env, b"b"),
data: Bytes::from_slice(&env, b"{}"),
localization: Map::new(&env),
};

let id = NotificationManager::send_notification(&env, recipient.clone(), NotificationChannel::InApp, content.clone()).unwrap();
// after overflow, implementation returns id 1 and stores 1
assert_eq!(id, 1u64);

let stored_counter: u64 = env.storage().instance().get(&NOTIFICATION_COUNTER).unwrap_or(0u64);
assert_eq!(stored_counter, 1u64);

// Now test reset_counters admin API
let admin = Address::random(&env);
NotificationManager::reset_counters(&env, admin.clone()).unwrap();

let c: u64 = env.storage().instance().get(&NOTIFICATION_COUNTER).unwrap_or(0u64);
assert_eq!(c, 0u64);

let logs: Map<u64, NotificationContent> = env.storage().instance().get(&NOTIFICATION_LOGS).unwrap_or_else(|| Map::new(&env));
assert_eq!(logs.len(), 0);

let tr: Map<u64, super::super::notification_types::NotificationTracking> = env.storage().instance().get(&NOTIFICATION_TRACKING).unwrap_or_else(|| Map::new(&env));
assert_eq!(tr.len(), 0);

let sch: Map<u64, super::super::notification_types::NotificationSchedule> = env.storage().instance().get(&SCHEDULED_NOTIFICATIONS).unwrap_or_else(|| Map::new(&env));
assert_eq!(sch.len(), 0);
}
}
63 changes: 63 additions & 0 deletions contracts/teachlink/src/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,15 @@ impl Bridge {
}
.publish(env);

// Audit log: record validator addition
let _ = crate::audit::AuditManager::log_validator_operation(
env,
true,
validator.clone(),
admin.clone(),
Bytes::new(env),
);

Ok(())
}

Expand Down Expand Up @@ -491,6 +500,15 @@ impl Bridge {
}
.publish(env);

// Audit log: record validator removal
let _ = crate::audit::AuditManager::log_validator_operation(
env,
false,
validator.clone(),
admin.clone(),
Bytes::new(env),
);

Ok(())
}

Expand Down Expand Up @@ -529,6 +547,15 @@ impl Bridge {
}
.publish(env);

// Audit: configuration change - supported chain added
let _ = crate::audit::AuditManager::create_audit_record(
env,
crate::types::OperationType::ConfigUpdate,
admin.clone(),
Bytes::from_slice(env, &chain_id.to_be_bytes()),
Bytes::new(env),
);

Ok(())
}

Expand Down Expand Up @@ -560,6 +587,15 @@ impl Bridge {
}
.publish(env);

// Audit: configuration change - supported chain removed
let _ = crate::audit::AuditManager::create_audit_record(
env,
crate::types::OperationType::ConfigUpdate,
admin.clone(),
Bytes::from_slice(env, &chain_id.to_be_bytes()),
Bytes::new(env),
);

Ok(())
}

Expand Down Expand Up @@ -591,6 +627,15 @@ impl Bridge {
}
.publish(env);

// Audit: fee update
let _ = crate::audit::AuditManager::create_audit_record(
env,
crate::types::OperationType::FeeUpdate,
admin.clone(),
Bytes::from_slice(env, &fee.to_be_bytes()),
Bytes::new(env),
);

Ok(())
}

Expand Down Expand Up @@ -628,6 +673,15 @@ impl Bridge {
}
.publish(env);

// Audit: fee recipient change
let _ = crate::audit::AuditManager::create_audit_record(
env,
crate::types::OperationType::ConfigUpdate,
admin.clone(),
Bytes::new(env),
Bytes::new(env),
);

Ok(())
}

Expand Down Expand Up @@ -666,6 +720,15 @@ impl Bridge {
}
.publish(env);

// Audit: min validators updated
let _ = crate::audit::AuditManager::create_audit_record(
env,
crate::types::OperationType::ConfigUpdate,
admin.clone(),
Bytes::from_slice(env, &min_validators.to_be_bytes()),
Bytes::new(env),
);

Ok(())
}

Expand Down
10 changes: 9 additions & 1 deletion contracts/teachlink/src/performance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::events::PerfCacheInvalidatedEvent;
use crate::events::PerfMetricsComputedEvent;
use crate::storage::{PERF_CACHE, PERF_TS};
use crate::types::CachedBridgeSummary;
use soroban_sdk::{Address, Env};
use soroban_sdk::{Address, Bytes, Env};

/// Cache TTL in ledger seconds (1 hour).
pub const CACHE_TTL_SECS: u64 = 3_600;
Expand Down Expand Up @@ -70,6 +70,14 @@ impl PerformanceManager {
invalidated_at: env.ledger().timestamp(),
}
.publish(env);
// Audit: performance cache invalidation
let _ = crate::audit::AuditManager::create_audit_record(
env,
crate::types::OperationType::ConfigUpdate,
admin.clone(),
Bytes::new(env),
Bytes::new(env),
);
Ok(())
}
}
11 changes: 10 additions & 1 deletion contracts/teachlink/src/rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::storage::{
use crate::types::{RewardRate, UserReward};
use crate::validation::RewardsValidator;

use soroban_sdk::{symbol_short, vec, Address, Env, IntoVal, Map, String};
use soroban_sdk::{symbol_short, vec, Address, Bytes, Env, IntoVal, Map, String};

// Maximum reward amount to prevent overflow (i128::MAX / 2)
const MAX_REWARD_AMOUNT: i128 = 170141183460469231731687303715884105727;
Expand Down Expand Up @@ -310,6 +310,15 @@ impl Rewards {
rewards_admin.require_auth();

env.storage().instance().set(&REWARDS_ADMIN, &new_admin);

// Audit: rewards admin updated
let _ = crate::audit::AuditManager::create_audit_record(
env,
crate::types::OperationType::ConfigUpdate,
rewards_admin.clone(),
Bytes::new(env),
Bytes::new(env),
);
}

// ==========================
Expand Down
Loading