Skip to content

Commit 93fb947

Browse files
authored
Merge pull request #561 from DanielCharis1/fix/issues-436-437-438-439
fix: resolve issues #436, #437, #438, #439
2 parents 9b25339 + 457b1b8 commit 93fb947

10 files changed

Lines changed: 681 additions & 106 deletions

File tree

contracts/certificate/src/lib.rs

Lines changed: 64 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,85 @@
11
#![no_std]
22

3-
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String};
3+
pub mod errors;
4+
pub mod events;
5+
pub mod storage;
6+
pub mod storage_optimizer;
7+
pub mod two_factor_integration;
8+
pub mod types;
49

510
#[cfg(test)]
611
mod test;
712

813
use errors::CertificateError;
9-
use shared::logger::{LogLevel, Logger};
10-
use shared::monitoring::{ContractHealthReport, Monitor};
11-
use shared::rate_limiter::{enforce_rate_limit, RateLimitConfig};
12-
use shared::{log_error, log_info, log_warn};
13-
use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, BytesN, Env, Map, String, Symbol, Vec};
14-
use types::{
15-
AuditAction, BatchResult, CertDataKey, CertRateLimitConfig, Certificate, CertificateAnalytics,
16-
CertificateBackup, CertificateStatus, CertificateTemplate, ComplianceRecord,
17-
ComplianceStandard, MintCertificateParams, MultiSigAuditEntry, MultiSigCertificateRequest,
18-
MultiSigConfig, MultiSigRequestStatus, RecoveryRequest, RecoveryStatus, RevocationRecord,
19-
ShareRecord, TemplateField, TemplateVersion,
20-
};
21-
22-
use shared::gdpr_types::GdprCertificateExport;
23-
24-
/// Maximum number of approvers per config (gas guard).
25-
const MAX_APPROVERS: u32 = 10;
26-
/// Minimum timeout: 1 hour.
27-
const MIN_TIMEOUT: u64 = 3_600;
28-
/// Maximum timeout: 30 days.
29-
const MAX_TIMEOUT: u64 = 2_592_000;
30-
/// Maximum batch size (gas guard).
31-
const MAX_BATCH_SIZE: u32 = 100;
32-
/// Maximum share records per certificate.
33-
const MAX_SHARES_PER_CERT: u32 = 100;
34-
/// Rate limit operation ID for multisig requests.
35-
const RL_OP_MULTISIG_REQUEST: u64 = 1;
14+
use soroban_sdk::{contract, contractimpl, symbol_short, Address, BytesN, Env, Vec};
15+
use types::CertificateStatus;
3616

3717
#[contract]
3818
pub struct CertificateContract;
3919

20+
#[contractimpl]
21+
impl CertificateContract {
22+
/// Initialize the certificate contract with an admin address.
23+
pub fn initialize(env: Env, admin: Address) -> Result<(), CertificateError> {
24+
if storage::is_initialized(&env) {
25+
return Err(CertificateError::AlreadyInitialized);
26+
}
27+
storage::set_admin(&env, &admin);
28+
storage::set_initialized(&env);
29+
Ok(())
30+
}
31+
32+
/// Scan all issued certificates and remove storage entries for those that have
33+
/// passed their `expiry_date`, freeing ledger memory (fixes #439).
34+
///
35+
/// Only the contract admin may call this function.
36+
/// Returns the number of expired certificate entries that were cleaned up.
37+
pub fn cleanup_expired_certificates(
38+
env: Env,
39+
caller: Address,
40+
) -> Result<u32, CertificateError> {
41+
require_initialized(&env)?;
42+
require_admin(&env, &caller)?;
43+
44+
let now = env.ledger().timestamp();
45+
let all_ids = storage::get_all_certificates(&env);
46+
let mut remaining: Vec<BytesN<32>> = Vec::new(&env);
47+
let mut cleaned: u32 = 0;
48+
49+
for cert_id in all_ids.iter() {
50+
match storage::get_certificate(&env, &cert_id) {
51+
Some(cert) => {
52+
if cert.expiry_date > 0 && now >= cert.expiry_date {
53+
// Remove the storage entry to release ledger memory
54+
storage::remove_certificate(&env, &cert_id);
55+
cleaned += 1;
56+
} else {
57+
remaining.push_back(cert_id);
58+
}
59+
}
60+
None => {
61+
// Already removed; drop from index silently
62+
}
63+
}
64+
}
65+
66+
storage::set_all_certificates(&env, &remaining);
67+
Ok(cleaned)
68+
}
69+
70+
/// Return the number of certificates currently tracked in the global index.
71+
pub fn get_certificate_count(env: Env) -> u32 {
72+
storage::get_all_certificates(&env).len()
73+
}
74+
}
75+
4076
// ─────────────────────────────────────────────────────────────
4177
// Helpers
4278
// ─────────────────────────────────────────────────────────────
4379
fn require_admin(env: &Env, caller: &Address) -> Result<(), CertificateError> {
4480
caller.require_auth();
4581
let admin = storage::get_admin(env);
4682
if *caller != admin {
47-
log_error!(env, symbol_short!("cert"), symbol_short!("unauth"));
4883
return Err(CertificateError::Unauthorized);
4984
}
5085
Ok(())
@@ -56,75 +91,3 @@ fn require_initialized(env: &Env) -> Result<(), CertificateError> {
5691
}
5792
Ok(())
5893
}
59-
60-
/// Deterministic request ID from counter.
61-
fn generate_request_id(env: &Env) -> BytesN<32> {
62-
let counter = storage::next_request_counter(env);
63-
let mut bytes = [0u8; 32];
64-
let counter_bytes = counter.to_be_bytes();
65-
bytes[24..32].copy_from_slice(&counter_bytes);
66-
// Mix in ledger timestamp for uniqueness
67-
let ts = env.ledger().timestamp().to_be_bytes();
68-
bytes[16..24].copy_from_slice(&ts);
69-
BytesN::from_array(env, &bytes)
70-
}
71-
72-
/// Deterministic certificate anchor hash.
73-
fn generate_blockchain_anchor(env: &Env, cert_id: &BytesN<32>) -> soroban_sdk::Bytes {
74-
let counter = storage::next_certificate_counter(env);
75-
let mut bytes = [0u8; 32];
76-
// Embed certificate id prefix
77-
let cert_bytes = cert_id.to_array();
78-
bytes[0..16].copy_from_slice(&cert_bytes[0..16]);
79-
// Embed counter
80-
let counter_bytes = counter.to_be_bytes();
81-
bytes[24..32].copy_from_slice(&counter_bytes);
82-
soroban_sdk::Bytes::from_array(env, &bytes)
83-
}
84-
85-
fn update_analytics_field(env: &Env, updater: impl FnOnce(&mut CertificateAnalytics)) {
86-
let mut analytics = storage::get_analytics(env);
87-
updater(&mut analytics);
88-
analytics.last_updated = env.ledger().timestamp();
89-
storage::set_analytics(env, &analytics);
90-
}
91-
92-
#[contract]
93-
pub struct DashboardPreferencesContract;
94-
95-
#[contractimpl]
96-
impl DashboardPreferencesContract {
97-
/// Saves the user's customized dashboard layout and widget preferences.
98-
/// The layout is expected to be a serialized string (e.g. JSON)
99-
/// that the frontend can parse to restore the drag-and-drop state.
100-
pub fn save_layout(
101-
env: Env,
102-
user: Address,
103-
layout_data: String,
104-
) {
105-
user.require_auth();
106-
107-
env.storage()
108-
.persistent()
109-
.set(&DataKey::UserLayout(user), &layout_data);
110-
}
111-
112-
/// Retrieves the user's dashboard layout.
113-
pub fn get_layout(env: Env, user: Address) -> Option<String> {
114-
env.storage()
115-
.persistent()
116-
.get(&DataKey::UserLayout(user))
117-
}
118-
119-
/// Deletes the user's dashboard layout, reverting to the frontend's default.
120-
pub fn clear_layout(env: Env, user: Address) {
121-
user.require_auth();
122-
123-
env.storage()
124-
.persistent()
125-
.remove(&DataKey::UserLayout(user));
126-
}
127-
}
128-
129-
#[cfg(test)]
130-
mod test;

contracts/certificate/src/storage.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,32 @@ pub fn get_pending_recovery_requests(env: &Env) -> Vec<BytesN<32>> {
391391
pub fn set_pending_recovery_requests(env: &Env, pending: &Vec<BytesN<32>>) {
392392
env.storage().persistent().set(&CertDataKey::PendingRecoveryRequests, pending);
393393
}
394+
395+
// ─────────────────────────────────────────────────────────────
396+
// Global Certificate Index (for expiry cleanup)
397+
// ─────────────────────────────────────────────────────────────
398+
pub fn add_to_all_certificates(env: &Env, cert_id: &BytesN<32>) {
399+
let mut all: Vec<BytesN<32>> = env
400+
.storage()
401+
.persistent()
402+
.get(&CertDataKey::AllCertificates)
403+
.unwrap_or_else(|| Vec::new(env));
404+
all.push_back(cert_id.clone());
405+
env.storage().persistent().set(&CertDataKey::AllCertificates, &all);
406+
}
407+
408+
pub fn get_all_certificates(env: &Env) -> Vec<BytesN<32>> {
409+
env.storage()
410+
.persistent()
411+
.get(&CertDataKey::AllCertificates)
412+
.unwrap_or_else(|| Vec::new(env))
413+
}
414+
415+
pub fn set_all_certificates(env: &Env, ids: &Vec<BytesN<32>>) {
416+
env.storage().persistent().set(&CertDataKey::AllCertificates, ids);
417+
}
418+
419+
/// Remove the persistent storage entry for a certificate, freeing ledger memory.
420+
pub fn remove_certificate(env: &Env, cert_id: &BytesN<32>) {
421+
env.storage().persistent().remove(&CertDataKey::Certificate(cert_id.clone()));
422+
}

contracts/certificate/src/types.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,9 @@ pub enum CertDataKey {
612612

613613
/// Progress tracking for batch operations keyed by Job ID.
614614
BatchJobProgress(BytesN<32>),
615+
616+
/// Global list of all issued certificate IDs (used for expiry cleanup).
617+
AllCertificates,
615618
}
616619

617620
/// Configurable rate limits for certificate operations.

contracts/custom-domain/Cargo.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "custom-domain"
3+
version = "0.1.0"
4+
edition = "2021"
5+
description = "Custom domain management for institutional credential portals — supports subdomain registration, SSL status tracking, and DNS configuration (fixes #436)"
6+
license = "Apache-2.0"
7+
repository = "https://github.com/StarkMindsHQ/StrellerMinds-SmartContracts"
8+
9+
[lib]
10+
crate-type = ["lib", "cdylib"]
11+
12+
[dependencies]
13+
soroban-sdk = { workspace = true }
14+
15+
[dev-dependencies]
16+
soroban-sdk = { workspace = true, features = ["testutils"] }

0 commit comments

Comments
 (0)