@@ -303,6 +303,80 @@ contract OperatorTableUpdaterUnitTests_confirmGlobalTableRoot is OperatorTableUp
303303 assertEq (operatorTableUpdater.getReferenceBlockNumberByTimestamp (referenceTimestamp), referenceBlockNumber);
304304 assertEq (operatorTableUpdater.getReferenceTimestampByBlockNumber (referenceBlockNumber), referenceTimestamp);
305305 }
306+
307+ function testFuzz_silentReturn_alreadyConfirmed (Randomness r ) public rand (r) {
308+ uint32 referenceTimestamp = r.Uint32 (operatorTableUpdater.getLatestReferenceTimestamp () + 1 , type (uint32 ).max);
309+ uint32 referenceBlockNumber = r.Uint32 ();
310+ cheats.warp (uint (referenceTimestamp));
311+ bytes32 globalTableRoot = bytes32 (r.Uint256 (1 , type (uint ).max));
312+ mockCertificate.messageHash =
313+ operatorTableUpdater.getGlobalTableUpdateMessageHash (globalTableRoot, referenceTimestamp, referenceBlockNumber);
314+ _setIsValidCertificate (mockCertificate, true );
315+
316+ // First confirmation should succeed and emit event
317+ operatorTableUpdater.confirmGlobalTableRoot (mockCertificate, globalTableRoot, referenceTimestamp, referenceBlockNumber);
318+
319+ // Verify the root is now valid
320+ assertTrue (operatorTableUpdater.isRootValid (globalTableRoot), "Global table root should be valid after first confirmation " );
321+
322+ // Store initial state to verify it doesn't change
323+ bytes32 initialCurrentRoot = operatorTableUpdater.getCurrentGlobalTableRoot ();
324+ uint32 initialLatestTimestamp = operatorTableUpdater.getLatestReferenceTimestamp ();
325+ uint32 initialLatestBlockNumber = operatorTableUpdater.getLatestReferenceBlockNumber ();
326+
327+ // Second confirmation with the same root should silently return without emitting events
328+ // We should NOT expect any events to be emitted
329+ cheats.recordLogs ();
330+ operatorTableUpdater.confirmGlobalTableRoot (mockCertificate, globalTableRoot, referenceTimestamp, referenceBlockNumber);
331+
332+ // Verify no events were emitted on the second call
333+ Vm.Log[] memory logs = cheats.getRecordedLogs ();
334+ assertEq (logs.length , 0 , "No events should be emitted when confirming an already valid root " );
335+
336+ // Verify state remains unchanged after silent return
337+ assertEq (operatorTableUpdater.getCurrentGlobalTableRoot (), initialCurrentRoot, "Current root should remain unchanged " );
338+ assertEq (operatorTableUpdater.getLatestReferenceTimestamp (), initialLatestTimestamp, "Latest timestamp should remain unchanged " );
339+ assertEq (
340+ operatorTableUpdater.getLatestReferenceBlockNumber (), initialLatestBlockNumber, "Latest block number should remain unchanged "
341+ );
342+ assertTrue (operatorTableUpdater.isRootValid (globalTableRoot), "Global table root should still be valid " );
343+ }
344+
345+ function test_silentReturn_alreadyConfirmed_differentCertificate () public {
346+ uint32 referenceTimestamp = uint32 (block .timestamp );
347+ uint32 referenceBlockNumber = uint32 (block .number );
348+ bytes32 globalTableRoot = bytes32 (uint (12_345 ));
349+
350+ // First certificate
351+ BN254Certificate memory firstCertificate;
352+ firstCertificate.referenceTimestamp = 1 ; // GENERATOR_REFERENCE_TIMESTAMP
353+ firstCertificate.messageHash =
354+ operatorTableUpdater.getGlobalTableUpdateMessageHash (globalTableRoot, referenceTimestamp, referenceBlockNumber);
355+ _setIsValidCertificate (firstCertificate, true );
356+
357+ // Confirm with first certificate
358+ cheats.expectEmit (true , true , true , true );
359+ emit NewGlobalTableRoot (referenceTimestamp, globalTableRoot);
360+ operatorTableUpdater.confirmGlobalTableRoot (firstCertificate, globalTableRoot, referenceTimestamp, referenceBlockNumber);
361+
362+ // Create a different certificate for the same global table root
363+ BN254Certificate memory secondCertificate;
364+ secondCertificate.referenceTimestamp = 1 ; // GENERATOR_REFERENCE_TIMESTAMP
365+ secondCertificate.messageHash = firstCertificate.messageHash; // Same message hash
366+ secondCertificate.signature = BN254.G1Point ({X: 999 , Y: 888 }); // Different signature to make it a different certificate
367+ _setIsValidCertificate (secondCertificate, true );
368+
369+ // Second confirmation with different certificate but same root should silently return
370+ cheats.recordLogs ();
371+ operatorTableUpdater.confirmGlobalTableRoot (secondCertificate, globalTableRoot, referenceTimestamp, referenceBlockNumber);
372+
373+ // Verify no events were emitted
374+ Vm.Log[] memory logs = cheats.getRecordedLogs ();
375+ assertEq (logs.length , 0 , "No events should be emitted when confirming an already valid root with different certificate " );
376+
377+ // Verify the root is still valid
378+ assertTrue (operatorTableUpdater.isRootValid (globalTableRoot), "Global table root should still be valid " );
379+ }
306380}
307381
308382contract OperatorTableUpdaterUnitTests_updateOperatorTable_BN254 is OperatorTableUpdaterUnitTests {
0 commit comments