From 809c336857b7b2e2e1fa6a4e2e92e5fce5eaf954 Mon Sep 17 00:00:00 2001 From: OmniZlatoon Date: Sun, 30 Aug 2026 14:12:06 +0100 Subject: [PATCH 1/3] fix(contracts): index reissued certificates and mark originals as superseded - Add new certificate to IssuerCertIds and OwnerCertIds indexes - Mark original certificate status as Revoked with reason 'Superseded' - Emit CertificateRevokedEvent for original certificate - Update test_reissue_certificate to verify all three requirements Fixes: reissue_certificate does not index the new certificate and leaves the old one active The fix ensures: 1. Reissued certificates are discoverable via get_certificates_by_issuer/owner 2. Original certificates are marked as revoked (not simultaneously active) 3. Proper audit trail with revocation events for both old and new certs --- stellar-contracts/src/lib.rs | 23 +++++++++++++++++++++-- stellar-contracts/src/test.rs | 15 +++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/stellar-contracts/src/lib.rs b/stellar-contracts/src/lib.rs index 07c1e0d5..37fd739b 100644 --- a/stellar-contracts/src/lib.rs +++ b/stellar-contracts/src/lib.rs @@ -488,15 +488,34 @@ impl CertificateContract { // Store new certificate Self::set_persistent(&env, &DataKey::Certificate(new_id.clone()), &new_cert); - // Emit issuance event + // Index the new certificate by issuer and owner + Self::append_cert_id(&env, DataKey::IssuerCertIds(issuer.clone()), new_id.clone()); + Self::append_cert_id(&env, DataKey::OwnerCertIds(new_cert.owner.clone()), new_id.clone()); + + // Mark original certificate as revoked (superseded) + let mut updated_original = original_cert; + updated_original.status = CertificateStatus::Revoked; + updated_original.revocation_reason = Some(String::from_str(&env, "Superseded")); + Self::set_persistent(&env, &DataKey::Certificate(old_id.clone()), &updated_original); + + // Emit issuance event for new certificate env.events().publish( (symbol_short!("issued"), new_id.clone()), CertificateIssuedEvent { id: new_id, - issuer, + issuer: issuer.clone(), owner: new_cert.owner, }, ); + + // Emit revocation event for original certificate + env.events().publish( + (symbol_short!("revoked"), old_id.clone()), + CertificateRevokedEvent { + id: old_id, + reason: String::from_str(&env, "Superseded"), + }, + ); } // --- Certificate Transfer Functions --- diff --git a/stellar-contracts/src/test.rs b/stellar-contracts/src/test.rs index af354bc3..1ec82562 100644 --- a/stellar-contracts/src/test.rs +++ b/stellar-contracts/src/test.rs @@ -150,10 +150,21 @@ fn test_reissue_certificate() { assert_eq!(new_cert.metadata_uri, new_metadata); assert_eq!(new_cert.parent_certificate_id, Some(old_id.clone())); - // Verify original certificate still exists + // Verify original certificate is now revoked (superseded) let original_cert = client.get_certificate(&old_id).expect("Certificate should exist"); assert_eq!(original_cert.id, old_id); - assert_eq!(original_cert.status, CertificateStatus::Active); // Original remains active + assert_eq!(original_cert.status, CertificateStatus::Revoked); + assert_eq!(original_cert.revocation_reason, Some(String::from_str(&env, "Superseded"))); + + // Verify new certificate is indexed by issuer + let issuer_certs = client.get_certificates_by_issuer(&issuer, &Pagination { page: 1, limit: 10 }); + let cert_ids: Vec = issuer_certs.data.iter().map(|c| c.id.clone()).collect(); + assert!(cert_ids.contains(&new_id), "New cert should be indexed by issuer"); + + // Verify new certificate is indexed by owner + let owner_certs = client.get_certificates_by_owner(&owner, &Pagination { page: 1, limit: 10 }); + let owner_cert_ids: Vec = owner_certs.data.iter().map(|c| c.id.clone()).collect(); + assert!(owner_cert_ids.contains(&new_id), "New cert should be indexed by owner"); } #[test] From 8894a5f468053c930e0d051c59822ffdfa16b610 Mon Sep 17 00:00:00 2001 From: OmniZlatoon Date: Tue, 1 Sep 2026 06:03:31 +0100 Subject: [PATCH 2/3] chore: drop contract-only changes --- stellar-contracts/src/lib.rs | 23 ++--------------------- stellar-contracts/src/test.rs | 15 ++------------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/stellar-contracts/src/lib.rs b/stellar-contracts/src/lib.rs index aaf3c563..e5070c22 100644 --- a/stellar-contracts/src/lib.rs +++ b/stellar-contracts/src/lib.rs @@ -457,34 +457,15 @@ impl CertificateContract { // Store new certificate Self::set_persistent(&env, &DataKey::Certificate(new_id.clone()), &new_cert); - // Index the new certificate by issuer and owner - Self::append_cert_id(&env, DataKey::IssuerCertIds(issuer.clone()), new_id.clone()); - Self::append_cert_id(&env, DataKey::OwnerCertIds(new_cert.owner.clone()), new_id.clone()); - - // Mark original certificate as revoked (superseded) - let mut updated_original = original_cert; - updated_original.status = CertificateStatus::Revoked; - updated_original.revocation_reason = Some(String::from_str(&env, "Superseded")); - Self::set_persistent(&env, &DataKey::Certificate(old_id.clone()), &updated_original); - - // Emit issuance event for new certificate + // Emit issuance event env.events().publish( (symbol_short!("reissued"), new_id.clone()), CertificateReissuedEvent { id: new_id, - issuer: issuer.clone(), + issuer, owner: new_cert.owner, }, ); - - // Emit revocation event for original certificate - env.events().publish( - (symbol_short!("revoked"), old_id.clone()), - CertificateRevokedEvent { - id: old_id, - reason: String::from_str(&env, "Superseded"), - }, - ); } // --- Certificate Transfer Functions --- diff --git a/stellar-contracts/src/test.rs b/stellar-contracts/src/test.rs index 792a935b..660a8272 100644 --- a/stellar-contracts/src/test.rs +++ b/stellar-contracts/src/test.rs @@ -150,21 +150,10 @@ fn test_reissue_certificate() { assert_eq!(new_cert.metadata_uri, new_metadata); assert_eq!(new_cert.parent_certificate_id, Some(old_id.clone())); - // Verify original certificate is now revoked (superseded) + // Verify original certificate still exists let original_cert = client.get_certificate(&old_id).expect("Certificate should exist"); assert_eq!(original_cert.id, old_id); - assert_eq!(original_cert.status, CertificateStatus::Revoked); - assert_eq!(original_cert.revocation_reason, Some(String::from_str(&env, "Superseded"))); - - // Verify new certificate is indexed by issuer - let issuer_certs = client.get_certificates_by_issuer(&issuer, &Pagination { page: 1, limit: 10 }); - let cert_ids: Vec = issuer_certs.data.iter().map(|c| c.id.clone()).collect(); - assert!(cert_ids.contains(&new_id), "New cert should be indexed by issuer"); - - // Verify new certificate is indexed by owner - let owner_certs = client.get_certificates_by_owner(&owner, &Pagination { page: 1, limit: 10 }); - let owner_cert_ids: Vec = owner_certs.data.iter().map(|c| c.id.clone()).collect(); - assert!(owner_cert_ids.contains(&new_id), "New cert should be indexed by owner"); + assert_eq!(original_cert.status, CertificateStatus::Active); // Original remains active } #[test] From 6e480e68f50a127a72527db49ff5e244071d8a43 Mon Sep 17 00:00:00 2001 From: OmniZlatoon Date: Tue, 1 Sep 2026 06:23:41 +0100 Subject: [PATCH 3/3] fix(contract): resolve reissue move and event payload --- stellar-contracts/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stellar-contracts/src/lib.rs b/stellar-contracts/src/lib.rs index e5070c22..070a42eb 100644 --- a/stellar-contracts/src/lib.rs +++ b/stellar-contracts/src/lib.rs @@ -443,7 +443,7 @@ impl CertificateContract { let new_cert = Certificate { id: new_id.clone(), issuer: issuer.clone(), - owner: new_owner.unwrap_or(original_cert.owner), + owner: new_owner.unwrap_or(original_cert.owner.clone()), status: CertificateStatus::Active, metadata_uri: new_metadata_uri, issued_at: env.ledger().timestamp(), @@ -462,6 +462,7 @@ impl CertificateContract { (symbol_short!("reissued"), new_id.clone()), CertificateReissuedEvent { id: new_id, + old_id: old_id.clone(), issuer, owner: new_cert.owner, },