From 83a70de5202ec8c35679196a71e5996aa23a97b7 Mon Sep 17 00:00:00 2001 From: Leopold Joy Date: Fri, 24 Jul 2026 19:59:30 +0100 Subject: [PATCH] fix: emit cert events on state transitions Co-authored-by: OpenCode --- src/CertManager.sol | 2 ++ test/CertManager.t.sol | 14 ++++++++++++++ test/helpers/CertManagerDemo.sol | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/CertManager.sol b/src/CertManager.sol index 29ecfd0..c0c109f 100644 --- a/src/CertManager.sol +++ b/src/CertManager.sol @@ -211,11 +211,13 @@ contract CertManager is ICertManager { } function unrevokeCert(bytes32 certId) external onlyOwner { + if (!revoked[certId]) return; revoked[certId] = false; emit CertUnrevoked(certId, msg.sender); } function _revokeCert(bytes32 certId) internal { + if (revoked[certId]) return; revoked[certId] = true; emit CertRevoked(certId, msg.sender); } diff --git a/test/CertManager.t.sol b/test/CertManager.t.sol index 6db1293..cd11ea1 100644 --- a/test/CertManager.t.sol +++ b/test/CertManager.t.sol @@ -711,6 +711,20 @@ contract CertRevocationEventTest is Test { cm.unrevokeCert(CERT_ID); } + function test_RepeatedRevokeDoesNotEmitEvent() public { + cm.revokeCert(CERT_ID); + + vm.recordLogs(); + cm.revokeCert(CERT_ID); + assertEq(vm.getRecordedLogs().length, 0, "repeated revoke emitted event"); + } + + function test_RepeatedUnrevokeDoesNotEmitEvent() public { + vm.recordLogs(); + cm.unrevokeCert(CERT_ID); + assertEq(vm.getRecordedLogs().length, 0, "repeated unrevoke emitted event"); + } + /// @dev The recorded account is the actual caller, not the contract: a delegated revoker /// address shows up in the event topic. function test_RevokeCertRecordsActualCaller() public { diff --git a/test/helpers/CertManagerDemo.sol b/test/helpers/CertManagerDemo.sol index 40ef0ca..649501b 100644 --- a/test/helpers/CertManagerDemo.sol +++ b/test/helpers/CertManagerDemo.sol @@ -7,7 +7,7 @@ import {IP384Verifier} from "../../src/IP384Verifier.sol"; /// @notice Demo-only certificate manager with configurable certificate expiry grace. /// @dev This exists only to replay expired Nitro fixtures on testnets. Do not use in production or audit target deployments. contract CertManagerDemo is CertManager { - uint256 public immutable certificateExpiryGraceSeconds; + uint256 private immutable certificateExpiryGraceSeconds; constructor(IP384Verifier p384Verifier_, uint256 certificateExpiryGraceSeconds_) CertManager(p384Verifier_) { certificateExpiryGraceSeconds = certificateExpiryGraceSeconds_;