@@ -122,6 +122,25 @@ contract PSPPrepareInstallationTest is PSPBaseTest {
122122 assertTrue (found, "InstallationPrepared not emitted " );
123123 }
124124
125+ /// Preparing an installation for a plugin that is already installed reverts.
126+ /// Build 5 (Bad) returns a fixed plugin address, so re-preparing after an
127+ /// apply lands in the SAME installation slot (non-zero `currentAppliedSetupId`).
128+ function test_prepareInstallation_revertsIfPluginAlreadyInstalled () public {
129+ (address plugin , IPluginSetup.PreparedSetupData memory data ) =
130+ psp.prepareInstallation (address (dao), _prepareInstallParams (5 , "" ));
131+ _grantApplyInstallation (owner);
132+ _grantPspRoot ();
133+ psp.applyInstallation (
134+ address (dao),
135+ PluginSetupProcessor.ApplyInstallationParams ({
136+ pluginSetupRef: _ref (5 ), plugin: plugin, permissions: data.permissions, helpersHash: hashHelpers (data.helpers)
137+ })
138+ );
139+
140+ vm.expectRevert (PluginSetupProcessor.PluginAlreadyInstalled.selector );
141+ psp.prepareInstallation (address (dao), _prepareInstallParams (5 , "" ));
142+ }
143+
125144 function test_prepareInstallation_revertsIfSamePrepIdPending () public {
126145 // The V1 mock deploys a NEW proxy each call (different `plugin` →
127146 // different installationId), so re-using V1 lands in a fresh state
@@ -208,6 +227,24 @@ contract PSPApplyInstallationTest is PSPBaseTest {
208227 assertEq (currentAppliedId, expectedAppliedId);
209228 }
210229
230+ /// Applying an installation for an already-installed plugin reverts. The
231+ /// `currentAppliedSetupId != 0` guard is checked BEFORE `validatePreparedSetupId`,
232+ /// so re-applying the same params surfaces `PluginAlreadyInstalled` (not
233+ /// `SetupNotApplicable`). Build 5 (Bad) gives the fixed plugin address needed.
234+ function test_applyInstallation_revertsIfPluginAlreadyInstalled () public {
235+ (address plugin , IPluginSetup.PreparedSetupData memory data ) =
236+ psp.prepareInstallation (address (dao), _prepareInstallParams (5 , "" ));
237+ PluginSetupProcessor.ApplyInstallationParams memory p = PluginSetupProcessor.ApplyInstallationParams ({
238+ pluginSetupRef: _ref (5 ), plugin: plugin, permissions: data.permissions, helpersHash: hashHelpers (data.helpers)
239+ });
240+ _grantApplyInstallation (owner);
241+ _grantPspRoot ();
242+ psp.applyInstallation (address (dao), p);
243+
244+ vm.expectRevert (PluginSetupProcessor.PluginAlreadyInstalled.selector );
245+ psp.applyInstallation (address (dao), p);
246+ }
247+
211248 /// **F32 closer**: `msg.sender == _dao` bypasses APPLY_INSTALLATION_PERMISSION.
212249 function test_applyInstallation_daoAsSelfBypassesPermissionCheck () public {
213250 (address plugin , PluginSetupProcessor.ApplyInstallationParams memory p ) = _prepare ();
@@ -432,6 +469,13 @@ contract PSPConstructorAndConstantsTest is PSPBaseTest {
432469 assertEq (address (psp.repoRegistry ()), address (pluginRepoRegistry));
433470 }
434471
472+ function test_protocolVersion_returnsCurrent () public view {
473+ uint8 [3 ] memory v = psp.protocolVersion ();
474+ assertEq (v[0 ], 1 );
475+ assertEq (v[1 ], 4 );
476+ assertEq (v[2 ], 0 );
477+ }
478+
435479 function test_permissionId_applyInstallationMatchesKeccak () public view {
436480 assertEq (psp.APPLY_INSTALLATION_PERMISSION_ID (), keccak256 ("APPLY_INSTALLATION_PERMISSION " ));
437481 }
0 commit comments