From 2d9d51ff1c545d45df2887413bbae6822e53668b Mon Sep 17 00:00:00 2001 From: Eric Shenghsiung Liu Date: Mon, 1 Jun 2026 18:02:21 -0400 Subject: [PATCH 1/2] test(ActivationRegistry): add activate and deactivate revertOrder tests BOP-221 --- .../activate_revertOrder.t.sol | 15 +++++++++++++++ .../deactivate_revertOrder.t.sol | 16 ++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 test/unit/ActivationRegistry/activate_revertOrder.t.sol create mode 100644 test/unit/ActivationRegistry/deactivate_revertOrder.t.sol diff --git a/test/unit/ActivationRegistry/activate_revertOrder.t.sol b/test/unit/ActivationRegistry/activate_revertOrder.t.sol new file mode 100644 index 00000000..3e75ee8f --- /dev/null +++ b/test/unit/ActivationRegistry/activate_revertOrder.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ActivationRegistryTest} from "test/lib/ActivationRegistryTest.sol"; + +contract ActivationRegistryActivateRevertOrderTest is ActivationRegistryTest { + /// @notice Verifies Unauthorized fires before AlreadyActivated when caller is not admin + /// and the feature is already activated + /// @dev Revert order: access-control check precedes state validation; a non-admin caller + /// never reaches the AlreadyActivated guard regardless of feature state. + /// Fuzz: any caller that is not the activationAdmin, any feature id. + function test_activate_revertOrder_unauthorized_beats_alreadyActivated(address caller, bytes32 feature) public { + // unimplemented + } +} diff --git a/test/unit/ActivationRegistry/deactivate_revertOrder.t.sol b/test/unit/ActivationRegistry/deactivate_revertOrder.t.sol new file mode 100644 index 00000000..35bf2ee8 --- /dev/null +++ b/test/unit/ActivationRegistry/deactivate_revertOrder.t.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ActivationRegistryTest} from "test/lib/ActivationRegistryTest.sol"; + +contract ActivationRegistryDeactivateRevertOrderTest is ActivationRegistryTest { + /// @notice Verifies Unauthorized fires before FeatureNotActivated when caller is not admin + /// and the feature is not currently activated + /// @dev Revert order: access-control check precedes state validation; a non-admin caller + /// never reaches the FeatureNotActivated guard regardless of feature state. + /// Fuzz: any caller that is not the activationAdmin, any feature id. + function test_deactivate_revertOrder_unauthorized_beats_featureNotActivated(address caller, bytes32 feature) + public { + // unimplemented + } +} From 59968a6b583277fcfc8c3001a4fe6f2a11376a7a Mon Sep 17 00:00:00 2001 From: Eric Shenghsiung Liu Date: Mon, 1 Jun 2026 19:26:14 -0400 Subject: [PATCH 2/2] test(ActivationRegistry): implement activate and deactivate revertOrder assertions Fill in real fuzz assertions for the two revertOrder stubs: activate confirms Unauthorized fires before AlreadyActivated; deactivate confirms Unauthorized fires before FeatureNotActivated. Both tests run 256 fuzz rounds and pass. --- .../activate_revertOrder.t.sol | 16 +++++++++++++++- .../deactivate_revertOrder.t.sol | 17 +++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/test/unit/ActivationRegistry/activate_revertOrder.t.sol b/test/unit/ActivationRegistry/activate_revertOrder.t.sol index 3e75ee8f..91874a61 100644 --- a/test/unit/ActivationRegistry/activate_revertOrder.t.sol +++ b/test/unit/ActivationRegistry/activate_revertOrder.t.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import {IActivationRegistry} from "src/interfaces/IActivationRegistry.sol"; + import {ActivationRegistryTest} from "test/lib/ActivationRegistryTest.sol"; contract ActivationRegistryActivateRevertOrderTest is ActivationRegistryTest { @@ -10,6 +12,18 @@ contract ActivationRegistryActivateRevertOrderTest is ActivationRegistryTest { /// never reaches the AlreadyActivated guard regardless of feature state. /// Fuzz: any caller that is not the activationAdmin, any feature id. function test_activate_revertOrder_unauthorized_beats_alreadyActivated(address caller, bytes32 feature) public { - // unimplemented + _assumeValidCaller(caller); + vm.assume(caller != activationAdmin); + + // Activate the feature as admin so it is already activated, establishing + // the precondition that AlreadyActivated *could* fire for an admin caller. + vm.prank(activationAdmin); + activationRegistry.activate(feature); + + // A non-admin caller must see Unauthorized, not AlreadyActivated, + // because the access-control check in activate fires first. + vm.expectRevert(abi.encodeWithSelector(IActivationRegistry.Unauthorized.selector, caller)); + vm.prank(caller); + activationRegistry.activate(feature); } } diff --git a/test/unit/ActivationRegistry/deactivate_revertOrder.t.sol b/test/unit/ActivationRegistry/deactivate_revertOrder.t.sol index 35bf2ee8..95e17b13 100644 --- a/test/unit/ActivationRegistry/deactivate_revertOrder.t.sol +++ b/test/unit/ActivationRegistry/deactivate_revertOrder.t.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; +import {IActivationRegistry} from "src/interfaces/IActivationRegistry.sol"; + import {ActivationRegistryTest} from "test/lib/ActivationRegistryTest.sol"; contract ActivationRegistryDeactivateRevertOrderTest is ActivationRegistryTest { @@ -10,7 +12,18 @@ contract ActivationRegistryDeactivateRevertOrderTest is ActivationRegistryTest { /// never reaches the FeatureNotActivated guard regardless of feature state. /// Fuzz: any caller that is not the activationAdmin, any feature id. function test_deactivate_revertOrder_unauthorized_beats_featureNotActivated(address caller, bytes32 feature) - public { - // unimplemented + public + { + _assumeValidCaller(caller); + vm.assume(caller != activationAdmin); + + // The feature is not activated (default state), establishing the precondition + // that FeatureNotActivated *could* fire for an admin caller. + + // A non-admin caller must see Unauthorized, not FeatureNotActivated, + // because the access-control check in deactivate fires first. + vm.expectRevert(abi.encodeWithSelector(IActivationRegistry.Unauthorized.selector, caller)); + vm.prank(caller); + activationRegistry.deactivate(feature); } }