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) ]
611mod test;
712
813use 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]
3818pub 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// ─────────────────────────────────────────────────────────────
4379fn 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;
0 commit comments