diff --git a/contracts/smart-account/interfaces/modules/ISecurityPolicyManagerPlugin.sol b/contracts/smart-account/interfaces/modules/ISecurityPolicyManagerPlugin.sol index dbe6d66e..25236c3e 100644 --- a/contracts/smart-account/interfaces/modules/ISecurityPolicyManagerPlugin.sol +++ b/contracts/smart-account/interfaces/modules/ISecurityPolicyManagerPlugin.sol @@ -5,10 +5,7 @@ import {ISecurityPolicyPlugin} from "./ISecurityPolicyPlugin.sol"; address constant SENTINEL_MODULE_ADDRESS = address(0x1); -/// @title Security Policy Manager Plugin -/// @author @ankurdubey521 -/// @dev Execution Phase Plugin responsible for enforcing security policies during plugin installation on the smart contract wallet -interface ISecurityPolicyManagerPlugin { +interface ISecurityPolicyManagerPluginEventsErrors { event SecurityPolicyEnabled(address indexed scw, address indexed policy); event SecurityPolicyDisabled(address indexed scw, address indexed policy); @@ -17,7 +14,14 @@ interface ISecurityPolicyManagerPlugin { error InvalidSecurityPolicyAddress(address policy); error InvalidPointerAddress(address pointer); error EmptyPolicyList(); +} +/// @title Security Policy Manager Plugin +/// @author @ankurdubey521 +/// @dev Execution Phase Plugin responsible for enforcing security policies during plugin installation on the smart contract wallet +interface ISecurityPolicyManagerPlugin is + ISecurityPolicyManagerPluginEventsErrors +{ /// @dev Enables the security policies for the smart contract wallet. Used during the setup process. /// @param _policy The security policy to be enabled function enableSecurityPolicy(ISecurityPolicyPlugin _policy) external; @@ -56,8 +60,12 @@ interface ISecurityPolicyManagerPlugin { /// @dev Returns the security policy for the smart contract wallet. /// @param _scw The address of the smart contract wallet - /// @return SecurityPolicy The security policy of the smart contract wallet - function securityPolicies( - address _scw - ) external view returns (ISecurityPolicyPlugin[] memory); + /// @param _start The address of the first security policy in the list + /// @param _pageSize The number of security policies to be returned + /// @return enabledPolicies The list of enabled security policies + function securityPoliciesPaginated( + address _scw, + address _start, + uint256 _pageSize + ) external view returns (ISecurityPolicyPlugin[] memory enabledPolicies); } diff --git a/contracts/smart-account/modules/SecurityPolicyManagerPlugin.sol b/contracts/smart-account/modules/SecurityPolicyManagerPlugin.sol index ee44261a..edb12f40 100644 --- a/contracts/smart-account/modules/SecurityPolicyManagerPlugin.sol +++ b/contracts/smart-account/modules/SecurityPolicyManagerPlugin.sol @@ -3,10 +3,25 @@ pragma solidity 0.8.17; import {ISecurityPolicyManagerPlugin, ISecurityPolicyPlugin, SENTINEL_MODULE_ADDRESS} from "contracts/smart-account/interfaces/modules/ISecurityPolicyManagerPlugin.sol"; +/// @title Security Policy Manager Plugin +/// @author @ankurdubey521 +/// @dev Execution Phase Plugin responsible for enforcing security policies during plugin installation on the smart contract wallet contract SecurityPolicyManagerPlugin is ISecurityPolicyManagerPlugin { mapping(address => mapping(address => address)) internal enabledSecurityPoliciesLinkedList; + ////////////////////////// PLUGIN INSTALLATION FUNCTIONS ////////////////////////// + + /// @inheritdoc ISecurityPolicyManagerPlugin + function checkSetupAndEnableModule( + address, + bytes calldata + ) external override returns (address) { + revert("Not implemented"); + } + + ////////////////////////// SECURITY POLICY MANAGEMENT FUNCTIONS ////////////////////////// + /// @inheritdoc ISecurityPolicyManagerPlugin function enableSecurityPolicy( ISecurityPolicyPlugin _policy @@ -27,9 +42,10 @@ contract SecurityPolicyManagerPlugin is ISecurityPolicyManagerPlugin { revert SecurityPolicyAlreadyEnabled(address(_policy)); } - enabledSecurityPolicies[address(_policy)] = enabledSecurityPolicies[ - SENTINEL_MODULE_ADDRESS - ]; + address head = enabledSecurityPolicies[SENTINEL_MODULE_ADDRESS]; + enabledSecurityPolicies[address(_policy)] = head == address(0x0) + ? SENTINEL_MODULE_ADDRESS + : head; enabledSecurityPolicies[SENTINEL_MODULE_ADDRESS] = address(_policy); emit SecurityPolicyEnabled(msg.sender, address(_policy)); @@ -64,7 +80,9 @@ contract SecurityPolicyManagerPlugin is ISecurityPolicyManagerPlugin { revert SecurityPolicyAlreadyEnabled(address(policy)); } - enabledSecurityPolicies[policy] = head; + enabledSecurityPolicies[policy] = head == address(0x0) + ? SENTINEL_MODULE_ADDRESS + : head; head = policy; emit SecurityPolicyEnabled(msg.sender, policy); @@ -110,6 +128,7 @@ contract SecurityPolicyManagerPlugin is ISecurityPolicyManagerPlugin { emit SecurityPolicyDisabled(msg.sender, address(_policy)); } + /* solhint-disable code-complexity*/ /// @inheritdoc ISecurityPolicyManagerPlugin function disableSecurityPoliciesRange( ISecurityPolicyPlugin _start, @@ -145,16 +164,21 @@ contract SecurityPolicyManagerPlugin is ISecurityPolicyManagerPlugin { bool endFound = false; address current = address(_start); - while (current != address(_end) || current != SENTINEL_MODULE_ADDRESS) { - if (current == address(_end)) { - endFound = true; - } - + while (true) { address next = enabledSecurityPolicies[current]; delete enabledSecurityPolicies[current]; emit SecurityPolicyDisabled(msg.sender, current); + if (current == address(_end)) { + endFound = true; + break; + } + + if (current == SENTINEL_MODULE_ADDRESS) { + break; + } + current = next; } @@ -164,17 +188,42 @@ contract SecurityPolicyManagerPlugin is ISecurityPolicyManagerPlugin { } /// @inheritdoc ISecurityPolicyManagerPlugin - function checkSetupAndEnableModule( - address, - bytes calldata - ) external override returns (address) { - revert("Not implemented"); - } + function securityPoliciesPaginated( + address _scw, + address _start, + uint256 _pageSize + ) external view returns (ISecurityPolicyPlugin[] memory enabledPolicies) { + enabledPolicies = new ISecurityPolicyPlugin[](_pageSize); + uint256 actualEnabledPoliciesLength; - /// @inheritdoc ISecurityPolicyManagerPlugin - function securityPolicies( - address - ) external view override returns (ISecurityPolicyPlugin[] memory) { - revert("Not implemented"); + mapping(address => address) + storage enabledSecurityPolicies = enabledSecurityPoliciesLinkedList[ + _scw + ]; + + if (_start == address(0)) { + _start = SENTINEL_MODULE_ADDRESS; + } + + ISecurityPolicyPlugin current = ISecurityPolicyPlugin(_start); + do { + if (current != ISecurityPolicyPlugin(SENTINEL_MODULE_ADDRESS)) { + enabledPolicies[actualEnabledPoliciesLength] = current; + unchecked { + ++actualEnabledPoliciesLength; + } + } + current = ISecurityPolicyPlugin( + enabledSecurityPolicies[address(current)] + ); + } while ( + actualEnabledPoliciesLength < _pageSize && + current != ISecurityPolicyPlugin(SENTINEL_MODULE_ADDRESS) && + current != ISecurityPolicyPlugin(address(0)) + ); + + assembly { + mstore(enabledPolicies, actualEnabledPoliciesLength) + } } } diff --git a/test/foundry/base/SATestBase.sol b/test/foundry/base/SATestBase.sol index c41a0c61..bd87f311 100644 --- a/test/foundry/base/SATestBase.sol +++ b/test/foundry/base/SATestBase.sol @@ -2,24 +2,48 @@ pragma solidity 0.8.17; -import {Test} from "forge-std/Test.sol"; +import {Test, Vm} from "forge-std/Test.sol"; import {MockToken} from "test-contracts/mocks/MockToken.sol"; -import {EntryPoint, IEntryPoint} from "aa-core/EntryPoint.sol"; +import {EntryPoint, IEntryPoint, UserOperation} from "aa-core/EntryPoint.sol"; import {SmartAccountFactory} from "factory/SmartAccountFactory.sol"; import {SmartAccount} from "sa/SmartAccount.sol"; import {EcdsaOwnershipRegistryModule} from "modules/EcdsaOwnershipRegistryModule.sol"; abstract contract SATestBase is Test { // Test Environment Configuration - string constant mnemonic = "test test test test test test test test test test test junk"; - uint256 testAccountCount = 10; - uint256 initialMainAccountFunds = 100000 ether; + string constant mnemonic = + "test test test test test test test test test test test junk"; + uint256 constant testAccountCount = 10; + uint256 constant initialMainAccountFunds = 100000 ether; + uint256 constant defaultPreVerificationGas = 21000; + // Event Topics + bytes32 constant userOperationEventTopic = + 0x49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f; + bytes32 constant userOperationRevertReasonTopic = + 0x1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a201; uint32 nextKeyIndex; + struct UserOperationEventData { + bytes32 userOpHash; + address sender; + address paymaster; + uint256 nonce; + bool success; + uint256 actualGasCost; + uint256 actualGasUsed; + } + + struct UserOperationRevertReasonEventData { + bytes32 userOpHash; + address sender; + uint256 nonce; + bytes revertReason; + } + // Test Accounts struct TestAccount { - address addr; + address payable addr; uint256 privateKey; } @@ -55,7 +79,9 @@ abstract contract SATestBase is Test { // Generate Test Addresses for (uint256 i = 0; i < testAccountCount; i++) { uint256 privateKey = getNextPrivateKey(); - testAccounts.push(TestAccount(vm.addr(privateKey), privateKey)); + testAccounts.push( + TestAccount(payable(vm.addr(privateKey)), privateKey) + ); deal(testAccounts[i].addr, initialMainAccountFunds); } @@ -68,7 +94,10 @@ abstract contract SATestBase is Test { vm.label(bob.addr, string.concat("Bob", vm.toString(uint256(1)))); charlie = testAccounts[2]; - vm.label(charlie.addr, string.concat("Charlie", vm.toString(uint256(2)))); + vm.label( + charlie.addr, + string.concat("Charlie", vm.toString(uint256(2))) + ); dan = testAccounts[3]; vm.label(dan.addr, string.concat("Dan", vm.toString(uint256(3)))); @@ -100,15 +129,21 @@ abstract contract SATestBase is Test { entryPoint = new EntryPoint(); vm.label(address(entryPoint), "Entry Point"); - saImplementation = new SmartAccount(IEntryPoint(address(0xDEAD))); + saImplementation = new SmartAccount(entryPoint); vm.label(address(saImplementation), "Smart Account Implementation"); - factory = new SmartAccountFactory(address(saImplementation), owner.addr); + factory = new SmartAccountFactory( + address(saImplementation), + owner.addr + ); vm.label(address(factory), "Smart Account Factory"); // Deploy Modules ecdsaOwnershipRegistryModule = new EcdsaOwnershipRegistryModule(); - vm.label(address(ecdsaOwnershipRegistryModule), "ECDSA Ownership Registry Module"); + vm.label( + address(ecdsaOwnershipRegistryModule), + "ECDSA Ownership Registry Module" + ); } // Utility Functions @@ -118,12 +153,136 @@ abstract contract SATestBase is Test { uint256 _index, string memory _label ) internal returns (SmartAccount sa) { - sa = SmartAccount(payable(factory.deployCounterFactualAccount(_moduleSetupContract, _moduleSetupData, _index))); + sa = SmartAccount( + payable( + factory.deployCounterFactualAccount( + _moduleSetupContract, + _moduleSetupData, + _index + ) + ) + ); vm.label(address(sa), _label); } + function getSmartAccountExecuteCalldata( + address _dest, + uint256 _value, + bytes memory _calldata + ) internal pure returns (bytes memory) { + return abi.encodeCall(SmartAccount.execute, (_dest, _value, _calldata)); + } + + function getUserOperationEventData( + Vm.Log[] memory _entries + ) internal returns (UserOperationEventData memory data) { + for (uint256 i = 0; i < _entries.length; ++i) { + if (_entries[i].topics[0] != userOperationEventTopic) { + continue; + } + data.userOpHash = _entries[i].topics[1]; + data.sender = address(uint160(uint256(_entries[i].topics[2]))); + data.paymaster = address(uint160(uint256(_entries[i].topics[3]))); + ( + data.nonce, + data.success, + data.actualGasCost, + data.actualGasUsed + ) = abi.decode(_entries[i].data, (uint256, bool, uint256, uint256)); + return data; + } + fail("entries does not contain UserOperationEvent"); + } + + function getUserOperationRevertReasonEventData( + Vm.Log[] memory _entries + ) internal returns (UserOperationRevertReasonEventData memory data) { + for (uint256 i = 0; i < _entries.length; ++i) { + if (_entries[i].topics[0] != userOperationRevertReasonTopic) { + continue; + } + data.userOpHash = _entries[i].topics[1]; + data.sender = address(uint160(uint256(_entries[i].topics[2]))); + (data.nonce, data.revertReason) = abi.decode( + _entries[i].data, + (uint256, bytes) + ); + return data; + } + fail("entries does not contain UserOperationRevertReasonEvent"); + } + + function arraifyOps( + UserOperation memory _op + ) internal pure returns (UserOperation[] memory) { + UserOperation[] memory ops = new UserOperation[](1); + ops[0] = _op; + return ops; + } + + function arraifyOps( + UserOperation memory _op1, + UserOperation memory _op2 + ) internal pure returns (UserOperation[] memory) { + UserOperation[] memory ops = new UserOperation[](2); + ops[0] = _op1; + ops[1] = _op2; + return ops; + } + + function arraifyOps( + UserOperation memory _op1, + UserOperation memory _op2, + UserOperation memory _op3 + ) internal pure returns (UserOperation[] memory) { + UserOperation[] memory ops = new UserOperation[](3); + ops[0] = _op1; + ops[1] = _op2; + ops[2] = _op3; + return ops; + } + // Module Setup Data Helpers - function getEcdsaOwnershipRegistryModuleSetupData(address _owner) internal pure returns (bytes memory) { - return abi.encodeCall(EcdsaOwnershipRegistryModule.initForSmartAccount, (_owner)); + function getEcdsaOwnershipRegistryModuleSetupData( + address _owner + ) internal pure returns (bytes memory) { + return + abi.encodeCall( + EcdsaOwnershipRegistryModule.initForSmartAccount, + (_owner) + ); + } + + // Validation Module Op Creation Helpers + function makeEcdsaModuleUserOp( + bytes memory _calldata, + SmartAccount _sa, + uint192 _nonceKey, + TestAccount memory _signer + ) internal view returns (UserOperation memory op) { + op = UserOperation({ + sender: address(_sa), + nonce: entryPoint.getNonce(address(_sa), _nonceKey), + initCode: bytes(""), + callData: _calldata, + callGasLimit: gasleft() / 100, + verificationGasLimit: gasleft() / 100, + preVerificationGas: defaultPreVerificationGas, + maxFeePerGas: tx.gasprice, + maxPriorityFeePerGas: tx.gasprice - block.basefee, + paymasterAndData: bytes(""), + signature: bytes("") + }); + + // Sign the UserOp + bytes32 userOpHash = entryPoint.getUserOpHash(op); + (uint8 v, bytes32 r, bytes32 s) = vm.sign( + _signer.privateKey, + userOpHash + ); + op.signature = abi.encode( + abi.encodePacked(r, s, v), + ecdsaOwnershipRegistryModule + ); } } diff --git a/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.PluginManagementTest.t.sol b/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.PluginManagementTest.t.sol new file mode 100644 index 00000000..53e33a58 --- /dev/null +++ b/test/foundry/module/SecurityPolicy/SecurityPolicyManagerPlugin.PluginManagementTest.t.sol @@ -0,0 +1,1044 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {Vm} from "forge-std/Test.sol"; +import {SATestBase} from "../../base/SATestBase.sol"; +import {SmartAccount} from "sa/SmartAccount.sol"; +import {SecurityPolicyManagerPlugin, SENTINEL_MODULE_ADDRESS} from "modules/SecurityPolicyManagerPlugin.sol"; +import {ISecurityPolicyPlugin} from "interfaces/modules/ISecurityPolicyPlugin.sol"; +import {ISecurityPolicyManagerPlugin, ISecurityPolicyManagerPluginEventsErrors} from "interfaces/modules/ISecurityPolicyManagerPlugin.sol"; +import {UserOperation} from "aa-core/EntryPoint.sol"; +import "forge-std/console2.sol"; + +contract TestSecurityPolicyPlugin is ISecurityPolicyPlugin { + function validateSecurityPolicy(address, address) external pure override { + require(true); + } +} + +contract SecurityPolicyManagerPluginPluginManagementTest is + SATestBase, + ISecurityPolicyManagerPluginEventsErrors +{ + SmartAccount sa; + SecurityPolicyManagerPlugin spmp; + TestSecurityPolicyPlugin p1; + TestSecurityPolicyPlugin p2; + TestSecurityPolicyPlugin p3; + TestSecurityPolicyPlugin p4; + + function setUp() public virtual override { + super.setUp(); + + // Deploy Smart Account with default module + uint256 smartAccountDeploymentIndex = 0; + bytes memory moduleSetupData = getEcdsaOwnershipRegistryModuleSetupData( + alice.addr + ); + sa = getSmartAccountWithModule( + address(ecdsaOwnershipRegistryModule), + moduleSetupData, + smartAccountDeploymentIndex, + "aliceSA" + ); + spmp = new SecurityPolicyManagerPlugin(); + vm.label(address(spmp), "SecurityPolicyManagerPlugin"); + p1 = new TestSecurityPolicyPlugin(); + vm.label(address(p1), "p1"); + p2 = new TestSecurityPolicyPlugin(); + vm.label(address(p2), "p2"); + p3 = new TestSecurityPolicyPlugin(); + vm.label(address(p3), "p3"); + p4 = new TestSecurityPolicyPlugin(); + vm.label(address(p4), "p4"); + } + + function testEnableSingleSecurityPolicyPlugin() external { + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicy, + (p1) + ) + ); + + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.expectEmit(true, true, true, true); + emit SecurityPolicyEnabled(address(sa), address(p1)); + + entryPoint.handleOps(arraifyOps(op), owner.addr); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 1); + assertEq(address(enabledSecurityPolicies[0]), address(p1)); + } + + function testDisableSingleSecurityPolicyPlugin() external { + // Enable p1,p2,p3,p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable p1 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPolicy, + (p1, p2) + ) + ); + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.expectEmit(true, true, true, true); + emit SecurityPolicyDisabled(address(sa), address(p1)); + + entryPoint.handleOps(arraifyOps(op), owner.addr); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 3); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + } + + function testSecurityPoliciesQueryPaginated() external { + // Enable p1, p2, p3, p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Page Size 100 + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 4); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + assertEq(address(enabledSecurityPolicies[3]), address(p1)); + + // Page Size 4 + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(0), + 4 + ); + assertEq(enabledSecurityPolicies.length, 4); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + assertEq(address(enabledSecurityPolicies[3]), address(p1)); + + // Page Size 3 + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(0), + 3 + ); + assertEq(enabledSecurityPolicies.length, 3); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(enabledSecurityPolicies[2]), + 3 + ); + assertEq(enabledSecurityPolicies.length, 2); + assertEq(address(enabledSecurityPolicies[0]), address(p2)); + assertEq(address(enabledSecurityPolicies[1]), address(p1)); + + // Page Size 2 + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(0), + 2 + ); + assertEq(enabledSecurityPolicies.length, 2); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(p2), + 2 + ); + assertEq(enabledSecurityPolicies.length, 2); + assertEq(address(enabledSecurityPolicies[0]), address(p2)); + assertEq(address(enabledSecurityPolicies[1]), address(p1)); + + // Page Size 1 + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(p4), + 1 + ); + assertEq(enabledSecurityPolicies.length, 1); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(p3), + 1 + ); + assertEq(enabledSecurityPolicies.length, 1); + assertEq(address(enabledSecurityPolicies[0]), address(p3)); + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(p2), + 1 + ); + assertEq(enabledSecurityPolicies.length, 1); + assertEq(address(enabledSecurityPolicies[0]), address(p2)); + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(p1), + 1 + ); + assertEq(enabledSecurityPolicies.length, 1); + assertEq(address(enabledSecurityPolicies[0]), address(p1)); + } + + function testDisableSingleSecurityPolicyPluginsRange() external { + // Enable p1,p2,p3,p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable p3,p2 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPoliciesRange, + (p3, p2, p4) + ) + ); + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.expectEmit(true, true, true, true); + emit SecurityPolicyDisabled(address(sa), address(p3)); + emit SecurityPolicyDisabled(address(sa), address(p2)); + + entryPoint.handleOps(arraifyOps(op), owner.addr); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 2); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p1)); + + // Disable p4,p1 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPoliciesRange, + (p4, p1, ISecurityPolicyPlugin(SENTINEL_MODULE_ADDRESS)) + ) + ); + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.expectEmit(true, true, true, true); + emit SecurityPolicyDisabled(address(sa), address(p4)); + emit SecurityPolicyDisabled(address(sa), address(p1)); + + entryPoint.handleOps(arraifyOps(op), owner.addr); + + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(0), + 100 + ); + assertEq(enabledSecurityPolicies.length, 0); + } + + function testEnableMultipleSecurityPolicyPlugins() external { + // Enable p1, p2 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 2 + ); + policies[0] = p1; + policies[1] = p2; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.expectEmit(true, true, true, true); + emit SecurityPolicyEnabled(address(sa), address(p1)); + vm.expectEmit(true, true, true, true); + emit SecurityPolicyEnabled(address(sa), address(p2)); + + entryPoint.handleOps(arraifyOps(op), owner.addr); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 2); + assertEq(address(enabledSecurityPolicies[0]), address(p2)); + assertEq(address(enabledSecurityPolicies[1]), address(p1)); + + // Enable p3, p4 + policies[0] = p3; + policies[1] = p4; + + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.expectEmit(true, true, true, true); + emit SecurityPolicyEnabled(address(sa), address(p3)); + vm.expectEmit(true, true, true, true); + emit SecurityPolicyEnabled(address(sa), address(p4)); + + entryPoint.handleOps(arraifyOps(op), owner.addr); + + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(0), + 100 + ); + assertEq(enabledSecurityPolicies.length, 4); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + assertEq(address(enabledSecurityPolicies[3]), address(p1)); + } + + function testAddAndRemoveAllPolicies() external { + // Enable p1,p2,p3,p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable p1,p2,p3,p4 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPoliciesRange, + (p4, p1, ISecurityPolicyPlugin(SENTINEL_MODULE_ADDRESS)) + ) + ); + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.expectEmit(true, true, true, true); + emit SecurityPolicyDisabled(address(sa), address(p4)); + emit SecurityPolicyDisabled(address(sa), address(p3)); + emit SecurityPolicyDisabled(address(sa), address(p2)); + emit SecurityPolicyDisabled(address(sa), address(p1)); + + entryPoint.handleOps(arraifyOps(op), owner.addr); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 0); + + // Enable p1,p2,p3,p4 + policies = new ISecurityPolicyPlugin[](4); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable p1,p2,p3,p4 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPoliciesRange, + (p4, p1, ISecurityPolicyPlugin(SENTINEL_MODULE_ADDRESS)) + ) + ); + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.expectEmit(true, true, true, true); + emit SecurityPolicyDisabled(address(sa), address(p4)); + emit SecurityPolicyDisabled(address(sa), address(p3)); + emit SecurityPolicyDisabled(address(sa), address(p2)); + emit SecurityPolicyDisabled(address(sa), address(p1)); + + entryPoint.handleOps(arraifyOps(op), owner.addr); + + enabledSecurityPolicies = spmp.securityPoliciesPaginated( + address(sa), + address(0), + 100 + ); + assertEq(enabledSecurityPolicies.length, 0); + } + + function testShouldNotAllowEmptyEnableList() external { + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 0 + ); + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256(abi.encodePacked(EmptyPolicyList.selector)) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 0); + } + + function testShouldNotAllowPolicyAdditionWithZeroAddressSingle() external { + ISecurityPolicyPlugin policy = ISecurityPolicyPlugin(address(0x0)); + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicy, + (policy) + ) + ); + + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector( + InvalidSecurityPolicyAddress.selector, + policy + ) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 0); + } + + function testShouldNotAllowPolicyAdditionWithZeroAddressMulti() external { + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 1 + ); + policies[0] = ISecurityPolicyPlugin(address(0x0)); + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector( + InvalidSecurityPolicyAddress.selector, + policies[0] + ) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 0); + } + + function testShouldNotAllowPolicyAdditionWithSentinelAddressSingle() + external + { + ISecurityPolicyPlugin policy = ISecurityPolicyPlugin( + SENTINEL_MODULE_ADDRESS + ); + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicy, + (policy) + ) + ); + + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector( + InvalidSecurityPolicyAddress.selector, + policy + ) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 0); + } + + function testShouldNotAllowPolicyAdditionWithSentinelAddressMulti() + external + { + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 1 + ); + policies[0] = ISecurityPolicyPlugin(SENTINEL_MODULE_ADDRESS); + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector( + InvalidSecurityPolicyAddress.selector, + policies[0] + ) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 0); + } + + function testShouldNotAllowDisablingZeroAddressPolicySingleDisable() + external + { + // Enable p1,p2,p3,p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable address(0) + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPolicy, + (ISecurityPolicyPlugin(address(0)), p4) + ) + ); + + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector( + InvalidSecurityPolicyAddress.selector, + ISecurityPolicyPlugin(address(0)) + ) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 4); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + assertEq(address(enabledSecurityPolicies[3]), address(p1)); + } + + function testShouldNotAllowDisablingSentinelAddressPolicySingleDisable() + external + { + // Enable p1,p2,p3,p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable SENTINEL_MODULE_ADDRESS + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPolicy, + (ISecurityPolicyPlugin(SENTINEL_MODULE_ADDRESS), p4) + ) + ); + + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector( + InvalidSecurityPolicyAddress.selector, + ISecurityPolicyPlugin(SENTINEL_MODULE_ADDRESS) + ) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 4); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + assertEq(address(enabledSecurityPolicies[3]), address(p1)); + } + + function testShouldNotAllowDisablingAlreadyDisabledPolicySingleDisable() + external + { + // Enable p1,p2,p3 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 3 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable p4 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPolicy, + (p4, p4) + ) + ); + + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector( + SecurityPolicyAlreadyDisabled.selector, + ISecurityPolicyPlugin(p4) + ) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 3); + assertEq(address(enabledSecurityPolicies[0]), address(p3)); + assertEq(address(enabledSecurityPolicies[1]), address(p2)); + assertEq(address(enabledSecurityPolicies[2]), address(p1)); + } + + function testShouldNotAllowDisablingWithInvalidPointerSingleDisable() + external + { + // Enable p1,p2,p3,p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable p3 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPolicy, + (p3, p2) + ) + ); + + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector(InvalidPointerAddress.selector, p2) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 4); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + assertEq(address(enabledSecurityPolicies[3]), address(p1)); + } + + function testShouldNotAllowDisablingRangeWithInvalidRange() external { + // Enable p1,p2,p3,p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable p1->p4 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPoliciesRange, + (p1, p4, p2) + ) + ); + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector( + InvalidSecurityPolicyAddress.selector, + p4 + ) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 4); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + assertEq(address(enabledSecurityPolicies[3]), address(p1)); + } + + function testShouldNotAllowDisablingRangeWithInvalidPointer() external { + // Enable p1,p2,p3,p4 + ISecurityPolicyPlugin[] memory policies = new ISecurityPolicyPlugin[]( + 4 + ); + policies[0] = p1; + policies[1] = p2; + policies[2] = p3; + policies[3] = p4; + + bytes memory data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.enableSecurityPolicies, + (policies) + ) + ); + UserOperation memory op = makeEcdsaModuleUserOp(data, sa, 0, alice); + entryPoint.handleOps(arraifyOps(op), owner.addr); + + // Disable p4->p1 + data = getSmartAccountExecuteCalldata( + address(spmp), + 0, + abi.encodeCall( + ISecurityPolicyManagerPlugin.disableSecurityPoliciesRange, + (p4, p1, p2) + ) + ); + op = makeEcdsaModuleUserOp(data, sa, 0, alice); + + vm.recordLogs(); + entryPoint.handleOps(arraifyOps(op), owner.addr); + Vm.Log[] memory logs = vm.getRecordedLogs(); + + UserOperationEventData memory eventData = getUserOperationEventData( + logs + ); + assertFalse(eventData.success); + UserOperationRevertReasonEventData + memory revertReasonEventData = getUserOperationRevertReasonEventData( + logs + ); + assertEq( + keccak256(revertReasonEventData.revertReason), + keccak256( + abi.encodeWithSelector(InvalidPointerAddress.selector, p2) + ) + ); + + ISecurityPolicyPlugin[] memory enabledSecurityPolicies = spmp + .securityPoliciesPaginated(address(sa), address(0), 100); + assertEq(enabledSecurityPolicies.length, 4); + assertEq(address(enabledSecurityPolicies[0]), address(p4)); + assertEq(address(enabledSecurityPolicies[1]), address(p3)); + assertEq(address(enabledSecurityPolicies[2]), address(p2)); + assertEq(address(enabledSecurityPolicies[3]), address(p1)); + } +}